mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3e1317bac | ||
|
|
85a05765e2 | ||
|
|
42be1d46a6 | ||
|
|
0cfa2ad436 | ||
|
|
f1fd245073 | ||
|
|
8529a2cf1a | ||
|
|
44fad139fe | ||
|
|
26a41df3e3 | ||
|
|
1174d7d77f | ||
|
|
392888d113 | ||
|
|
da95588b25 | ||
|
|
dfe534b1a6 | ||
|
|
66db873651 | ||
|
|
d60dd745e4 | ||
|
|
45d959a49e | ||
|
|
28b5e3382c | ||
|
|
96bc00007b | ||
|
|
70a5880d36 | ||
|
|
0f26418f4a | ||
|
|
ad9f1fa454 | ||
|
|
419bc4b175 | ||
|
|
5753516da5 | ||
|
|
be39dcfd5b | ||
|
|
0c22f52b46 | ||
|
|
aedf369174 | ||
|
|
95f8db67f3 | ||
|
|
21411282bb | ||
|
|
9cb777a6b6 | ||
|
|
0b959b8ec6 | ||
|
|
5d5b853f70 | ||
|
|
96324ea5ae | ||
|
|
02fe2e7511 | ||
|
|
7c227ece0d | ||
|
|
6922631b03 | ||
|
|
4483d0cab9 | ||
|
|
568d9cc8a0 | ||
|
|
a03f5c0fab | ||
|
|
e7deaf9882 | ||
|
|
7bdfc0ea06 | ||
|
|
34ba176f52 | ||
|
|
8196857c8f | ||
|
|
3c3c03455d | ||
|
|
bef0a36c55 | ||
|
|
3ebfc2b0af | ||
|
|
8f68f048d8 | ||
|
|
485d1d1e37 | ||
|
|
03e08f2825 | ||
|
|
e147546b3d | ||
|
|
abcd920964 | ||
|
|
e9e72fbbf8 | ||
|
|
de0b6b1528 | ||
|
|
5861dcad58 | ||
|
|
1169d9bfd3 | ||
|
|
97b8bb73ab | ||
|
|
8627d3c5ae | ||
|
|
4098793db2 | ||
|
|
cec84849b9 | ||
|
|
b883f9a9d2 | ||
|
|
34b549cfe2 | ||
|
|
c0eeea9c83 | ||
|
|
c95642863e | ||
|
|
77d9a53423 | ||
|
|
70b90c41dc | ||
|
|
866623a39e |
@@ -52,8 +52,6 @@ chore: upgrade sqlx to 0.7
|
||||
4. Stage ONLY the modified/relevant files: `git add <file1> <file2> ...`
|
||||
5. Create the commit with conventional format:
|
||||
```bash
|
||||
git commit -m "<type>: <description>
|
||||
|
||||
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
||||
git commit -m "<type>: <description>"
|
||||
```
|
||||
6. Run `git status` to verify the commit succeeded
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: update-sqlx
|
||||
description: How to safely update SQLx offline query cache. MUST use when SQL queries change.
|
||||
---
|
||||
|
||||
# SQLx Offline Query Cache
|
||||
|
||||
Windmill uses `SQLX_OFFLINE=true` in CI, which requires all `sqlx::query!` / `sqlx::query_as!` macros to have matching cached query data in `backend/.sqlx/`.
|
||||
|
||||
## When to Run
|
||||
|
||||
Run after any change to SQL queries in Rust source files. Without it, CI will fail with:
|
||||
```
|
||||
error: `SQLX_OFFLINE=true` but there is no cached data for this query
|
||||
```
|
||||
|
||||
## The Problem
|
||||
|
||||
`cargo sqlx prepare --workspace` **deletes all existing cache files** and regenerates only the ones found in the current compilation. If you don't compile with every feature flag (especially `private` for EE files), you will **silently delete EE query caches**, breaking CI for enterprise tests.
|
||||
|
||||
The standard `./update_sqlx.sh` script tries to compile with all features, but it often fails locally because the EE symlinks can be out of sync with `main`.
|
||||
|
||||
## Safe Procedure
|
||||
|
||||
Always preserve the existing EE caches from `origin/main`. Use this workflow:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# 1. Restore the full cache from main (includes EE caches)
|
||||
git checkout origin/main -- .sqlx/
|
||||
|
||||
# 2. Run prepare with OSS features (what compiles locally)
|
||||
# This regenerates OSS caches to match your code changes.
|
||||
cargo sqlx prepare --workspace -- --workspace --features all_sqlx_features
|
||||
|
||||
# 3. Restore any EE caches that were deleted in step 2.
|
||||
# These are files present in origin/main but missing after prepare.
|
||||
git ls-tree origin/main backend/.sqlx/ \
|
||||
| awk '{print $4}' | sed 's|backend/\.sqlx/||' | sort > /tmp/main_files.txt
|
||||
|
||||
find backend/.sqlx -name "*.json" -printf '%P\n' | sort > /tmp/current_files.txt
|
||||
|
||||
comm -23 /tmp/main_files.txt /tmp/current_files.txt > /tmp/missing_files.txt
|
||||
|
||||
while read f; do
|
||||
git show "origin/main:backend/.sqlx/$f" > "backend/.sqlx/$f"
|
||||
done < /tmp/missing_files.txt
|
||||
|
||||
# 4. Verify nothing was lost from main
|
||||
find backend/.sqlx -name "*.json" -printf '%P\n' | sort > /tmp/current_files.txt
|
||||
comm -23 /tmp/main_files.txt /tmp/current_files.txt | wc -l
|
||||
# Should output: 0
|
||||
```
|
||||
|
||||
## If EE Compiles Locally
|
||||
|
||||
If your EE repo happens to be in sync, you can use the full script (faster):
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
./update_sqlx.sh
|
||||
```
|
||||
|
||||
But if it fails with EE compilation errors, use the safe procedure above.
|
||||
|
||||
## What NOT to Do
|
||||
|
||||
- **Never** run `cargo sqlx prepare --workspace` with only OSS features and commit the result — it will delete EE caches.
|
||||
- **Never** set `SQLX_OFFLINE=true` for local `cargo sqlx prepare` — use a live database per CLAUDE.md. (CI runs with `SQLX_OFFLINE=true`, which is why the cache must be complete.)
|
||||
- **Never** skip the verification step (step 4 above).
|
||||
|
||||
## Verification
|
||||
|
||||
After committing, the diff against `origin/main` should show:
|
||||
- A few **new** cache files (for your changed queries)
|
||||
- A few **deleted** cache files (for old queries that no longer exist)
|
||||
- **Zero** net deletions from the EE cache set
|
||||
|
||||
```bash
|
||||
git diff origin/main --stat backend/.sqlx/
|
||||
```
|
||||
@@ -111,7 +111,9 @@ Regenerate frontend client: `npm run generate-backend-client` from `frontend/`.
|
||||
|
||||
**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query.
|
||||
|
||||
**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON).
|
||||
**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON). The block re-uses the `trigger_ignore_keys` variable so the new kind automatically participates in fork-export stripping (`mode` field is omitted when the source workspace is a fork — keeps fork→parent merges from flipping the parent's enabled state).
|
||||
|
||||
**Fork cloning (`clone_triggers_and_schedules` in workspaces.rs)** — add an `INSERT INTO {kind}_trigger ... SELECT ...` block that copies all rows from the parent workspace, forcing `mode = 'disabled'::TRIGGER_MODE`. Always runs at fork creation; forgetting this means users can't carry `{kind}` triggers into their forks.
|
||||
|
||||
## 6.5 Hardcoded trigger-kind arrays (silent-failure hotspots)
|
||||
|
||||
|
||||
@@ -145,6 +145,10 @@ jobs:
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_BUILD_JOBS: 12
|
||||
# Tests' poll-time stack frames (deep nested async fn chains in
|
||||
# debug builds) reach ~1.8MB. 4MB gives ~2x headroom against flaky
|
||||
# overflows under parallel-test contention.
|
||||
RUST_MIN_STACK: 4194304
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
OPENSSL_DIR: ${{ env.VCPKG_INSTALLATION_ROOT }}\installed\x64-windows-static
|
||||
DENO_PATH: ${{ steps.runtime-paths.outputs.DENO_PATH }}
|
||||
|
||||
@@ -244,6 +244,11 @@ jobs:
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_BUILD_JOBS: 12
|
||||
# Tests' poll-time stack frames (deep nested async fn chains in
|
||||
# debug builds) reach ~1.8MB, leaving very thin headroom on the
|
||||
# default 2MB thread stack. 4MB gives ~2x buffer against flaky
|
||||
# overflows under parallel-test contention.
|
||||
RUST_MIN_STACK: 4194304
|
||||
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- "backend/windmill-api/openapi.yaml"
|
||||
- "cli/src/main.ts"
|
||||
- "cli/src/commands/**"
|
||||
- "frontend/src/lib/components/copilot/chat/workspaceToolsZod.gen.ts"
|
||||
pull_request:
|
||||
paths:
|
||||
- "system_prompts/**"
|
||||
@@ -19,6 +20,7 @@ on:
|
||||
- "backend/windmill-api/openapi.yaml"
|
||||
- "cli/src/main.ts"
|
||||
- "cli/src/commands/**"
|
||||
- "frontend/src/lib/components/copilot/chat/workspaceToolsZod.gen.ts"
|
||||
|
||||
jobs:
|
||||
check-freshness:
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Fast Claude
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai-fast')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai-fast')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai-fast')) ||
|
||||
(github.event_name == 'issues' && contains(github.event.issue.body, '/ai-fast'))
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
claude-code-action:
|
||||
needs: check-membership
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude PR Action
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
allowed_bots: "windmill-internal-app[bot]"
|
||||
trigger_phrase: "/ai-fast"
|
||||
settings: |
|
||||
{
|
||||
"env": {
|
||||
"SQLX_OFFLINE": "true"
|
||||
}
|
||||
}
|
||||
claude_args: |
|
||||
--allowedTools "Bash,WebFetch,WebSearch"
|
||||
--model opus
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Claude PR Assistant
|
||||
name: Fast Claude
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
@@ -26,7 +26,6 @@ jobs:
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -38,37 +37,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Run npm install and generate-backend-client
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
# add a build directory for cargo check
|
||||
mkdir -p build
|
||||
npm install
|
||||
npm run generate-backend-client
|
||||
|
||||
- name: install xmlsec1 and gssapi
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev libsasl2-dev libcurl4-openssl-dev mold clang
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
|
||||
- name: cargo check
|
||||
working-directory: ./backend
|
||||
timeout-minutes: 16
|
||||
run: |
|
||||
SQLX_OFFLINE=true cargo check --features all_sqlx_features
|
||||
|
||||
- name: Run Claude PR Action
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
@@ -84,24 +52,3 @@ jobs:
|
||||
claude_args: |
|
||||
--allowedTools "Bash,WebFetch,WebSearch"
|
||||
--model opus
|
||||
--system-prompt "## IMPORTANT INSTRUCTIONS
|
||||
- Your branch name should be a short description of the requested changes.
|
||||
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main.
|
||||
|
||||
## Code Quality Requirements
|
||||
|
||||
After making any code changes, you MUST run the appropriate validation commands:
|
||||
|
||||
**Frontend Changes:**
|
||||
- Run: \`npm run check\` in the frontend directory
|
||||
- Fix all warnings and errors before proceeding
|
||||
|
||||
**Backend Changes:**
|
||||
- Run: \`cargo check --features all_sqlx_features\` in the backend directory
|
||||
- Fix all warnings and errors before proceeding
|
||||
|
||||
**Pull Request Creation:**
|
||||
- DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
|
||||
|
||||
## Available Tools
|
||||
- Bash: Full access to run validation commands and git operations"
|
||||
|
||||
@@ -1,5 +1,87 @@
|
||||
# Changelog
|
||||
|
||||
## [1.694.0](https://github.com/windmill-labs/windmill/compare/v1.693.4...v1.694.0) (2026-05-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ansible delegate_to_git_repo install_requirements, dynamic fields, --limit ([#8997](https://github.com/windmill-labs/windmill/issues/8997)) ([96324ea](https://github.com/windmill-labs/windmill/commit/96324ea5aed4054d33895102ec9313f5dadc77a2))
|
||||
* **cli:** wmill-lock.yaml auto-fill + --rehash-only + path-prefix dedup ([#8978](https://github.com/windmill-labs/windmill/issues/8978)) ([0b959b8](https://github.com/windmill-labs/windmill/commit/0b959b8ec61b24d861c5a10a9242a7a0e6013707))
|
||||
* **forks:** handle triggers and schedules in workspace forks ([#8976](https://github.com/windmill-labs/windmill/issues/8976)) ([d60dd74](https://github.com/windmill-labs/windmill/commit/d60dd745e49853bb130b139f300fc0f2ab8ebe39))
|
||||
* support assigning a worker tag to app inline scripts ([#9002](https://github.com/windmill-labs/windmill/issues/9002)) ([0c22f52](https://github.com/windmill-labs/windmill/commit/0c22f52b46c56d3577309e37c1e81a1a1feb9b7c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** only preserve case for raw-app runnableIds, not app/flow summaries ([#9000](https://github.com/windmill-labs/windmill/issues/9000)) ([5d5b853](https://github.com/windmill-labs/windmill/commit/5d5b853f70a73453f63d14edcd5d2fac8e3d804c))
|
||||
* distinguish AlreadyCompleted from execution failure on OTLP job span ([#9004](https://github.com/windmill-labs/windmill/issues/9004)) ([70a5880](https://github.com/windmill-labs/windmill/commit/70a5880d3619edece4ce67a00407ee0d2d523469))
|
||||
* nested-restart iteration count for step-id collisions across subflow boundaries ([#9003](https://github.com/windmill-labs/windmill/issues/9003)) ([ad9f1fa](https://github.com/windmill-labs/windmill/commit/ad9f1fa4541f2eefb0b013bac42cd626af424852))
|
||||
* omit empty assets array on scripts and raw app inline scripts ([#9006](https://github.com/windmill-labs/windmill/issues/9006)) ([419bc4b](https://github.com/windmill-labs/windmill/commit/419bc4b1757a7c20c2b7aa9b7d3b02e3515f934a))
|
||||
* pair PG arg type with actual Rust binding to keep query_typed_raw safe ([#8999](https://github.com/windmill-labs/windmill/issues/8999)) ([aedf369](https://github.com/windmill-labs/windmill/commit/aedf3691744748a307976ef01ea7f63b0961fad4))
|
||||
* route email trigger path through standard info channel ([#8996](https://github.com/windmill-labs/windmill/issues/8996)) ([2141128](https://github.com/windmill-labs/windmill/commit/21411282bb4a0442046bf71fdc7ea012fa2c3d3d))
|
||||
* surface scope errors as 403 and show real message in CLI ([#8953](https://github.com/windmill-labs/windmill/issues/8953)) ([66db873](https://github.com/windmill-labs/windmill/commit/66db873651a04b0701d8231fbf11ab973c3fc69b))
|
||||
* use otel.status_message for OTLP Status.message on failed jobs ([#8995](https://github.com/windmill-labs/windmill/issues/8995)) ([9cb777a](https://github.com/windmill-labs/windmill/commit/9cb777a6b6e969696cf9beade1cf07c86967dafc))
|
||||
|
||||
## [1.693.4](https://github.com/windmill-labs/windmill/compare/v1.693.3...v1.693.4) (2026-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** pin wasm parser versions in published package.json ([#8993](https://github.com/windmill-labs/windmill/issues/8993)) ([7c227ec](https://github.com/windmill-labs/windmill/commit/7c227ece0d546ef0f521a963f927e4c913011e8e))
|
||||
|
||||
## [1.693.3](https://github.com/windmill-labs/windmill/compare/v1.693.2...v1.693.3) (2026-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid named prepared statements in datatable/PG executor ([#8988](https://github.com/windmill-labs/windmill/issues/8988)) ([7bdfc0e](https://github.com/windmill-labs/windmill/commit/7bdfc0ea0636a93a893ab5f9f58a590e6a0b7be8))
|
||||
* improve raw app builder queue behavior for bigger apps ([e7deaf9](https://github.com/windmill-labs/windmill/commit/e7deaf988254eebbf99febc8a644622c9f809bd8))
|
||||
* sanitize underscores in agent worker suffix ([#8992](https://github.com/windmill-labs/windmill/issues/8992)) ([568d9cc](https://github.com/windmill-labs/windmill/commit/568d9cc8a086178314e15e3c27fb55ff4817f5cd))
|
||||
* **workspaces:** split get_settings into admin-only + public endpoint ([#8990](https://github.com/windmill-labs/windmill/issues/8990)) ([4483d0c](https://github.com/windmill-labs/windmill/commit/4483d0cab91e8a9df026b09c7908291f3fbbfd63))
|
||||
|
||||
## [1.693.2](https://github.com/windmill-labs/windmill/compare/v1.693.1...v1.693.2) (2026-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid effect_update_depth_exceeded when clicking flow node on runs page ([#8986](https://github.com/windmill-labs/windmill/issues/8986)) ([3ebfc2b](https://github.com/windmill-labs/windmill/commit/3ebfc2b0af38f7eb17774de08a214d7813e07952))
|
||||
* OAuth popup login reliability + auto-login Safari edge cases ([#8971](https://github.com/windmill-labs/windmill/issues/8971)) ([3c3c034](https://github.com/windmill-labs/windmill/commit/3c3c03455d68fde982937787992e20c3f8eeeaaf))
|
||||
|
||||
## [1.693.1](https://github.com/windmill-labs/windmill/compare/v1.693.0...v1.693.1) (2026-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include labels when loading flow with draft for editing ([#8981](https://github.com/windmill-labs/windmill/issues/8981)) ([485d1d1](https://github.com/windmill-labs/windmill/commit/485d1d1e3785b5ed7e5f1a5ee127c0fed15fba3e)), closes [#8963](https://github.com/windmill-labs/windmill/issues/8963)
|
||||
|
||||
## [1.693.0](https://github.com/windmill-labs/windmill/compare/v1.692.0...v1.693.0) (2026-04-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ai chat schedule and trigger tools ([#8961](https://github.com/windmill-labs/windmill/issues/8961)) ([b883f9a](https://github.com/windmill-labs/windmill/commit/b883f9a9d2e38a5981860de268fa6227cdd645de))
|
||||
* add delete_after_secs and sensitive_inputs for raw app runnables ([#8975](https://github.com/windmill-labs/windmill/issues/8975)) ([1169d9b](https://github.com/windmill-labs/windmill/commit/1169d9bfd315e43194f9e5a2bc55af843476ef68))
|
||||
* add min release age instance settings for bun and uv ([#8956](https://github.com/windmill-labs/windmill/issues/8956)) ([1d279e7](https://github.com/windmill-labs/windmill/commit/1d279e7a1e77fd183bb0c99d2430cfd9dc0a617c))
|
||||
* edit scopes on existing API tokens ([#8967](https://github.com/windmill-labs/windmill/issues/8967)) ([e9e72fb](https://github.com/windmill-labs/windmill/commit/e9e72fbbf83363ba1426b3dde3d657de02ba50b3))
|
||||
* OTEL span status on failed jobs + Python stderr severity classification ([#8918](https://github.com/windmill-labs/windmill/issues/8918)) ([cec8484](https://github.com/windmill-labs/windmill/commit/cec84849b9aea92d355dd346546969027c603498))
|
||||
* support restart from steps inside BranchOne, ForLoop, Subflow ([#8955](https://github.com/windmill-labs/windmill/issues/8955)) ([c956428](https://github.com/windmill-labs/windmill/commit/c95642863e366c106d529a57540a75df9480397c))
|
||||
* support S3Object input args in native SQL scripts ([#8954](https://github.com/windmill-labs/windmill/issues/8954)) ([c0eeea9](https://github.com/windmill-labs/windmill/commit/c0eeea9c833f9be3981389a19d0964400fd2bda8))
|
||||
* workspace-shared ui/ folder reusable across raw apps ([#8974](https://github.com/windmill-labs/windmill/issues/8974)) ([de0b6b1](https://github.com/windmill-labs/windmill/commit/de0b6b15285612ef00b94e332dcb203aed88f2cc))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** debounce wmill dev flow round-trip 200ms ([#8977](https://github.com/windmill-labs/windmill/issues/8977)) ([5861dca](https://github.com/windmill-labs/windmill/commit/5861dcad589df99f57e730d8d9cdb3eabc3424e4))
|
||||
* prevent React app editor from overwriting files on theme switch ([#8965](https://github.com/windmill-labs/windmill/issues/8965)) ([70b90c4](https://github.com/windmill-labs/windmill/commit/70b90c41dc28d1a850bce69836cde920c1404c3b))
|
||||
* show skipped label on flow progress bar ([#8973](https://github.com/windmill-labs/windmill/issues/8973)) ([8627d3c](https://github.com/windmill-labs/windmill/commit/8627d3c5aeabfac12a9f06211f8e7db020e758f9))
|
||||
* split flow prompts for frontend chat ([#8968](https://github.com/windmill-labs/windmill/issues/8968)) ([4098793](https://github.com/windmill-labs/windmill/commit/4098793db22249c5b4467c2adb72131407f5d6d3))
|
||||
* strip additionalProperties from google schemas ([#8964](https://github.com/windmill-labs/windmill/issues/8964)) ([77d9a53](https://github.com/windmill-labs/windmill/commit/77d9a534235a1ff8cbfcdb037a65735db987e2fe))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* optimize datatable app chat schemas ([#8960](https://github.com/windmill-labs/windmill/issues/8960)) ([34b549c](https://github.com/windmill-labs/windmill/commit/34b549cfe2e2e060561eabe369a99cfb4d9c9568))
|
||||
|
||||
## [1.692.0](https://github.com/windmill-labs/windmill/compare/v1.691.1...v1.692.0) (2026-04-27)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { FlowModule, InputTransform } from '../../../../../frontend/src/lib
|
||||
import type { ExtendedOpenFlow } from '../../../../../frontend/src/lib/components/flows/types'
|
||||
import type { FlowAIChatHelpers } from '../../../../../frontend/src/lib/components/copilot/chat/flow/core'
|
||||
import type { ScriptLintResult } from '../../../../../frontend/src/lib/components/copilot/chat/shared'
|
||||
import type { WorkspaceMutationTarget } from '../../../../../frontend/src/lib/components/copilot/chat/workspaceTools'
|
||||
import {
|
||||
createInlineScriptSession
|
||||
} from '../../../../../frontend/src/lib/components/copilot/chat/flow/inlineScriptsUtils'
|
||||
@@ -38,7 +39,8 @@ export async function createFlowFileHelpers(
|
||||
initialPreprocessorModule?: FlowModule,
|
||||
initialFailureModule?: FlowModule,
|
||||
workspaceRoot?: string,
|
||||
workspaceFixtures?: FlowWorkspaceFixtures
|
||||
workspaceFixtures?: FlowWorkspaceFixtures,
|
||||
currentFlowPath?: string
|
||||
): Promise<{
|
||||
helpers: FlowAIChatHelpers
|
||||
getFlow: () => ExtendedOpenFlow
|
||||
@@ -97,10 +99,17 @@ export async function createFlowFileHelpers(
|
||||
return result
|
||||
}
|
||||
|
||||
const helpers: FlowAIChatHelpers = {
|
||||
const helpers: FlowAIChatHelpers & {
|
||||
getWorkspaceMutationTarget: () => WorkspaceMutationTarget
|
||||
} = {
|
||||
getFlowAndSelectedId: () => ({ flow, selectedId: '' }),
|
||||
getRootModules: () => flow.value.modules,
|
||||
inlineScriptSession,
|
||||
getWorkspaceMutationTarget: () => ({
|
||||
kind: 'flow',
|
||||
path: currentFlowPath,
|
||||
deployed: Boolean(currentFlowPath)
|
||||
}),
|
||||
setSnapshot: () => {},
|
||||
revertToSnapshot: () => {},
|
||||
setCode: async (id: string, code: string) => {
|
||||
|
||||
@@ -17,11 +17,12 @@ import {
|
||||
} from "./fileHelpers";
|
||||
import { runEval } from "../shared";
|
||||
import type { ModeRunContext } from "../../../../core/types";
|
||||
import type { TokenUsage } from "../shared/types";
|
||||
import type { TokenUsage, ToolCallDetail } from "../shared/types";
|
||||
import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
|
||||
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
|
||||
|
||||
export interface FlowFixture {
|
||||
path?: string;
|
||||
value?: {
|
||||
modules?: FlowModule[];
|
||||
preprocessor_module?: FlowModule;
|
||||
@@ -37,6 +38,7 @@ export interface FlowEvalResult {
|
||||
assistantMessageCount: number;
|
||||
toolCallCount: number;
|
||||
toolsUsed: string[];
|
||||
toolCallDetails: ToolCallDetail[];
|
||||
tokenUsage: TokenUsage;
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ export async function runFlowEval(
|
||||
options?.initialFlow?.value?.failure_module,
|
||||
workspaceRoot,
|
||||
options?.workspaceFixtures,
|
||||
options?.initialFlow?.path,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -111,6 +114,7 @@ export async function runFlowEval(
|
||||
assistantMessageCount: rawResult.iterations,
|
||||
toolCallCount: rawResult.toolCallsCount,
|
||||
toolsUsed: rawResult.toolsCalled,
|
||||
toolCallDetails: rawResult.toolCallDetails,
|
||||
tokenUsage: rawResult.tokenUsage,
|
||||
};
|
||||
} finally {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mkdir, rm, writeFile } from 'fs/promises'
|
||||
import { dirname, join } from 'path'
|
||||
import type { ScriptLang } from '../../../../../frontend/src/lib/gen/types.gen'
|
||||
import type { ScriptChatHelpers } from '../../../../../frontend/src/lib/components/copilot/chat/script/core'
|
||||
import type { WorkspaceMutationTarget } from '../../../../../frontend/src/lib/components/copilot/chat/workspaceTools'
|
||||
import { buildScriptLintResult } from './preview'
|
||||
import { registerBenchmarkWorkspace, unregisterBenchmarkWorkspace } from '../../mockBackend'
|
||||
|
||||
@@ -12,6 +13,10 @@ export interface ScriptEvalState {
|
||||
args: Record<string, any>
|
||||
}
|
||||
|
||||
function toRunnablePath(filePath: string): string {
|
||||
return filePath.replace(/\.[^/.]+$/, '')
|
||||
}
|
||||
|
||||
export async function createScriptFileHelpers(
|
||||
initialScript: ScriptEvalState,
|
||||
workspaceRoot?: string
|
||||
@@ -55,13 +60,20 @@ export async function createScriptFileHelpers(
|
||||
const getLintErrors: NonNullable<ScriptChatHelpers['getLintErrors']> = () =>
|
||||
buildScriptLintResult(script.code, script.lang)
|
||||
|
||||
const helpers: ScriptChatHelpers = {
|
||||
const helpers: ScriptChatHelpers & {
|
||||
getWorkspaceMutationTarget: () => WorkspaceMutationTarget
|
||||
} = {
|
||||
getScriptOptions: () => ({
|
||||
code: script.code,
|
||||
lang: script.lang,
|
||||
path: script.path,
|
||||
args: structuredClone(script.args)
|
||||
}),
|
||||
getWorkspaceMutationTarget: () => ({
|
||||
kind: 'script',
|
||||
path: script.path ? toRunnablePath(script.path) : undefined,
|
||||
deployed: Boolean(script.path)
|
||||
}),
|
||||
applyCode,
|
||||
getLintErrors
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { Tool as ProductionTool } from "../../../../../frontend/src/lib/com
|
||||
import { createScriptFileHelpers, type ScriptEvalState } from "./fileHelpers";
|
||||
import { runEval } from "../shared";
|
||||
import type { ModeRunContext } from "../../../../core/types";
|
||||
import type { TokenUsage } from "../shared/types";
|
||||
import type { TokenUsage, ToolCallDetail } from "../shared/types";
|
||||
import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
|
||||
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ScriptEvalResult {
|
||||
assistantMessageCount: number;
|
||||
toolCallCount: number;
|
||||
toolsUsed: string[];
|
||||
toolCallDetails: ToolCallDetail[];
|
||||
tokenUsage: TokenUsage;
|
||||
}
|
||||
|
||||
@@ -111,6 +112,7 @@ export async function runScriptEval(
|
||||
assistantMessageCount: rawResult.iterations,
|
||||
toolCallCount: rawResult.toolCallsCount,
|
||||
toolsUsed: rawResult.toolsCalled,
|
||||
toolCallDetails: rawResult.toolCallDetails,
|
||||
tokenUsage: rawResult.tokenUsage,
|
||||
};
|
||||
} finally {
|
||||
|
||||
@@ -227,6 +227,60 @@ export function runBenchmarkFlowByPath(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export function previewBenchmarkSchedule(input: {
|
||||
requestBody?: Record<string, unknown>
|
||||
}): Record<string, unknown> {
|
||||
const schedule = input.requestBody?.schedule
|
||||
if (typeof schedule !== 'string' || schedule.trim().split(/\s+/).length !== 6) {
|
||||
throw new Error(`schedule must use a six-field cron expression, got ${JSON.stringify(schedule)}`)
|
||||
}
|
||||
|
||||
return {
|
||||
next_runs: ['1970-01-02T00:00:00.000Z']
|
||||
}
|
||||
}
|
||||
|
||||
export function createBenchmarkSchedule(input: {
|
||||
workspace: string
|
||||
requestBody: Record<string, unknown>
|
||||
}): Record<string, unknown> {
|
||||
assertBenchmarkWorkspacePath('schedule', input.requestBody.path)
|
||||
assertBenchmarkWorkspacePath('target', input.requestBody.script_path)
|
||||
return {
|
||||
path: input.requestBody.path,
|
||||
target_path: input.requestBody.script_path,
|
||||
is_flow: input.requestBody.is_flow,
|
||||
mocked: true
|
||||
}
|
||||
}
|
||||
|
||||
export function createBenchmarkHttpTrigger(input: {
|
||||
workspace: string
|
||||
requestBody: Record<string, unknown>
|
||||
}): Record<string, unknown> {
|
||||
assertBenchmarkWorkspacePath('trigger', input.requestBody.path)
|
||||
assertBenchmarkWorkspacePath('target', input.requestBody.script_path)
|
||||
if (
|
||||
typeof input.requestBody.route_path === 'string' &&
|
||||
input.requestBody.route_path.startsWith('/')
|
||||
) {
|
||||
throw new Error(`HTTP trigger route_path must not start with /, got "${input.requestBody.route_path}"`)
|
||||
}
|
||||
return {
|
||||
path: input.requestBody.path,
|
||||
target_path: input.requestBody.script_path,
|
||||
route_path: input.requestBody.route_path,
|
||||
is_flow: input.requestBody.is_flow,
|
||||
mocked: true
|
||||
}
|
||||
}
|
||||
|
||||
function assertBenchmarkWorkspacePath(label: string, value: unknown): void {
|
||||
if (typeof value !== 'string' || (!value.startsWith('f/') && !value.startsWith('u/'))) {
|
||||
throw new Error(`${label} path must start with f/ or u/, got ${JSON.stringify(value)}`)
|
||||
}
|
||||
}
|
||||
|
||||
function buildBenchmarkScriptHash(path: string): string {
|
||||
return `benchmark:${path}`
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ vi.mock('$lib/gen', async () => {
|
||||
hasBenchmarkWorkspace,
|
||||
listBenchmarkFlows,
|
||||
listBenchmarkScripts,
|
||||
createBenchmarkHttpTrigger,
|
||||
createBenchmarkSchedule,
|
||||
previewBenchmarkSchedule,
|
||||
runBenchmarkFlowByPath,
|
||||
runBenchmarkScriptPreview
|
||||
} = await import('./mockBackend')
|
||||
@@ -137,6 +140,20 @@ vi.mock('$lib/gen', async () => {
|
||||
}
|
||||
return actual.JobService.getJob(data)
|
||||
}
|
||||
}),
|
||||
ScheduleService: wrapService(actual.ScheduleService, {
|
||||
previewSchedule: async (data: { requestBody?: Record<string, unknown> }) =>
|
||||
previewBenchmarkSchedule(data),
|
||||
createSchedule: async (data: { workspace: string; requestBody: Record<string, unknown> }) =>
|
||||
hasBenchmarkWorkspace(data.workspace)
|
||||
? createBenchmarkSchedule(data)
|
||||
: actual.ScheduleService.createSchedule(data)
|
||||
}),
|
||||
HttpTriggerService: wrapService(actual.HttpTriggerService, {
|
||||
createHttpTrigger: async (data: { workspace: string; requestBody: Record<string, unknown> }) =>
|
||||
hasBenchmarkWorkspace(data.workspace)
|
||||
? createBenchmarkHttpTrigger(data)
|
||||
: actual.HttpTriggerService.createHttpTrigger(data)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -129,6 +129,70 @@
|
||||
- does not duplicate the greeting logic in a new inline script
|
||||
- wires the name input into the reused script
|
||||
|
||||
- id: wac-typescript-order-workflow
|
||||
prompt: |-
|
||||
Create a Windmill Workflow-as-Code TypeScript script at `f/evals/order_workflow.ts`.
|
||||
It should take an `orderId` string, load the order in a durable task, checkpoint a processing timestamp with `step`, and return `{ orderId, processedAt, status }`.
|
||||
cliExpect:
|
||||
requiredSkills:
|
||||
- write-workflow-as-code
|
||||
requiredSkillsBeforeFirstMutation:
|
||||
- write-workflow-as-code
|
||||
forbiddenSkills:
|
||||
- write-flow
|
||||
- write-script-bun
|
||||
- write-script-python3
|
||||
judgeChecklist:
|
||||
- creates the requested TypeScript WAC script at f/evals/order_workflow.ts
|
||||
- uses the Workflow-as-Code SDK from windmill-client
|
||||
- wraps the entrypoint with workflow
|
||||
- uses a durable task for loading the order
|
||||
- uses step to checkpoint the processing timestamp
|
||||
- does not create an OpenFlow flow.yaml or flow folder
|
||||
|
||||
- id: wac-python-approval-workflow
|
||||
prompt: |-
|
||||
Create a Windmill Workflow-as-Code Python script at `f/evals/approval_workflow.py`.
|
||||
It should take a `request_id` string, prepare an approval summary in a task, create resume URLs inside a durable step, wait for approval, and return the approval result.
|
||||
cliExpect:
|
||||
requiredSkills:
|
||||
- write-workflow-as-code
|
||||
requiredSkillsBeforeFirstMutation:
|
||||
- write-workflow-as-code
|
||||
forbiddenSkills:
|
||||
- write-flow
|
||||
- write-script-bun
|
||||
- write-script-python3
|
||||
judgeChecklist:
|
||||
- creates the requested Python WAC script at f/evals/approval_workflow.py
|
||||
- imports Workflow-as-Code helpers from wmill
|
||||
- decorates an async entrypoint with @workflow
|
||||
- uses @task for the approval summary work
|
||||
- gets resume URLs inside step before waiting for approval
|
||||
- uses wait_for_approval
|
||||
- does not create an OpenFlow flow.yaml or flow folder
|
||||
|
||||
- id: wac-not-openflow-disambiguation
|
||||
prompt: |-
|
||||
Create this as Workflow-as-Code, not an OpenFlow YAML flow: a TypeScript script at `f/evals/fanout_workflow.ts`.
|
||||
It should take an array of customer IDs, process each customer with a WAC task, run the independent customer tasks in parallel, and return the collected results.
|
||||
cliExpect:
|
||||
requiredSkills:
|
||||
- write-workflow-as-code
|
||||
requiredSkillsBeforeFirstMutation:
|
||||
- write-workflow-as-code
|
||||
forbiddenSkills:
|
||||
- write-flow
|
||||
- write-script-bun
|
||||
- write-script-python3
|
||||
judgeChecklist:
|
||||
- creates the requested TypeScript script at f/evals/fanout_workflow.ts
|
||||
- treats the request as Workflow-as-Code rather than an OpenFlow flow
|
||||
- uses workflow for the script entrypoint
|
||||
- uses task for each customer processing unit
|
||||
- runs independent customer tasks in parallel
|
||||
- does not create a flow folder or flow.yaml
|
||||
|
||||
- id: cli-job-debug-guidance
|
||||
prompt: |-
|
||||
A Windmill job failed.
|
||||
|
||||
@@ -412,3 +412,57 @@
|
||||
- the updated flow rejects empty payload strings
|
||||
- "the existing `process_event` step remains the main step"
|
||||
- failures return a compact error object with the error message and failing step id
|
||||
|
||||
- id: flow-test15-create-current-flow-schedule
|
||||
prompt: |-
|
||||
Update this flow by adding a final step named `return_schedule_status`.
|
||||
It should return an object with `scheduled: true` and the order summary from `results.summarize_orders`.
|
||||
Also create an enabled daily schedule named `order_processing_daily` for the current flow.
|
||||
It should run every day at 07:30 UTC with empty args.
|
||||
Do not ask me for the flow path.
|
||||
initial: ai_evals/fixtures/frontend/flow/initial/scheduled_order_flow.json
|
||||
validate:
|
||||
topLevelStepIds:
|
||||
- return_schedule_status
|
||||
toolExpect:
|
||||
requiredToolsUsed:
|
||||
- create_schedule
|
||||
toolCallArgs:
|
||||
- tool: create_schedule
|
||||
field: path
|
||||
stringStartsWithAnyOf:
|
||||
- f/
|
||||
- u/
|
||||
stringMustNotStartWithAnyOf:
|
||||
- schedules/
|
||||
skipJudge: true
|
||||
judgeChecklist:
|
||||
- "the flow includes a final top-level step named `return_schedule_status`"
|
||||
- "`return_schedule_status` returns `scheduled: true` and the order summary"
|
||||
|
||||
- id: flow-test16-create-current-flow-http-trigger
|
||||
prompt: |-
|
||||
Update this flow by adding a final step named `webhook_response`.
|
||||
It should return an object with `ok: true` and the order summary from `results.summarize_orders`.
|
||||
Also create a public POST HTTP endpoint named `order_processing_webhook` for the current flow.
|
||||
Use route path `ai-evals/order-processing` and no authentication.
|
||||
Do not ask me for the flow path.
|
||||
initial: ai_evals/fixtures/frontend/flow/initial/scheduled_order_flow.json
|
||||
validate:
|
||||
topLevelStepIds:
|
||||
- webhook_response
|
||||
toolExpect:
|
||||
requiredToolsUsed:
|
||||
- create_trigger
|
||||
toolCallArgs:
|
||||
- tool: create_trigger
|
||||
field: path
|
||||
stringStartsWithAnyOf:
|
||||
- f/
|
||||
- u/
|
||||
stringMustNotStartWithAnyOf:
|
||||
- schedules/
|
||||
skipJudge: true
|
||||
judgeChecklist:
|
||||
- "the flow includes a final top-level step named `webhook_response`"
|
||||
- "`webhook_response` returns `ok: true` and the order summary"
|
||||
|
||||
@@ -9,3 +9,51 @@
|
||||
- uses the existing `name` input
|
||||
- returns a plain greeting string
|
||||
- does not wrap the result in an object or array
|
||||
|
||||
- id: script-test2-create-current-script-schedule
|
||||
prompt: |-
|
||||
Update the current Bun script so it takes the existing `name` input and returns a plain greeting string like `Hello, Alice!`.
|
||||
Also create an enabled daily schedule named `greet_user_daily` for the current script.
|
||||
It should run every day at 09:00 UTC and pass `{ "name": "Alice" }` as args.
|
||||
Do not ask me for the script path.
|
||||
initial: ai_evals/fixtures/frontend/script/initial/test1_empty_bun.json
|
||||
expected: ai_evals/fixtures/frontend/script/expected/test1_greet_user.json
|
||||
toolExpect:
|
||||
requiredToolsUsed:
|
||||
- create_schedule
|
||||
toolCallArgs:
|
||||
- tool: create_schedule
|
||||
field: path
|
||||
stringStartsWithAnyOf:
|
||||
- f/
|
||||
- u/
|
||||
stringMustNotStartWithAnyOf:
|
||||
- schedules/
|
||||
skipJudge: true
|
||||
judgeChecklist:
|
||||
- uses the existing `name` input
|
||||
- returns a plain greeting string
|
||||
|
||||
- id: script-test3-create-current-script-http-trigger
|
||||
prompt: |-
|
||||
Update the current Bun script so it takes the existing `name` input and returns a plain greeting string like `Hello, Alice!`.
|
||||
Also create a public POST HTTP endpoint named `greet_user_webhook` for the current script.
|
||||
Use route path `ai-evals/greet-user` and no authentication.
|
||||
Do not ask me for the script path.
|
||||
initial: ai_evals/fixtures/frontend/script/initial/test1_empty_bun.json
|
||||
expected: ai_evals/fixtures/frontend/script/expected/test1_greet_user.json
|
||||
toolExpect:
|
||||
requiredToolsUsed:
|
||||
- create_trigger
|
||||
toolCallArgs:
|
||||
- tool: create_trigger
|
||||
field: path
|
||||
stringStartsWithAnyOf:
|
||||
- f/
|
||||
- u/
|
||||
stringMustNotStartWithAnyOf:
|
||||
- schedules/
|
||||
skipJudge: true
|
||||
judgeChecklist:
|
||||
- uses the existing `name` input
|
||||
- returns a plain greeting string
|
||||
|
||||
@@ -182,4 +182,24 @@ describe("loadCases", () => {
|
||||
forbiddenExecutedCommands: ["^wmill generate-metadata", "^wmill sync push"],
|
||||
});
|
||||
});
|
||||
|
||||
it("loads tool expectations for workspace mutation cases", async () => {
|
||||
const scriptCases = await loadCases("script");
|
||||
const caseEntry = scriptCases.find(
|
||||
(entry) => entry.id === "script-test2-create-current-script-schedule"
|
||||
);
|
||||
|
||||
expect(caseEntry?.toolExpect).toEqual({
|
||||
requiredToolsUsed: ["create_schedule"],
|
||||
toolCallArgs: [
|
||||
{
|
||||
tool: "create_schedule",
|
||||
field: "path",
|
||||
stringStartsWithAnyOf: ["f/", "u/"],
|
||||
stringMustNotStartWithAnyOf: ["schedules/"],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(caseEntry?.skipJudge).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,8 +19,10 @@ interface RawEvalCase {
|
||||
initial?: string;
|
||||
expected?: string;
|
||||
validate?: EvalValidationSpec;
|
||||
toolExpect?: EvalCase["toolExpect"];
|
||||
cliExpect?: CliValidationSpec;
|
||||
judgeChecklist?: string[];
|
||||
skipJudge?: boolean;
|
||||
runtime?: EvalCaseRuntimeSpec;
|
||||
}
|
||||
export function getRepoRoot(): string {
|
||||
@@ -46,8 +48,10 @@ export async function loadCases(mode: EvalMode): Promise<EvalCase[]> {
|
||||
initialPath: resolveFixturePath(entry.initial),
|
||||
expectedPath: resolveFixturePath(entry.expected),
|
||||
validate: entry.validate,
|
||||
toolExpect: entry.toolExpect,
|
||||
cliExpect: entry.cliExpect,
|
||||
judgeChecklist: entry.judgeChecklist,
|
||||
skipJudge: entry.skipJudge,
|
||||
runtime: entry.runtime,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
FrontendBenchmarkProgressEvent,
|
||||
ModeRunner,
|
||||
} from "./types";
|
||||
import { validateToolExpectations } from "./validators";
|
||||
|
||||
export async function runSuite<TInitial, TExpected, TActual>(input: {
|
||||
modeRunner: ModeRunner<TInitial, TExpected, TActual>;
|
||||
@@ -169,6 +170,10 @@ async function runCaseAttempts<TInitial, TExpected, TActual>(input: {
|
||||
actual: run.actual,
|
||||
run,
|
||||
}),
|
||||
...validateToolExpectations({
|
||||
run,
|
||||
toolExpect: input.evalCase.toolExpect,
|
||||
}),
|
||||
];
|
||||
const artifactFiles = input.modeRunner.buildArtifacts?.(run.actual) ?? [];
|
||||
|
||||
@@ -213,7 +218,7 @@ async function runCaseAttempts<TInitial, TExpected, TActual>(input: {
|
||||
let judgeScore: number | null = null;
|
||||
let judgeSummary: string | null = null;
|
||||
|
||||
if (run.success) {
|
||||
if (run.success && !input.evalCase.skipJudge) {
|
||||
const judge = await judgeOutput({
|
||||
mode: input.modeRunner.mode,
|
||||
prompt: input.evalCase.prompt,
|
||||
@@ -243,6 +248,7 @@ async function runCaseAttempts<TInitial, TExpected, TActual>(input: {
|
||||
assistantMessageCount: run.assistantMessageCount,
|
||||
toolCallCount: run.toolCallCount,
|
||||
toolsUsed: uniqueStrings(run.toolsUsed),
|
||||
toolCallDetails: run.toolCallDetails,
|
||||
skillsInvoked: uniqueStrings(run.skillsInvoked),
|
||||
checks,
|
||||
judgeScore,
|
||||
|
||||
@@ -123,6 +123,23 @@ export interface CliValidationSpec {
|
||||
workspaceUnchanged?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolCallDetail {
|
||||
name: string;
|
||||
arguments: unknown;
|
||||
}
|
||||
|
||||
export interface ToolCallArgumentRule {
|
||||
tool: string;
|
||||
field: string;
|
||||
stringStartsWithAnyOf?: string[];
|
||||
stringMustNotStartWithAnyOf?: string[];
|
||||
}
|
||||
|
||||
export interface ToolValidationSpec {
|
||||
requiredToolsUsed?: string[];
|
||||
toolCallArgs?: ToolCallArgumentRule[];
|
||||
}
|
||||
|
||||
export type EvalValidationSpec = FlowValidationSpec | AppValidationSpec;
|
||||
|
||||
export interface EvalCase {
|
||||
@@ -131,8 +148,10 @@ export interface EvalCase {
|
||||
initialPath?: string;
|
||||
expectedPath?: string;
|
||||
validate?: EvalValidationSpec;
|
||||
toolExpect?: ToolValidationSpec;
|
||||
cliExpect?: CliValidationSpec;
|
||||
judgeChecklist?: string[];
|
||||
skipJudge?: boolean;
|
||||
runtime?: EvalCaseRuntimeSpec;
|
||||
}
|
||||
|
||||
@@ -195,6 +214,7 @@ export interface ModeRunOutput<TActual> {
|
||||
assistantMessageCount: number;
|
||||
toolCallCount: number;
|
||||
toolsUsed: string[];
|
||||
toolCallDetails?: ToolCallDetail[];
|
||||
skillsInvoked: string[];
|
||||
tokenUsage?: BenchmarkTokenUsage | null;
|
||||
}
|
||||
@@ -251,6 +271,7 @@ export interface BenchmarkAttemptResult {
|
||||
assistantMessageCount: number;
|
||||
toolCallCount: number;
|
||||
toolsUsed: string[];
|
||||
toolCallDetails?: ToolCallDetail[];
|
||||
skillsInvoked: string[];
|
||||
checks: BenchmarkCheck[];
|
||||
judgeScore: number | null;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { validateAppState, validateCliWorkspace, validateScriptState } from "./validators";
|
||||
import {
|
||||
validateAppState,
|
||||
validateCliWorkspace,
|
||||
validateScriptState,
|
||||
validateToolExpectations,
|
||||
} from "./validators";
|
||||
|
||||
describe("validateScriptState", () => {
|
||||
it("accepts semantically equivalent script implementations", () => {
|
||||
@@ -35,6 +40,85 @@ describe("validateScriptState", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateToolExpectations", () => {
|
||||
it("accepts Windmill-prefixed schedule paths", () => {
|
||||
const checks = validateToolExpectations({
|
||||
run: {
|
||||
success: true,
|
||||
actual: {},
|
||||
assistantMessageCount: 1,
|
||||
toolCallCount: 1,
|
||||
toolsUsed: ["create_schedule"],
|
||||
toolCallDetails: [
|
||||
{
|
||||
name: "create_schedule",
|
||||
arguments: {
|
||||
path: "f/evals/greet_user_daily",
|
||||
},
|
||||
},
|
||||
],
|
||||
skillsInvoked: [],
|
||||
},
|
||||
toolExpect: {
|
||||
requiredToolsUsed: ["create_schedule"],
|
||||
toolCallArgs: [
|
||||
{
|
||||
tool: "create_schedule",
|
||||
field: "path",
|
||||
stringStartsWithAnyOf: ["f/", "u/"],
|
||||
stringMustNotStartWithAnyOf: ["schedules/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(checks.every((check) => check.passed)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects schedule-prefixed tool paths", () => {
|
||||
const checks = validateToolExpectations({
|
||||
run: {
|
||||
success: true,
|
||||
actual: {},
|
||||
assistantMessageCount: 1,
|
||||
toolCallCount: 1,
|
||||
toolsUsed: ["create_schedule"],
|
||||
toolCallDetails: [
|
||||
{
|
||||
name: "create_schedule",
|
||||
arguments: {
|
||||
path: "schedules/greet_user_daily",
|
||||
},
|
||||
},
|
||||
],
|
||||
skillsInvoked: [],
|
||||
},
|
||||
toolExpect: {
|
||||
requiredToolsUsed: ["create_schedule"],
|
||||
toolCallArgs: [
|
||||
{
|
||||
tool: "create_schedule",
|
||||
field: "path",
|
||||
stringStartsWithAnyOf: ["f/", "u/"],
|
||||
stringMustNotStartWithAnyOf: ["schedules/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(checks).toContainEqual({
|
||||
name: "create_schedule.path uses an accepted prefix",
|
||||
passed: false,
|
||||
details: 'accepted prefixes: f/, u/; values: "schedules/greet_user_daily"',
|
||||
});
|
||||
expect(checks).toContainEqual({
|
||||
name: "create_schedule.path avoids rejected prefixes",
|
||||
passed: false,
|
||||
details: 'rejected prefixes: schedules/; values: "schedules/greet_user_daily"',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateAppState", () => {
|
||||
it("accepts app persistence requirements when a datatable table is registered", () => {
|
||||
const checks = validateAppState({
|
||||
|
||||
@@ -6,6 +6,8 @@ import type {
|
||||
CliTrace,
|
||||
CliValidationSpec,
|
||||
FlowValidationSpec,
|
||||
ModeRunOutput,
|
||||
ToolValidationSpec,
|
||||
} from "./types";
|
||||
|
||||
export interface ScriptState {
|
||||
@@ -16,6 +18,7 @@ export interface ScriptState {
|
||||
}
|
||||
|
||||
export interface FlowState {
|
||||
path?: string;
|
||||
summary?: string;
|
||||
value?: {
|
||||
preprocessor_module?: Record<string, unknown>;
|
||||
@@ -129,6 +132,76 @@ export function validateFlowState(input: {
|
||||
return checks;
|
||||
}
|
||||
|
||||
export function validateToolExpectations(input: {
|
||||
run: ModeRunOutput<unknown>;
|
||||
toolExpect?: ToolValidationSpec;
|
||||
}): BenchmarkCheck[] {
|
||||
const expect = input.toolExpect;
|
||||
if (!expect) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const checks: BenchmarkCheck[] = [];
|
||||
const toolCallDetails = input.run.toolCallDetails ?? [];
|
||||
|
||||
for (const toolName of expect.requiredToolsUsed ?? []) {
|
||||
checks.push(
|
||||
check(
|
||||
`uses ${toolName}`,
|
||||
input.run.toolsUsed.includes(toolName),
|
||||
`tools used: ${input.run.toolsUsed.join(", ") || "none"}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (const rule of expect.toolCallArgs ?? []) {
|
||||
const calls = toolCallDetails.filter((call) => call.name === rule.tool);
|
||||
checks.push(
|
||||
check(
|
||||
`${rule.tool} was called for argument validation`,
|
||||
calls.length > 0,
|
||||
`tools called: ${toolCallDetails.map((call) => call.name).join(", ") || "none"}`
|
||||
)
|
||||
);
|
||||
if (calls.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const values = calls.map((call) => getToolArgumentValue(call.arguments, rule.field));
|
||||
if (rule.stringStartsWithAnyOf && rule.stringStartsWithAnyOf.length > 0) {
|
||||
const invalidValues = values.filter(
|
||||
(value) =>
|
||||
typeof value !== "string" ||
|
||||
!rule.stringStartsWithAnyOf!.some((prefix) => value.startsWith(prefix))
|
||||
);
|
||||
checks.push(
|
||||
check(
|
||||
`${rule.tool}.${rule.field} uses an accepted prefix`,
|
||||
invalidValues.length === 0,
|
||||
`accepted prefixes: ${rule.stringStartsWithAnyOf.join(", ")}; values: ${summarizeToolValues(values)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (rule.stringMustNotStartWithAnyOf && rule.stringMustNotStartWithAnyOf.length > 0) {
|
||||
const invalidValues = values.filter(
|
||||
(value) =>
|
||||
typeof value === "string" &&
|
||||
rule.stringMustNotStartWithAnyOf!.some((prefix) => value.startsWith(prefix))
|
||||
);
|
||||
checks.push(
|
||||
check(
|
||||
`${rule.tool}.${rule.field} avoids rejected prefixes`,
|
||||
invalidValues.length === 0,
|
||||
`rejected prefixes: ${rule.stringMustNotStartWithAnyOf.join(", ")}; values: ${summarizeToolValues(values)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return checks;
|
||||
}
|
||||
|
||||
export function validateAppState(input: {
|
||||
actual: AppFilesState;
|
||||
initial?: AppFilesState;
|
||||
@@ -329,6 +402,17 @@ function check(name: string, passed: boolean, details?: string): BenchmarkCheck
|
||||
return !passed && details ? { name, passed, details } : { name, passed };
|
||||
}
|
||||
|
||||
function getToolArgumentValue(args: unknown, dottedPath: string): unknown {
|
||||
if (!isObjectRecord(args)) {
|
||||
return undefined;
|
||||
}
|
||||
return getValueAtPath(args, dottedPath);
|
||||
}
|
||||
|
||||
function summarizeToolValues(values: unknown[]): string {
|
||||
return values.map((value) => JSON.stringify(value)).join(", ") || "none";
|
||||
}
|
||||
|
||||
function normalizeText(value: string): string {
|
||||
return value.replace(/\r\n/g, "\n").trim();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"path": "f/evals/order_processing_flow",
|
||||
"summary": "Order processing flow",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "get_orders",
|
||||
"summary": "Fetch orders",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "bun",
|
||||
"content": "export async function main() {\n return [\n { id: \"ORD-001\", total: 150 },\n { id: \"ORD-002\", total: 280 }\n ];\n}",
|
||||
"input_transforms": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "summarize_orders",
|
||||
"summary": "Summarize orders",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "bun",
|
||||
"content": "export async function main(orders: Array<{ id: string; total: number }>) {\n return {\n count: orders.length,\n total: orders.reduce((sum, order) => sum + order.total, 0)\n };\n}",
|
||||
"input_transforms": {
|
||||
"orders": {
|
||||
"type": "javascript",
|
||||
"expr": "results.get_orders"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export function createFlowModeRunner(
|
||||
assistantMessageCount: result.assistantMessageCount,
|
||||
toolCallCount: result.toolCallCount,
|
||||
toolsUsed: result.toolsUsed,
|
||||
toolCallDetails: result.toolCallDetails,
|
||||
skillsInvoked: [],
|
||||
tokenUsage: result.tokenUsage,
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ export function normalizeFlowStateFixture(value: unknown): FlowState {
|
||||
|
||||
export function normalizeFlowFixture(value: FlowState): FlowFixture {
|
||||
return {
|
||||
path: value.path,
|
||||
schema: value.schema,
|
||||
value: value.value
|
||||
? {
|
||||
|
||||
@@ -53,6 +53,7 @@ export function createScriptModeRunner(
|
||||
assistantMessageCount: result.assistantMessageCount,
|
||||
toolCallCount: result.toolCallCount,
|
||||
toolsUsed: result.toolsUsed,
|
||||
toolCallDetails: result.toolCallDetails,
|
||||
skillsInvoked: [],
|
||||
tokenUsage: result.tokenUsage,
|
||||
};
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO sqs_trigger (\n path, queue_url, aws_resource_path, message_attributes, script_path,\n is_flow, workspace_id, edited_by, edited_at, extra_perms, error,\n server_id, last_server_ping, aws_auth_resource_type, error_handler_path,\n error_handler_args, retry, mode, permissioned_as, labels\n )\n SELECT\n path, queue_url, aws_resource_path, message_attributes, script_path,\n is_flow, $1, edited_by, edited_at, extra_perms, NULL,\n NULL, NULL, aws_auth_resource_type, error_handler_path,\n error_handler_args, retry, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM sqs_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0b347b021123e66ffb6b7eb690f7619daf34795410505609a1ff3bf0be953550"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO schedule (\n workspace_id, path, edited_by, edited_at, schedule, enabled, script_path,\n args, extra_perms, is_flow, email, error, timezone, on_failure,\n on_recovery, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery_times, on_recovery_extra_args, ws_error_handler_muted, retry,\n summary, no_flow_overlap, tag, paused_until, on_success, on_success_extra_args,\n cron_version, description, dynamic_skip, permissioned_as, labels\n )\n SELECT\n $1, path, edited_by, edited_at, schedule, FALSE, script_path,\n args, extra_perms, is_flow, email, error, timezone, on_failure,\n on_recovery, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery_times, on_recovery_extra_args, ws_error_handler_muted, retry,\n summary, no_flow_overlap, tag, paused_until, on_success, on_success_extra_args,\n cron_version, description, dynamic_skip, permissioned_as, labels\n FROM schedule WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "149b645af2324fc3140bf2662e75e579dcc8b928be3a2cc051e62aa2ddc09b1e"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO azure_trigger (\n azure_resource_path, azure_mode, scope_resource_id, topic_name,\n subscription_name, event_type_filters, push_auth_config, path, script_path,\n is_flow, workspace_id, edited_by, email, edited_at, extra_perms, server_id,\n last_server_ping, error, mode, permissioned_as, error_handler_path,\n error_handler_args, retry, labels\n )\n SELECT\n azure_resource_path, azure_mode, scope_resource_id, topic_name,\n subscription_name, event_type_filters, push_auth_config, path, script_path,\n is_flow, $1, edited_by, email, edited_at, extra_perms, NULL,\n NULL, NULL, 'disabled'::TRIGGER_MODE, permissioned_as, error_handler_path,\n error_handler_args, retry, labels\n FROM azure_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "16e8d3f75ce4b5b18cefc25ebb670b506677681394606426964491488d64c62c"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO email_trigger (\n path, local_part, workspaced_local_part, script_path, is_flow,\n workspace_id, edited_by, edited_at, extra_perms, error_handler_path,\n error_handler_args, retry, mode, permissioned_as, labels\n )\n SELECT\n path, local_part, workspaced_local_part, script_path, is_flow,\n $1, edited_by, edited_at, extra_perms, error_handler_path,\n error_handler_args, retry, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM email_trigger\n WHERE workspace_id = $2\n AND (workspaced_local_part IS TRUE OR $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1a66a5a9c2b7b75e783c59b4c2ed3f7f84adc67318fd0bc5cdee78b2c406ae81"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO workspace_shared_ui (workspace_id, files, version, edited_at, edited_by)\n VALUES ($1, $2, 1, now(), $3)\n ON CONFLICT (workspace_id) DO UPDATE\n SET files = EXCLUDED.files,\n version = workspace_shared_ui.version + 1,\n edited_at = now(),\n edited_by = EXCLUDED.edited_by",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1f1e4046c55ec33e0ea51d3424c21f538ccc72b86f4ead52292463c29f8dfea6"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM schedule WHERE workspace_id = $1 AND path = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "22699056871c6306f689d4bc6f9a67070baa4ff83de3b4a04c4d8d6054a59319"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -38,5 +38,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ba9ab074f466bc2c581f018e2592f5a453e8a766c35dbf919d29c96966d63c75"
|
||||
"hash": "25784f87ccf0bc13b93739d34d416908b75d0d17392c2565e70b4738039b56a1"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
"query": "SELECT workspace_id, path, value, description, resource_type, extra_perms, created_by, edited_at, labels FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,13 +35,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
@@ -66,5 +66,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "45e4d13f5806122faecdb1d9ab18159555b652869a036b006f4a151e999b17b7"
|
||||
"hash": "27b243e7ff9838a8fc0a4a8e51ac5d33d8617fc75c0d9d08e428db488e0be409"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes FROM token WHERE email = $1\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes, workspace_id FROM token WHERE email = $1\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "scopes",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -47,8 +52,9 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ebc2eed287f93e184ed683feb20432caa6e6682620c90f38b29dd32b9a8fe633"
|
||||
"hash": "2b5fc0500beb2f4c7cf5997f9aea48f77e2abe4523180c507a9a90570127be6d"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
"query": "SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -37,5 +37,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d9c8f6ec7bd10e533876526255c15e376ccb4f898b9c0ab8840b2930bda24fdc"
|
||||
"hash": "2c9a56fd46767c15dabc7813cd2b167a2f73a06d661d31c375697a68f7066aa9"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO gcp_trigger (\n gcp_resource_path, topic_id, subscription_id, delivery_type,\n delivery_config, path, script_path, is_flow, workspace_id, edited_by,\n edited_at, extra_perms, server_id, last_server_ping, error,\n subscription_mode, error_handler_path, error_handler_args, retry,\n auto_acknowledge_msg, ack_deadline, mode, permissioned_as, labels\n )\n SELECT\n gcp_resource_path, topic_id, subscription_id, delivery_type,\n delivery_config, path, script_path, is_flow, $1, edited_by,\n edited_at, extra_perms, NULL, NULL, NULL,\n subscription_mode, error_handler_path, error_handler_args, retry,\n auto_acknowledge_msg, ack_deadline, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM gcp_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2e67fa50d5d66cbca4ef74111f0ff9b51a6168adad3775f8d6cf40d02cb29d14"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes FROM token WHERE email = $1 AND (label != 'ephemeral-script' OR label IS NULL)\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes, workspace_id FROM token WHERE email = $1 AND (label != 'ephemeral-script' OR label IS NULL)\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "scopes",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -47,8 +52,9 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1bf4a93cb85c6eed313a2f393da9408dd2aa4e47ef7a38a0d3ccca944a09f5bb"
|
||||
"hash": "40f0bc9a2555a7c90b3985a190bf3fce18c09693b645f2ac520de6936366f3c8"
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT files, version, edited_at, edited_by FROM workspace_shared_ui WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "files",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "410718c2290d3d4688382fe28c9aca76edec85ce6f0b0ede908d0fb1ac6925c4"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM resource_type WHERE workspace_id = $1",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset FROM resource_type WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -60,5 +60,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234"
|
||||
"hash": "45d5e9ead8193a04fd00c44a488590fdd2f7c4de45117a18360651655d153545"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO nats_trigger (\n path, nats_resource_path, subjects, stream_name, consumer_name,\n use_jetstream, script_path, is_flow, workspace_id, edited_by, edited_at,\n extra_perms, server_id, last_server_ping, error, error_handler_path,\n error_handler_args, retry, mode, permissioned_as, labels\n )\n SELECT\n path, nats_resource_path, subjects, stream_name, consumer_name,\n use_jetstream, script_path, is_flow, $1, edited_by, edited_at,\n extra_perms, NULL, NULL, NULL, error_handler_path,\n error_handler_args, retry, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM nats_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4d64c962e219f7cda8b93c3875acc78a4e17aae2c702ec1af6c3a4ae3f1e2716"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM email_trigger\n WHERE\n ((workspaced_local_part IS TRUE AND workspace_id || '-' || local_part = $1)\n OR (workspaced_local_part IS FALSE AND local_part = $1))\n AND ($2::TEXT IS NULL OR path != $2)\n )\n ",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM email_trigger\n WHERE\n ((workspaced_local_part IS TRUE AND workspace_id || '-' || local_part = $1)\n OR (workspaced_local_part IS FALSE AND local_part = $1))\n AND ($2::TEXT IS NULL OR NOT (workspace_id = $3 AND path = $2))\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -11,6 +11,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
@@ -19,5 +20,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bb94e3105cb1bc5d90af2bc914b579a6e821c432eac38ad877d9ff362d8ab916"
|
||||
"hash": "4d73cefcc1b72238b7731ab52376b849a6f712b4109a48d9f77da8dd0060ab1a"
|
||||
}
|
||||
+3
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM usr\n WHERE workspace_id = $1",
|
||||
"query": "SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via FROM usr\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -47,11 +47,6 @@
|
||||
"ordinal": 8,
|
||||
"name": "added_via",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "is_service_account",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -68,9 +63,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2"
|
||||
"hash": "500b68d23314cb0f9edd570e576d5b9822bebe88f90ae007d943f2c761858190"
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id, edited_by, edited_at,\n extra_perms, server_id, last_server_ping, error, filters, initial_messages,\n url_runnable_args, can_return_message, error_handler_path, error_handler_args,\n retry, can_return_error_result, mode, permissioned_as, filter_logic, labels,\n heartbeat\n )\n SELECT\n path, url, script_path, is_flow, $1, edited_by, edited_at,\n extra_perms, NULL, NULL, NULL, filters, initial_messages,\n url_runnable_args, can_return_message, error_handler_path, error_handler_args,\n retry, can_return_error_result, 'disabled'::TRIGGER_MODE, permissioned_as, filter_logic, labels,\n heartbeat\n FROM websocket_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5fd769bde29e88eb43f5dc0ecd7f4a1fe20d216d818d69eea4ddbaace5233137"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -61,5 +61,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "03d63d2e64b012f624d2731b5bcb8849c74a9474777be61edf0ed43ddda07ef3"
|
||||
"hash": "623b061ccaa6bb883e95771fde8c911a165c9c430b7db389370361ca74d737f4"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM config WHERE name LIKE 'worker__%'",
|
||||
"query": "SELECT name, config FROM config WHERE name LIKE 'worker__%'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,5 +22,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ce9e56ff451bae10af2c396352f5f93f78658e57b79dc5295553cacc328eb2b7"
|
||||
"hash": "6f95d6927a9be75f6c371c799ebe3755142647d0257b76e9befd0c4f31c332b1"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT *\n FROM usr\n WHERE workspace_id = $1\n ",
|
||||
"query": "\n SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account\n FROM usr\n WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -72,5 +72,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c"
|
||||
"hash": "704d873d4585e00aa0a88cc9949d3a6cc806b33a844b8e0e600cb8bfae428e84"
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n workspace_id,\n slack_team_id,\n slack_name,\n teams_team_id,\n teams_team_name,\n teams_team_guid,\n mute_critical_alerts,\n deploy_ui,\n large_file_storage,\n datatable\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "slack_team_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "slack_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "teams_team_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "teams_team_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "teams_team_guid",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "mute_critical_alerts",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "datatable",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "71eeda25c59d724d6e0c4b2b52078567d6e477a2c62656de1deaef602221edcf"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT version FROM workspace_shared_ui WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "77f9650e543821605d6cd5eb11af3a6d46e60d5288357f22faf5fcd5298923c3"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"query": "SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -73,5 +73,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b"
|
||||
"hash": "8f308f841b2d60a6be2bc4ae6aeae506781295a3bcf036dbbc8600f229e61408"
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE token SET scopes = $1\n WHERE email = $2 AND token_prefix = $3\n RETURNING token_prefix",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token_prefix",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a7a20412e303568b271f949642de55e9880ef05786fe59f05de5e025ef315726"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM http_trigger\n WHERE\n ((workspaced_route IS TRUE AND workspace_id || '/' || route_path_key = $1)\n OR (workspaced_route IS FALSE AND route_path_key = $1))\n AND http_method = $2\n AND ($3::TEXT IS NULL OR path != $3)\n )\n ",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM http_trigger\n WHERE\n ((workspaced_route IS TRUE AND workspace_id || '/' || route_path_key = $1)\n OR (workspaced_route IS FALSE AND route_path_key = $1))\n AND http_method = $2\n AND ($3::TEXT IS NULL OR NOT (workspace_id = $4 AND path = $3))\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -26,6 +26,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -33,5 +34,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fe464b8b3ade86743d82c5e3fb14f457e07f07e44c7b693d5d755899d4210dee"
|
||||
"hash": "aa0a2f90d15a642ad3caaa3876d9cb4a5391ff8663da61b48bfeb73bfa005bbe"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow, workspace_id,\n edited_by, edited_at, extra_perms, authentication_method, http_method,\n static_asset_config, is_static_website, workspaced_route, wrap_body,\n raw_string, authentication_resource_path, summary, description,\n error_handler_path, error_handler_args, retry, request_type, mode,\n permissioned_as, labels\n )\n SELECT\n path, route_path, route_path_key, script_path, is_flow, $1,\n edited_by, edited_at, extra_perms, authentication_method, http_method,\n static_asset_config, is_static_website, workspaced_route, wrap_body,\n raw_string, authentication_resource_path, summary, description,\n error_handler_path, error_handler_args, retry, request_type, 'disabled'::TRIGGER_MODE,\n permissioned_as, labels\n FROM http_trigger\n WHERE workspace_id = $2\n AND (workspaced_route IS TRUE OR $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "aee9bf16e37a5361f96d6b35dddffa6cc37346cdb6b38ecbf6e1530eef3a57bd"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO postgres_trigger (\n path, script_path, is_flow, workspace_id, edited_by, edited_at,\n extra_perms, postgres_resource_path, error, server_id, last_server_ping,\n replication_slot_name, publication_name, error_handler_path,\n error_handler_args, retry, mode, permissioned_as, labels\n )\n SELECT\n path, script_path, is_flow, $1, edited_by, edited_at,\n extra_perms, postgres_resource_path, NULL, NULL, NULL,\n replication_slot_name, publication_name, error_handler_path,\n error_handler_args, retry, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM postgres_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b20e4486e2038ac8138f5d7435db8cbd78ac3d68674335db79df7fd8a4ba91fd"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO mqtt_trigger (\n mqtt_resource_path, subscribe_topics, client_version, v5_config, v3_config,\n client_id, path, script_path, is_flow, workspace_id, edited_by, edited_at,\n extra_perms, server_id, last_server_ping, error, error_handler_path,\n error_handler_args, retry, mode, permissioned_as, labels\n )\n SELECT\n mqtt_resource_path, subscribe_topics, client_version, v5_config, v3_config,\n client_id, path, script_path, is_flow, $1, edited_by, edited_at,\n extra_perms, NULL, NULL, NULL, error_handler_path,\n error_handler_args, retry, 'disabled'::TRIGGER_MODE, permissioned_as, labels\n FROM mqtt_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c8c8c457ee80125938af97993c4ca759b66507071163f8d98a778f417b930413"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM config WHERE name = $1",
|
||||
"query": "SELECT name, config FROM config WHERE name = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -24,5 +24,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d233e07d19e8e339e1378c1bfc5d78d592c00ffb6f42c3d072f56305b40e50f9"
|
||||
"hash": "c9064664829d304013920e3f710c4dfab99b263e5271045c542c34216e2b47cb"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path, is_flow,\n workspace_id, edited_by, edited_at, extra_perms, server_id,\n last_server_ping, error, error_handler_path, error_handler_args, retry,\n mode, filters, auto_offset_reset, reset_offset, auto_commit,\n permissioned_as, filter_logic, labels\n )\n SELECT\n path, kafka_resource_path, topics, group_id, script_path, is_flow,\n $1, edited_by, edited_at, extra_perms, NULL,\n NULL, NULL, error_handler_path, error_handler_args, retry,\n 'disabled'::TRIGGER_MODE, filters, auto_offset_reset, reset_offset, auto_commit,\n permissioned_as, filter_logic, labels\n FROM kafka_trigger WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c9c0b92c1fea9b4bdafba32da60e5579a4c2940bed63097ffec7f757d9882667"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_status FROM v2_job_completed WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow_status",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ca60e1875e82a492b34bbfd771ad7ba526e4aa7914f1042c5ea461816f69b658"
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n j.runnable_id AS \"runnable_id: ScriptHash\",\n j.kind AS \"job_kind!: JobKind\",\n COALESCE(c.flow_status, c.workflow_as_code_status) AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n j.raw_flow AS \"raw_flow: sqlx::types::Json<Box<RawValue>>\"\n FROM v2_job_completed c JOIN v2_job j USING (id)\n WHERE j.id = $1 AND j.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "runnable_id: ScriptHash",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "job_kind!: JobKind",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlestepflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript",
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "raw_flow: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "cc181a37ae10254f0b7727655207c40ba729ac4c8a21667ede7b4d13d328482f"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER BY name",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER BY name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -60,5 +60,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b8d392ccfcccafe0c19511b3567bc11779b1052b0948c410468a8aeba1d26d33"
|
||||
"hash": "d0a95698b9a2c5e2543e94276d854d7e509c7db2c2ac7d395b7b53ad5dbc25e6"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type ORDER BY name",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type ORDER BY name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -58,5 +58,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "eb1f7f01461f5a7540c273b37e5d578c31cf151ab3ef813f7aada76533761e12"
|
||||
"hash": "e253b9e7e6450652589d6ee7ffa86d600e449cd399ac781af8b40c1c444972c3"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT parent_workspace_id IS NOT NULL FROM workspace WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f57ff370f6775602d2c200d78650d65ffa5bfc5f10e8bd2a3162894c93283259"
|
||||
}
|
||||
Generated
+83
-83
@@ -7681,14 +7681,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json-patch"
|
||||
version = "4.1.0"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90"
|
||||
checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72"
|
||||
dependencies = [
|
||||
"jsonptr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13260,9 +13260,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sse-stream"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c5e6deb40826033bd7b11c7ef25ef71193fabd71f680f40dd16538a2704d2f4"
|
||||
checksum = "f3962b63f038885f15bce2c6e02c0e7925c072f1ac86bb60fd44c5c6b762fb72"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
@@ -16020,7 +16020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16101,7 +16101,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-ai"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
@@ -16125,7 +16125,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16138,7 +16138,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -16281,7 +16281,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16304,7 +16304,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16317,7 +16317,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16343,7 +16343,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16353,7 +16353,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16370,7 +16370,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"base64 0.22.1",
|
||||
@@ -16392,7 +16392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16415,7 +16415,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16431,7 +16431,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16452,7 +16452,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16473,7 +16473,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16487,7 +16487,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16519,7 +16519,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16544,7 +16544,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"flate2",
|
||||
@@ -16562,7 +16562,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16584,7 +16584,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16604,7 +16604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16634,7 +16634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16662,7 +16662,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16674,7 +16674,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.8.4",
|
||||
@@ -16699,7 +16699,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16713,7 +16713,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16746,7 +16746,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16760,7 +16760,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16779,7 +16779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aho-corasick",
|
||||
@@ -16880,7 +16880,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16899,7 +16899,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16914,7 +16914,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16938,7 +16938,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16955,7 +16955,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16971,7 +16971,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16992,7 +16992,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17023,7 +17023,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -17048,7 +17048,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -17082,7 +17082,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -17100,7 +17100,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -17109,7 +17109,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17121,7 +17121,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17133,7 +17133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -17145,7 +17145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17157,7 +17157,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17169,7 +17169,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -17180,7 +17180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -17191,7 +17191,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -17203,7 +17203,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -17214,7 +17214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17236,7 +17236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17248,7 +17248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17262,7 +17262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -17279,7 +17279,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17292,7 +17292,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -17304,7 +17304,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17322,7 +17322,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -17338,7 +17338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -17354,7 +17354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -17365,7 +17365,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17402,7 +17402,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17440,7 +17440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -17451,7 +17451,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17481,7 +17481,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17505,7 +17505,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17538,7 +17538,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-azure"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17571,7 +17571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17591,7 +17591,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17625,7 +17625,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17661,7 +17661,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17684,7 +17684,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17708,7 +17708,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17732,7 +17732,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17767,7 +17767,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17795,7 +17795,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17818,7 +17818,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17837,7 +17837,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -17949,7 +17949,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker-volumes"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -87,7 +87,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
ed842061576c3ac9b9eb89bb87f6db5b67904474
|
||||
57a2a9f4490f214246a5b6fa35cc870f35ba40ba
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TRIGGER IF EXISTS token_scopes_update_trigger ON token;
|
||||
DROP FUNCTION IF EXISTS notify_token_scopes_change();
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Invalidate auth cache (across instances) when token scopes change.
|
||||
-- Reuses the existing notify_token_invalidation channel handled in main.rs.
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_token_scopes_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF OLD.scopes IS DISTINCT FROM NEW.scopes THEN
|
||||
INSERT INTO notify_event (channel, payload)
|
||||
VALUES ('notify_token_invalidation', NEW.token_prefix);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE TRIGGER token_scopes_update_trigger
|
||||
AFTER UPDATE OF scopes ON token
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_token_scopes_change();
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE IF EXISTS workspace_shared_ui;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE workspace_shared_ui (
|
||||
workspace_id VARCHAR(50) PRIMARY KEY REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
files JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
edited_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
edited_by VARCHAR(255) NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
GRANT ALL ON workspace_shared_ui TO windmill_user;
|
||||
GRANT ALL ON workspace_shared_ui TO windmill_admin;
|
||||
@@ -134,6 +134,7 @@ fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: None,
|
||||
has_default: default.is_some(),
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
@@ -731,6 +732,7 @@ fn finalize_parameter(
|
||||
otyp,
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -774,7 +776,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -782,7 +785,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -790,7 +794,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("latest with spaces")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -798,7 +803,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -806,7 +812,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -833,7 +840,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("string".to_string()), // [string]
|
||||
@@ -841,7 +849,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None, // No type annotation
|
||||
@@ -849,7 +858,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("default value, with comma")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("int".to_string()), // [int]
|
||||
@@ -857,7 +867,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None, // Type inferred from default value
|
||||
@@ -865,7 +876,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Float,
|
||||
default: Some(json!(5.0)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None, // Type inferred from default value
|
||||
@@ -873,7 +885,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(5)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None, // No type annotation
|
||||
@@ -881,7 +894,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("PSCustomObject".to_string()), // [PSCustomObject]
|
||||
@@ -889,7 +903,8 @@ non_required="${5:-}"
|
||||
typ: Typ::Object(ObjectType::new(None, None)),
|
||||
default: None,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("string[]".to_string()), // [string[]]
|
||||
@@ -897,7 +912,8 @@ non_required="${5:-}"
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("string".to_string()), // [string] (last type bracket with Mandatory and ValidateSet)
|
||||
@@ -909,7 +925,8 @@ non_required="${5:-}"
|
||||
])), // ValidateSet enum
|
||||
default: None,
|
||||
has_default: false, // Required (Mandatory attribute)
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1462,7 +1479,8 @@ param(
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1470,7 +1488,8 @@ param(
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1478,7 +1497,8 @@ param(
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("latest with spaces")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1486,7 +1506,8 @@ param(
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1494,7 +1515,8 @@ param(
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
|
||||
@@ -77,7 +77,7 @@ pub fn parse_csharp_sig_meta(code: &str) -> anyhow::Result<CsharpMainSigMeta> {
|
||||
}
|
||||
}
|
||||
let (otyp, typ, name) = parse_csharp_typ(p_list_node, code)?;
|
||||
args.push(Arg { name, otyp, typ, default, has_default: false, oidx: None });
|
||||
args.push(Arg { name, otyp, typ, default, has_default: false, oidx: None, otyp_inferred: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -147,7 +148,10 @@ fn parse_go_typ(typ: &Expression) -> (Option<String>, Typ) {
|
||||
Typ::Object(ObjectType::new(None, Some(typs))),
|
||||
)
|
||||
}
|
||||
Expression::TypeInterface(_) => (Some("interface{}".to_string()), Typ::Object(ObjectType::new(None, Some(vec![])))),
|
||||
Expression::TypeInterface(_) => (
|
||||
Some("interface{}".to_string()),
|
||||
Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
),
|
||||
Expression::TypeMap(_) => (
|
||||
Some("map[string]interface{}".to_string()),
|
||||
Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
@@ -191,7 +195,8 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::Int,
|
||||
has_default: false,
|
||||
default: None,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("string".to_string()),
|
||||
@@ -199,7 +204,8 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("bool".to_string()),
|
||||
@@ -207,7 +213,8 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::Bool,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("[]string".to_string()),
|
||||
@@ -215,18 +222,23 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("struct { Name string `json:\"name\"` }".to_string()),
|
||||
name: "o".to_string(),
|
||||
typ: Typ::Object(ObjectType::new(None, Some(vec![ObjectProperty {
|
||||
key: "name".to_string(),
|
||||
typ: Box::new(Typ::Str(None))
|
||||
},]))),
|
||||
typ: Typ::Object(ObjectType::new(
|
||||
None,
|
||||
Some(vec![ObjectProperty {
|
||||
key: "name".to_string(),
|
||||
typ: Box::new(Typ::Str(None))
|
||||
},])
|
||||
)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("interface{}".to_string()),
|
||||
@@ -234,7 +246,8 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("map[string]interface{}".to_string()),
|
||||
@@ -242,12 +255,13 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
..Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ fn parse_graphql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ.unwrap()),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,7 +108,8 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("[String]".to_string()),
|
||||
@@ -115,7 +117,8 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("String".to_string()),
|
||||
@@ -123,7 +126,8 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("wahoo")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
|
||||
@@ -69,6 +69,7 @@ pub fn parse_java_sig_meta(code: &str) -> anyhow::Result<JavaMainSigMeta> {
|
||||
has_default: default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -256,7 +257,8 @@ class Main {
|
||||
typ: Typ::Bytes,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -264,7 +266,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "c".into(),
|
||||
@@ -272,7 +275,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "d".into(),
|
||||
@@ -280,7 +284,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "e".into(),
|
||||
@@ -288,7 +293,8 @@ class Main {
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "f".into(),
|
||||
@@ -296,7 +302,8 @@ class Main {
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "g".into(),
|
||||
@@ -304,7 +311,8 @@ class Main {
|
||||
typ: Typ::Bool,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "h".into(),
|
||||
@@ -312,7 +320,8 @@ class Main {
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -338,7 +347,8 @@ class Main {
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -346,7 +356,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "c".into(),
|
||||
@@ -354,7 +365,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "d".into(),
|
||||
@@ -362,7 +374,8 @@ class Main {
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "e".into(),
|
||||
@@ -370,7 +383,8 @@ class Main {
|
||||
typ: Typ::Float,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "f".into(),
|
||||
@@ -378,7 +392,8 @@ class Main {
|
||||
typ: Typ::Float,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "g".into(),
|
||||
@@ -386,7 +401,8 @@ class Main {
|
||||
typ: Typ::Bool,
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "h".into(),
|
||||
@@ -394,7 +410,8 @@ class Main {
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "i".into(),
|
||||
@@ -402,7 +419,8 @@ class Main {
|
||||
typ: Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -427,7 +445,8 @@ class Main {
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -435,7 +454,8 @@ class Main {
|
||||
typ: Typ::List(Box::new(Typ::Object(ObjectType::new(None, Some(vec![]))))),
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "c".into(),
|
||||
@@ -443,7 +463,8 @@ class Main {
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: Some(json!(null)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
@@ -152,6 +152,7 @@ pub fn parse_nu_signature(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
has_default: default.is_some() || optional,
|
||||
default: default.or_else(|| if optional { Some(json!(null)) } else { None }),
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -35,7 +36,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "c".into(),
|
||||
@@ -43,7 +45,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "d".into(),
|
||||
@@ -51,7 +54,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -80,7 +84,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: Some(serde_json::Value::Null),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
@@ -109,7 +114,8 @@ mod test {
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "bar".into(),
|
||||
@@ -117,7 +123,8 @@ mod test {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -158,7 +165,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a2".into(),
|
||||
@@ -166,7 +174,8 @@ mod test {
|
||||
typ: Typ::Bool,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a3".into(),
|
||||
@@ -174,7 +183,8 @@ mod test {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a4".into(),
|
||||
@@ -182,7 +192,8 @@ mod test {
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a5".into(),
|
||||
@@ -190,7 +201,8 @@ mod test {
|
||||
typ: Typ::Datetime,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a6".into(),
|
||||
@@ -198,7 +210,8 @@ mod test {
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a7".into(),
|
||||
@@ -206,7 +219,8 @@ mod test {
|
||||
typ: Typ::Object(ObjectType::new(None, Some(vec![]))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a8".into(),
|
||||
@@ -214,7 +228,8 @@ mod test {
|
||||
typ: Typ::List(Box::new(Typ::Unknown)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a9".into(),
|
||||
@@ -222,7 +237,8 @@ mod test {
|
||||
typ: Typ::List(Box::new(Typ::Object(ObjectType::new(None, Some(vec![]))))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "a10".into(),
|
||||
@@ -230,7 +246,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -262,7 +279,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: Some(json!("Foo")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "bar".into(),
|
||||
@@ -270,7 +288,8 @@ mod test {
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("Bar")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "bazz".into(),
|
||||
@@ -278,7 +297,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -375,7 +395,8 @@ mod test {
|
||||
typ: Typ::List(Box::new(Typ::Float)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
@@ -406,7 +427,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "foo".into(),
|
||||
@@ -414,7 +436,8 @@ mod test {
|
||||
typ: Typ::List(Box::new(Typ::Float)),
|
||||
default: Some(json!([2, 3, 4])),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -422,7 +445,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -452,7 +476,8 @@ mod test {
|
||||
typ: Typ::Datetime,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
@@ -515,7 +540,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "b".into(),
|
||||
@@ -523,7 +549,8 @@ mod test {
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "c".into(),
|
||||
@@ -531,7 +558,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: Some(serde_json::Value::Null),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "d".into(),
|
||||
@@ -539,7 +567,8 @@ mod test {
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("foo")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "bi".into(),
|
||||
@@ -547,7 +576,8 @@ mod test {
|
||||
typ: Typ::Unknown,
|
||||
default: Some(serde_json::Value::Null),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
|
||||
@@ -91,6 +91,7 @@ pub fn parse_php_signature(
|
||||
has_default: default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -146,7 +147,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
typ: Typ::Str(None),
|
||||
has_default: true,
|
||||
default: Some(Value::String("hey".to_string())),
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -154,7 +156,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
typ: Typ::Bool,
|
||||
has_default: true,
|
||||
default: Some(Value::Bool(false)),
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -162,7 +165,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
typ: Typ::Int,
|
||||
has_default: true,
|
||||
default: Some(Value::Number(Number::from(3))),
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -170,7 +174,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
typ: Typ::Float,
|
||||
has_default: true,
|
||||
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap())),
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -178,7 +183,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
typ: Typ::Resource("stripe".to_string()),
|
||||
has_default: false,
|
||||
default: None,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
|
||||
@@ -477,6 +477,7 @@ pub fn parse_python_signature(
|
||||
has_default: has_default || default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
@@ -716,7 +717,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -724,7 +726,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Datetime,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -732,7 +735,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -740,7 +744,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("wewe")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -748,7 +753,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(21)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -756,7 +762,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: Some(json!([1, 2])),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -764,7 +771,8 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
typ: Typ::Bool,
|
||||
default: Some(json!(true)),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -806,7 +814,8 @@ def main(test1: str,
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -814,7 +823,8 @@ def main(test1: str,
|
||||
typ: Typ::Datetime,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -822,7 +832,8 @@ def main(test1: str,
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -830,7 +841,8 @@ def main(test1: str,
|
||||
typ: Typ::Resource("postgresql".to_string()),
|
||||
default: Some(json!("$res:g/all/resource")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -867,7 +879,8 @@ def main(test1: str,
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -875,7 +888,8 @@ def main(test1: str,
|
||||
typ: Typ::Resource("s3_object".to_string()),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -883,7 +897,8 @@ def main(test1: str,
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("test")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -891,7 +906,8 @@ def main(test1: str,
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -925,7 +941,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
|
||||
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -936,7 +953,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
|
||||
])))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -969,7 +987,8 @@ def main(test1: DynSelect_foo): return
|
||||
typ: Typ::DynSelect("foo".to_string()),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -1094,7 +1113,8 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1102,7 +1122,8 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1110,7 +1131,8 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: Some(json!([1, 2, 3, 4])),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1118,7 +1140,8 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: Some(json!([1, 2, 3, 4])),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1126,7 +1149,8 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: Some(json!(["a", "b"])),
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1160,7 +1184,8 @@ def main(a: str, b: Optional[str], c: str | None): return
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1168,7 +1193,8 @@ def main(a: str, b: Optional[str], c: str | None): return
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -1176,7 +1202,8 @@ def main(a: str, b: Optional[str], c: str | None): return
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true,
|
||||
oidx: None
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn parse_rust_signature(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let (otyp, typ, name) = parse_rust_typ(param);
|
||||
Arg { name, otyp, typ, default: None, has_default: false, oidx: None }
|
||||
Arg { name, otyp, typ, default: None, has_default: false, oidx: None, otyp_inferred: false }
|
||||
})
|
||||
.collect_vec();
|
||||
Ok(MainArgSignature {
|
||||
|
||||
@@ -281,6 +281,7 @@ fn parse_oracledb_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -305,6 +306,7 @@ fn parse_oracledb_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -331,6 +333,7 @@ fn parse_sql_sanitized_interpolation(code: &str) -> Vec<Arg> {
|
||||
otyp: Some(otyp.to_string()),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,6 +363,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -384,6 +388,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -518,7 +523,25 @@ fn run_on_sql_statement_matches<
|
||||
}
|
||||
|
||||
pub fn parse_pg_statement_arg_indices(code: &str) -> HashSet<i32> {
|
||||
let mut arg_indices = HashSet::new();
|
||||
parse_pg_statement_arg_positions(code)
|
||||
.into_iter()
|
||||
.map(|(idx, _)| idx)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Like `parse_pg_statement_arg_indices`, but also returns the byte range of
|
||||
/// each placeholder occurrence in `code` (excluding `$`, including the digits).
|
||||
/// The same string-/comment-/dollar-quote-aware tokenizer is used, so
|
||||
/// occurrences inside string literals and comments are correctly skipped —
|
||||
/// this is what callers need to renumber `$N → $M` without mangling literal
|
||||
/// SQL bytes that happen to match the `$\d+` pattern.
|
||||
///
|
||||
/// The returned vec is in source order. Each entry is `(idx, range)` where
|
||||
/// `idx` is the parameter number and `range` covers the `$N` digits (i.e.
|
||||
/// `code[range.start - 1 .. range.end]` is the full `$N` token, and
|
||||
/// `code[range]` is just the digits).
|
||||
pub fn parse_pg_statement_arg_positions(code: &str) -> Vec<(i32, std::ops::Range<usize>)> {
|
||||
let mut positions = Vec::new();
|
||||
run_on_sql_statement_matches(
|
||||
code,
|
||||
true,
|
||||
@@ -529,21 +552,24 @@ pub fn parse_pg_statement_arg_indices(code: &str) -> HashSet<i32> {
|
||||
.is_some_and(|&(_, next_char)| next_char.is_ascii_digit())
|
||||
},
|
||||
|_, chars| {
|
||||
let start = chars.peek().map(|&(i, _)| i).unwrap_or(0);
|
||||
let mut arg_idx = String::new();
|
||||
while let Some(&(_, char)) = chars.peek() {
|
||||
let mut end = start;
|
||||
while let Some(&(i, char)) = chars.peek() {
|
||||
if char.is_ascii_digit() {
|
||||
arg_idx.push(char);
|
||||
end = i + char.len_utf8();
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(arg_idx) = arg_idx.parse::<i32>() {
|
||||
arg_indices.insert(arg_idx);
|
||||
positions.push((arg_idx, start..end));
|
||||
}
|
||||
},
|
||||
);
|
||||
arg_indices
|
||||
positions
|
||||
}
|
||||
|
||||
fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
@@ -577,12 +603,16 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: Some(idx),
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: infer types from usage for non-explicitly-typed args
|
||||
let mut hm: HashMap<i32, String> = HashMap::new();
|
||||
// Second pass: infer types from usage for non-explicitly-typed args.
|
||||
// We track whether each entry came from an inline `$N::TYPE` cast or from
|
||||
// the parser's "text" fallback, so the executor can later distinguish
|
||||
// "user committed to text" from "no info, use a placeholder".
|
||||
let mut hm: HashMap<i32, (String, bool)> = HashMap::new();
|
||||
for cap in RE_CODE_PGSQL.captures_iter(code) {
|
||||
let idx = cap
|
||||
.get(1)
|
||||
@@ -594,15 +624,23 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let typ = cap
|
||||
let cast = cap
|
||||
.get(2)
|
||||
.map(|cap| transform_types_with_spaces(&cap, &code))
|
||||
.unwrap_or("text");
|
||||
hm.insert(idx, typ.to_string());
|
||||
.map(|cap| transform_types_with_spaces(&cap, &code));
|
||||
let inferred_default = cast.is_none();
|
||||
let typ: std::borrow::Cow<str> = cast.unwrap_or(std::borrow::Cow::Borrowed("text"));
|
||||
// Prefer an explicit cast over a previously seen default — once we
|
||||
// have any inline cast for the index, lock it in.
|
||||
match hm.get(&idx) {
|
||||
Some((_, false)) => {} // already locked from explicit cast
|
||||
_ => {
|
||||
hm.insert(idx, (typ.into_owned(), inferred_default));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add inferred args
|
||||
for (i, v) in hm.iter() {
|
||||
for (i, (v, inferred)) in hm.iter() {
|
||||
let typ = v.to_lowercase();
|
||||
args.push(Arg {
|
||||
name: format!("${}", i),
|
||||
@@ -611,6 +649,7 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
otyp: Some(typ),
|
||||
has_default: false,
|
||||
oidx: Some(*i),
|
||||
otyp_inferred: *inferred,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -646,6 +685,7 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
otyp: oarg.otyp,
|
||||
has_default,
|
||||
oidx: oarg.oidx,
|
||||
otyp_inferred: oarg.otyp_inferred,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -657,8 +697,12 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<(Vec<Arg>, bool)>> {
|
||||
}
|
||||
|
||||
// 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: &Match<'a>, code: &str) -> &'a str {
|
||||
// So we look for them manually and replace them with their shorter counterpart.
|
||||
// Returns `Cow::Borrowed` for the trivial case (the regex's own match) and
|
||||
// `Cow::Owned` when we need to alias a multi-word type and/or append a `[]`
|
||||
// suffix that the regex's `\w+` capture didn't pick up.
|
||||
fn transform_types_with_spaces<'a>(cap: &Match<'a>, code: &str) -> std::borrow::Cow<'a, str> {
|
||||
use std::borrow::Cow;
|
||||
lazy_static! {
|
||||
static ref TYPES: [(&'static str, &'static str); 6] = [
|
||||
("character varying", "varchar"),
|
||||
@@ -671,20 +715,31 @@ fn transform_types_with_spaces<'a>(cap: &Match<'a>, code: &str) -> &'a str {
|
||||
}
|
||||
let typ = &code[cap.start()..];
|
||||
for (long_type, alias) in TYPES.iter() {
|
||||
let mut typ = typ;
|
||||
let mut rest = 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) {
|
||||
if rest.len() < token.len() || !rest[..token.len()].eq_ignore_ascii_case(token) {
|
||||
found_mismatch = true;
|
||||
break;
|
||||
}
|
||||
typ = typ[token.len()..].trim_start();
|
||||
rest = rest[token.len()..].trim_start();
|
||||
}
|
||||
if !found_mismatch {
|
||||
return alias;
|
||||
// The regex captured only the first word (`\w+`), so its `[]`
|
||||
// detection in `(?:\[\])?` matched against the wrong position
|
||||
// and is empty for multi-word types. Re-check the trailing
|
||||
// bytes after the multi-word match: if they start with `[]`,
|
||||
// append the array suffix to the alias so the dispatch routes
|
||||
// through `convert_vec_val` instead of binding as JSONB.
|
||||
let with_suffix = rest.starts_with("[]");
|
||||
return if with_suffix {
|
||||
Cow::Owned(format!("{alias}[]"))
|
||||
} else {
|
||||
Cow::Borrowed(*alias)
|
||||
};
|
||||
}
|
||||
}
|
||||
cap.as_str()
|
||||
Cow::Borrowed(cap.as_str())
|
||||
}
|
||||
|
||||
pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<String> {
|
||||
@@ -736,6 +791,7 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -765,6 +821,7 @@ fn parse_duckdb_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -794,6 +851,7 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -823,6 +881,7 @@ fn parse_mssql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -854,6 +913,7 @@ pub fn parse_mysql_typ(typ: &str) -> Typ {
|
||||
"bool" | "bit" => Typ::Bool,
|
||||
"double precision" | "float" | "real" | "dec" | "fixed" => Typ::Float,
|
||||
"date" | "datetime" | "timestamp" | "time" => Typ::Datetime,
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
@@ -901,6 +961,7 @@ pub fn parse_pg_typ(typ: &str) -> Typ {
|
||||
| "timestamp with time zone"
|
||||
| "timestamp without time zone" => Typ::Datetime,
|
||||
"bytea" => Typ::Bytes,
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
@@ -919,6 +980,7 @@ pub fn parse_bigquery_typ(typ: &str) -> Typ {
|
||||
"integer" | "int64" => Typ::Int,
|
||||
"float" | "float64" | "numeric" | "bignumeric" => Typ::Float,
|
||||
"boolean" | "bool" => Typ::Bool,
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
@@ -959,6 +1021,7 @@ pub fn parse_snowflake_typ(typ: &str) -> Typ {
|
||||
"int" => Typ::Int,
|
||||
"float" => Typ::Float,
|
||||
"boolean" => Typ::Bool,
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
@@ -973,6 +1036,7 @@ pub fn parse_mssql_typ(typ: &str) -> Typ {
|
||||
"bigint" | "int" | "tinyint" | "smallint" => Typ::Int,
|
||||
"float" | "real" | "numeric" | "decimal" => Typ::Float,
|
||||
"bit" => Typ::Bool,
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
@@ -1001,6 +1065,7 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("bigint".to_string()),
|
||||
@@ -1009,6 +1074,7 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1043,6 +1109,7 @@ SELECT $2::TEXT;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1051,6 +1118,7 @@ SELECT $2::TEXT;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1059,6 +1127,7 @@ SELECT $2::TEXT;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(3),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1211,6 +1280,54 @@ SELECT $2;"#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pg_statement_arg_positions_skips_strings_and_comments() -> anyhow::Result<()> {
|
||||
// Each occurrence's byte range covers JUST the digits (after `$`).
|
||||
let code = "SELECT $5, $50";
|
||||
let positions = parse_pg_statement_arg_positions(code);
|
||||
let collected: Vec<(i32, &str)> = positions
|
||||
.iter()
|
||||
.map(|(idx, range)| (*idx, &code[range.clone()]))
|
||||
.collect();
|
||||
assert_eq!(collected, vec![(5, "5"), (50, "50")]);
|
||||
|
||||
// String literals and comments must not produce positions — this is
|
||||
// what stops the do_postgresql_inner rewrite from mangling SQL like
|
||||
// `'price: $5'`.
|
||||
let code = "SELECT 'literal $5' AS lbl, $5 FROM t -- mention $5";
|
||||
let positions = parse_pg_statement_arg_positions(code);
|
||||
let positions_only: Vec<(i32, std::ops::Range<usize>)> = positions.clone();
|
||||
assert_eq!(
|
||||
positions_only.iter().map(|(i, _)| *i).collect::<Vec<_>>(),
|
||||
vec![5],
|
||||
"only the real $5 between 'lbl,' and 'FROM' should be returned"
|
||||
);
|
||||
// The single returned position is the real placeholder (between
|
||||
// `lbl, ` and ` FROM`).
|
||||
let (idx, range) = &positions[0];
|
||||
assert_eq!(*idx, 5);
|
||||
// `code[range.start - 1 .. range.end]` should be the full `$5` token.
|
||||
assert_eq!(&code[range.start - 1..range.end], "$5");
|
||||
|
||||
// Dollar-quoted blocks similarly skipped.
|
||||
let code = "SELECT $$body with $5 inside$$, $7 FROM t";
|
||||
let positions = parse_pg_statement_arg_positions(code);
|
||||
assert_eq!(
|
||||
positions.iter().map(|(i, _)| *i).collect::<Vec<_>>(),
|
||||
vec![7],
|
||||
"$5 inside $$...$$ is part of the string"
|
||||
);
|
||||
|
||||
// Repeat indices show up multiple times — caller can rewrite each.
|
||||
let code = "SELECT $1, $1, $2";
|
||||
let positions = parse_pg_statement_arg_positions(code);
|
||||
assert_eq!(
|
||||
positions.iter().map(|(i, _)| *i).collect::<Vec<_>>(),
|
||||
vec![1, 1, 2]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_blocks_non_pg_ignores_dollar_quotes() -> anyhow::Result<()> {
|
||||
// Non-Postgres dialects (MySQL/Oracle/BigQuery/Snowflake) pass `false`,
|
||||
@@ -1254,6 +1371,7 @@ SELECT ?, ?;
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1262,6 +1380,7 @@ SELECT ?, ?;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1295,6 +1414,7 @@ SELECT :param2;
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1303,6 +1423,7 @@ SELECT :param2;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1311,6 +1432,7 @@ SELECT :param2;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1344,6 +1466,7 @@ SELECT @token;
|
||||
default: Some(json!("abc")),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("int64".to_string()),
|
||||
@@ -1352,6 +1475,7 @@ SELECT @token;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1385,6 +1509,7 @@ SELECT ?;
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
@@ -1393,6 +1518,7 @@ SELECT ?;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
@@ -1401,6 +1527,7 @@ SELECT ?;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1434,6 +1561,7 @@ SELECT @P2;
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
@@ -1442,6 +1570,7 @@ SELECT @P2;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
@@ -1450,6 +1579,7 @@ SELECT @P2;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1484,6 +1614,7 @@ SELECT * FROM table_name WHERE thing = :name4;
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1492,6 +1623,7 @@ SELECT * FROM table_name WHERE thing = :name4;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1500,6 +1632,7 @@ SELECT * FROM table_name WHERE thing = :name4;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1531,6 +1664,7 @@ SELECT * FROM users WHERE id = $1 AND email = $2::text;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1539,6 +1673,7 @@ SELECT * FROM users WHERE id = $1 AND email = $2::text;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1570,6 +1705,7 @@ SELECT * FROM users LIMIT $1 OFFSET $2;
|
||||
default: Some(json!(10)),
|
||||
has_default: true,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("bigint".to_string()),
|
||||
@@ -1578,6 +1714,7 @@ SELECT * FROM users LIMIT $1 OFFSET $2;
|
||||
default: Some(json!(0)),
|
||||
has_default: true,
|
||||
oidx: Some(2),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1613,6 +1750,7 @@ WHERE id = $1
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
@@ -1621,6 +1759,7 @@ WHERE id = $1
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("timestamptz".to_string()),
|
||||
@@ -1629,6 +1768,7 @@ WHERE id = $1
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(3),
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -1658,6 +1798,7 @@ SELECT * FROM users WHERE id = ANY($1);
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
@@ -1688,6 +1829,7 @@ SELECT $1::integer;
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
@@ -1698,6 +1840,118 @@ SELECT $1::integer;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pgsql_otyp_inferred_flag() -> anyhow::Result<()> {
|
||||
// Bare `$N` (no inline cast, no decl) should produce otyp = "text"
|
||||
// *and* otyp_inferred = true. This is the signal the PG executor
|
||||
// uses to decide whether the user committed to a text target.
|
||||
let code_bare = "SELECT $1, $2";
|
||||
let args = parse_pgsql_sig(code_bare)?.args;
|
||||
let map: HashMap<String, (Option<String>, bool)> = args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, (a.otyp, a.otyp_inferred)))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
map.get("$1").cloned(),
|
||||
Some((Some("text".to_string()), true)),
|
||||
"bare $1 → otyp_inferred true"
|
||||
);
|
||||
assert_eq!(
|
||||
map.get("$2").cloned(),
|
||||
Some((Some("text".to_string()), true)),
|
||||
"bare $2 → otyp_inferred true"
|
||||
);
|
||||
|
||||
// Inline `$N::TYPE` cast → otyp_inferred = false (user committed).
|
||||
let args = parse_pgsql_sig("SELECT $1::int, $2::text")?.args;
|
||||
let map: HashMap<String, (Option<String>, bool)> = args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, (a.otyp, a.otyp_inferred)))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
map.get("$1").cloned(),
|
||||
Some((Some("int".to_string()), false))
|
||||
);
|
||||
assert_eq!(
|
||||
map.get("$2").cloned(),
|
||||
Some((Some("text".to_string()), false)),
|
||||
"explicit $2::text → otyp_inferred false (distinct from bare $2)"
|
||||
);
|
||||
|
||||
// Declaration `-- $N name (TYPE)` → otyp_inferred = false (decl is
|
||||
// explicit by definition).
|
||||
let args = parse_pgsql_sig("-- $1 name (text)\nSELECT $1")?.args;
|
||||
assert_eq!(args[0].otyp.as_deref(), Some("text"));
|
||||
assert!(!args[0].otyp_inferred);
|
||||
|
||||
// Mixed: $1 has decl, $2 is bare → flag differs per arg.
|
||||
let args = parse_pgsql_sig("-- $1 a (int)\nSELECT $1, $2")?.args;
|
||||
let map: HashMap<String, bool> = args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.otyp_inferred))
|
||||
.collect();
|
||||
assert_eq!(map.get("a").copied(), Some(false), "$1 decl → not inferred");
|
||||
assert_eq!(map.get("$2").copied(), Some(true), "$2 bare → inferred");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_s3object_arg_per_dialect() -> anyhow::Result<()> {
|
||||
// Confirms that `(s3object)` is recognised as a resource-typed arg in every
|
||||
// native SQL dialect that opts in (PG, MySQL, MSSQL, BigQuery, Snowflake).
|
||||
// The frontend uses `Typ::Resource("S3Object")` to render the S3 picker, and
|
||||
// the worker dispatches on `otyp == "s3object"` to fetch + bind the file.
|
||||
let s3 = || Typ::Resource("S3Object".to_string());
|
||||
|
||||
assert_eq!(
|
||||
parse_pgsql_sig("-- $1 myfile (s3object)\nSELECT $1::jsonb;")?
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.typ, a.otyp))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![("myfile".to_string(), s3(), Some("s3object".to_string()))]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_mssql_sig("-- @P1 myfile (s3object)\nSELECT @P1;")?
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.typ, a.otyp))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![("myfile".to_string(), s3(), Some("s3object".to_string()))]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_mysql_sig("-- :myfile (s3object)\nSELECT :myfile;")?
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.typ, a.otyp))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![("myfile".to_string(), s3(), Some("s3object".to_string()))]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_bigquery_sig("-- @myfile (s3object)\nSELECT @myfile;")?
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.typ, a.otyp))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![("myfile".to_string(), s3(), Some("s3object".to_string()))]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_snowflake_sig("-- ? myfile (s3object)\nSELECT ?;")?
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|a| (a.name, a.typ, a.otyp))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![("myfile".to_string(), s3(), Some("s3object".to_string()))]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pgsql_safe_interpolated_args() -> anyhow::Result<()> {
|
||||
// There was a bug where enum would be "angrycreative"/"bishop"/"test SELECT x"
|
||||
@@ -1721,6 +1975,7 @@ SELECT x
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: None,
|
||||
|
||||
@@ -208,9 +208,7 @@ pub fn parse_relative_imports(code: &str, path: &str) -> anyhow::Result<Vec<Stri
|
||||
|
||||
/// Check if an import path is a relative import (starts with `./`, `../`, or `/`)
|
||||
fn is_relative_import(import_path: &str) -> bool {
|
||||
import_path.starts_with("./")
|
||||
|| import_path.starts_with("../")
|
||||
|| import_path.starts_with("/")
|
||||
import_path.starts_with("./") || import_path.starts_with("../") || import_path.starts_with("/")
|
||||
}
|
||||
|
||||
/// Normalize a path by resolving `.` and `..` components
|
||||
@@ -542,6 +540,7 @@ fn parse_param(
|
||||
default: None,
|
||||
has_default: ident.id.optional || nullable,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
})
|
||||
}
|
||||
// Pat::Object(ObjectPat { ... }) = todo!()
|
||||
@@ -596,13 +595,29 @@ fn parse_param(
|
||||
if typ == Typ::Unknown && dflt.is_some() {
|
||||
typ = json_to_typ(dflt.as_ref().unwrap(), false);
|
||||
}
|
||||
Ok(Arg { otyp, name, typ, default: dflt, has_default: true, oidx: None })
|
||||
Ok(Arg {
|
||||
otyp,
|
||||
name,
|
||||
typ,
|
||||
default: dflt,
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
})
|
||||
}
|
||||
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
||||
*counter += 1;
|
||||
let name = format!("anon{}", counter);
|
||||
Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable, oidx: None })
|
||||
Ok(Arg {
|
||||
otyp: None,
|
||||
name,
|
||||
typ,
|
||||
default: None,
|
||||
has_default: nullable,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"parameter syntax unsupported: `{}`: {:#?}",
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ};
|
||||
use windmill_parser_ts::{parse_deno_signature, parse_expr_for_imports, parse_relative_imports};
|
||||
use windmill_parser_ts::{
|
||||
parse_deno_signature, parse_expr_for_imports, parse_relative_imports,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_imports_basic() {
|
||||
@@ -78,6 +80,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "num_param".to_string(),
|
||||
@@ -86,6 +89,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "bool_param".to_string(),
|
||||
@@ -94,6 +98,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "any_param".to_string(),
|
||||
@@ -102,6 +107,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -136,6 +142,7 @@ mod tests {
|
||||
default: Some(json!("World")),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "count".to_string(),
|
||||
@@ -144,6 +151,7 @@ mod tests {
|
||||
default: Some(json!(42)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "enabled".to_string(),
|
||||
@@ -152,6 +160,7 @@ mod tests {
|
||||
default: Some(json!(true)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -186,6 +195,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "numbers".to_string(),
|
||||
@@ -194,6 +204,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "items".to_string(),
|
||||
@@ -202,6 +213,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -235,6 +247,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -267,6 +280,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -308,6 +322,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -347,6 +362,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -406,6 +422,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -440,6 +457,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "base64_param".to_string(),
|
||||
@@ -448,6 +466,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "email_param".to_string(),
|
||||
@@ -456,6 +475,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "sql_param".to_string(),
|
||||
@@ -464,6 +484,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -498,6 +519,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "optional".to_string(),
|
||||
@@ -506,6 +528,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "with_default".to_string(),
|
||||
@@ -514,6 +537,7 @@ mod tests {
|
||||
default: Some(json!(false)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -593,6 +617,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -623,6 +648,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -653,6 +679,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(false),
|
||||
@@ -686,6 +713,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "numbers".to_string(),
|
||||
@@ -694,6 +722,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
Arg {
|
||||
name: "plain".to_string(),
|
||||
@@ -702,6 +731,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
},
|
||||
],
|
||||
auto_kind: None,
|
||||
@@ -744,6 +774,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(true),
|
||||
@@ -775,6 +806,7 @@ mod tests {
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
}],
|
||||
auto_kind: None,
|
||||
has_preprocessor: Some(true),
|
||||
|
||||
+24
-24
@@ -6183,7 +6183,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -6263,7 +6263,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6275,7 +6275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"serde",
|
||||
@@ -6284,7 +6284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6296,7 +6296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6308,7 +6308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -6320,7 +6320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6332,7 +6332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6344,7 +6344,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -6355,7 +6355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6366,7 +6366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6378,7 +6378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6389,7 +6389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -6411,7 +6411,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6423,7 +6423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6437,7 +6437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case",
|
||||
@@ -6454,7 +6454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6467,7 +6467,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6479,7 +6479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6497,7 +6497,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -6513,7 +6513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6529,7 +6529,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.17",
|
||||
@@ -6561,7 +6561,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6572,7 +6572,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
members = ["."]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.692.0"
|
||||
version = "1.694.0"
|
||||
edition = "2021"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature
|
||||
has_default: default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,6 +69,7 @@ pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature
|
||||
has_default: inv.default.is_some(),
|
||||
default: inv.default.map(|v| json!(format!("$res:{}", v))),
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -81,6 +83,7 @@ pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature
|
||||
has_default: false,
|
||||
default: None,
|
||||
oidx: None,
|
||||
otyp_inferred: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -215,6 +218,7 @@ pub struct AnsiblePlaybookOptions {
|
||||
pub timeout: Option<i64>,
|
||||
pub flush_cache: Option<()>,
|
||||
pub force_handlers: Option<()>,
|
||||
pub limit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -265,6 +269,8 @@ pub struct DelegateToGitRepoDetails {
|
||||
pub commit: Option<String>,
|
||||
pub inventories_location: Option<String>,
|
||||
pub vars_location: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub install_requirements: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -300,6 +306,7 @@ impl Default for AnsibleRequirements {
|
||||
timeout: None,
|
||||
flush_cache: None,
|
||||
force_handlers: None,
|
||||
limit: None,
|
||||
},
|
||||
vault_password: None,
|
||||
vault_id: vec![],
|
||||
@@ -602,6 +609,10 @@ fn extract_delegate_to_git_repo_details(value: &Yaml) -> Option<DelegateToGitRep
|
||||
.get(&Yaml::String("vars_location".to_string()))
|
||||
.and_then(|s| s.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let install_requirements = v
|
||||
.get(&Yaml::String("install_requirements".to_string()))
|
||||
.and_then(|s| s.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
return Some(DelegateToGitRepoDetails {
|
||||
resource,
|
||||
@@ -609,6 +620,7 @@ fn extract_delegate_to_git_repo_details(value: &Yaml) -> Option<DelegateToGitRep
|
||||
commit,
|
||||
inventories_location,
|
||||
vars_location,
|
||||
install_requirements,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -654,6 +666,7 @@ fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
|
||||
timeout: None,
|
||||
flush_cache: None,
|
||||
force_handlers: None,
|
||||
limit: None,
|
||||
};
|
||||
for opt in opts {
|
||||
if let Yaml::String(o) = opt {
|
||||
@@ -691,6 +704,13 @@ fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
"limit" => {
|
||||
if let Yaml::String(limit) = value {
|
||||
if !limit.is_empty() {
|
||||
ret.limit = Some(limit.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -972,4 +992,63 @@ dependencies:
|
||||
let a = parse_delegate_to_git_repo(p).unwrap();
|
||||
println!("The resulting delegate_to_kit_repo is: {:#?}", a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_options_limit() {
|
||||
let p = r#"
|
||||
---
|
||||
options:
|
||||
- vv
|
||||
- limit: webservers:!db1.example.com
|
||||
- forks: 5
|
||||
---
|
||||
- name: Test
|
||||
hosts: all
|
||||
"#;
|
||||
let (_, reqs, _) = parse_ansible_reqs(p).unwrap();
|
||||
let opts = reqs.unwrap().options;
|
||||
assert_eq!(opts.limit.as_deref(), Some("webservers:!db1.example.com"));
|
||||
assert_eq!(opts.verbosity.as_deref(), Some("vv"));
|
||||
assert_eq!(opts.forks, Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_install_requirements_default_false() {
|
||||
let p = r#"
|
||||
---
|
||||
delegate_to_git_repo:
|
||||
resource: u/admin/repo
|
||||
playbook: site.yml
|
||||
---
|
||||
- name: Test
|
||||
hosts: all
|
||||
"#;
|
||||
let (_, reqs, _) = parse_ansible_reqs(p).unwrap();
|
||||
let d = reqs.unwrap().delegate_to_git_repo.unwrap();
|
||||
assert!(!d.install_requirements);
|
||||
assert_eq!(d.playbook.as_deref(), Some("site.yml"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_install_requirements_true() {
|
||||
let p = r#"
|
||||
---
|
||||
delegate_to_git_repo:
|
||||
resource: u/admin/repo
|
||||
playbook: "{{ playbook_name }}"
|
||||
inventories_location: "inventories/{{ env }}"
|
||||
install_requirements: true
|
||||
---
|
||||
- name: Test
|
||||
hosts: all
|
||||
"#;
|
||||
let (_, reqs, _) = parse_ansible_reqs(p).unwrap();
|
||||
let d = reqs.unwrap().delegate_to_git_repo.unwrap();
|
||||
assert!(d.install_requirements);
|
||||
assert_eq!(d.playbook.as_deref(), Some("{{ playbook_name }}"));
|
||||
assert_eq!(
|
||||
d.inventories_location.as_deref(),
|
||||
Some("inventories/{{ env }}")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,15 @@ pub struct Arg {
|
||||
pub default: Option<serde_json::Value>,
|
||||
pub has_default: bool,
|
||||
pub oidx: Option<i32>,
|
||||
/// `true` when `otyp` is the parser's fallback default rather than a value
|
||||
/// the user (or SDK) actually wrote down. Currently only set by the PG SQL
|
||||
/// parser when a placeholder has no `-- $N name (TYPE)` declaration *and*
|
||||
/// no `$N::TYPE` inline cast — the otyp is `"text"` purely as a
|
||||
/// placeholder. Consumers that care about original intent (e.g. the PG
|
||||
/// executor deciding whether to coerce `Number → String` for a text
|
||||
/// target) should treat `otyp_inferred = true` as "type unknown".
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub otyp_inferred: bool,
|
||||
}
|
||||
|
||||
pub fn json_to_typ(js: &Value, precise_arrays: bool) -> Typ {
|
||||
|
||||
@@ -22,6 +22,7 @@ fn bun_code(code: &str) -> RawCode {
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -72,6 +73,7 @@ export function main(name: string, count: number) {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = RunJob::from(job)
|
||||
@@ -115,6 +117,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -148,6 +151,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -182,6 +186,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -223,6 +228,7 @@ export async function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -263,6 +269,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -296,6 +303,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -339,6 +347,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let completed = run_job_in_new_worker_until_complete(&db, false, job, port).await;
|
||||
@@ -380,6 +389,7 @@ export function notMain() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let completed = run_job_in_new_worker_until_complete(&db, false, job, port).await;
|
||||
@@ -421,6 +431,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let completed = run_job_in_new_worker_until_complete(&db, false, job, port).await;
|
||||
@@ -459,6 +470,7 @@ export async function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let completed = run_job_in_new_worker_until_complete(&db, false, job, port).await;
|
||||
@@ -514,6 +526,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -552,6 +565,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -595,6 +609,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -694,6 +709,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -730,6 +746,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -777,6 +794,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -826,6 +844,7 @@ export function main(x: number) {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
// x=5, main adds 10 = 15
|
||||
@@ -877,6 +896,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -923,6 +943,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -939,6 +960,7 @@ export function main() {
|
||||
// Dedicated Worker Protocol Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
mod dedicated_worker_protocol {
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
@@ -1647,6 +1669,7 @@ export function main(x?: number): string {
|
||||
// Deno Dedicated Worker Protocol Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
mod dedicated_worker_protocol_deno {
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
@@ -1963,6 +1986,7 @@ export function main(name: string) {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = RunJob::from(job)
|
||||
@@ -2027,6 +2051,7 @@ export function main(name: string) {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = RunJob::from(job)
|
||||
@@ -2304,6 +2329,7 @@ module.exports.main = function() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = RunJob::from(job)
|
||||
@@ -2346,6 +2372,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = RunJob::from(job)
|
||||
@@ -2397,6 +2424,7 @@ module.exports.main = function() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -2458,6 +2486,7 @@ export function main() {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -63,10 +63,7 @@ fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuil
|
||||
|
||||
/// Create an app with inline script via API
|
||||
async fn create_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps/create",
|
||||
port
|
||||
);
|
||||
let url = format!("http://localhost:{}/api/w/test-workspace/apps/create", port);
|
||||
let resp = authed(client().post(&url), SAME_WS_TOKEN)
|
||||
.json(&json!({
|
||||
"path": path,
|
||||
@@ -102,17 +99,18 @@ async fn create_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("create app failed: {} - {}", resp.status(), resp.text().await?);
|
||||
anyhow::bail!(
|
||||
"create app failed: {} - {}",
|
||||
resp.status(),
|
||||
resp.text().await?
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a raw app with inline script via API (uses regular app endpoint with rawapp type)
|
||||
async fn create_raw_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps/create",
|
||||
port
|
||||
);
|
||||
let url = format!("http://localhost:{}/api/w/test-workspace/apps/create", port);
|
||||
let resp = authed(client().post(&url), SAME_WS_TOKEN)
|
||||
.json(&json!({
|
||||
"path": path,
|
||||
@@ -146,12 +144,21 @@ async fn create_raw_app_with_inline_script(port: u16, path: &str) -> anyhow::Res
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("create raw app failed: {} - {}", resp.status(), resp.text().await?);
|
||||
anyhow::bail!(
|
||||
"create raw app failed: {} - {}",
|
||||
resp.status(),
|
||||
resp.text().await?
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result<String> {
|
||||
async fn run_app_inline_script(
|
||||
port: u16,
|
||||
token: &str,
|
||||
app_path: &str,
|
||||
force_viewer: bool,
|
||||
) -> anyhow::Result<String> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}",
|
||||
port, app_path
|
||||
@@ -173,13 +180,22 @@ async fn run_app_inline_script(port: u16, token: &str, app_path: &str, force_vie
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("app inline script run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
anyhow::bail!(
|
||||
"app inline script run failed: {} - {}",
|
||||
resp.status(),
|
||||
resp.text().await?
|
||||
);
|
||||
}
|
||||
let job_id = resp.text().await?;
|
||||
wait_for_job_result(port, token, &job_id).await
|
||||
}
|
||||
|
||||
async fn run_raw_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result<String> {
|
||||
async fn run_raw_app_inline_script(
|
||||
port: u16,
|
||||
token: &str,
|
||||
app_path: &str,
|
||||
force_viewer: bool,
|
||||
) -> anyhow::Result<String> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}",
|
||||
port, app_path
|
||||
@@ -200,7 +216,11 @@ async fn run_raw_app_inline_script(port: u16, token: &str, app_path: &str, force
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("raw app inline script run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
anyhow::bail!(
|
||||
"raw app inline script run failed: {} - {}",
|
||||
resp.status(),
|
||||
resp.text().await?
|
||||
);
|
||||
}
|
||||
let job_id = resp.text().await?;
|
||||
wait_for_job_result(port, token, &job_id).await
|
||||
@@ -215,8 +235,12 @@ async fn wait_for_job_result(port: u16, token: &str, job_id: &str) -> anyhow::Re
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
let resp = authed(client().get(&url), token).send().await?;
|
||||
if resp.status().is_success() {
|
||||
return Ok(resp.json::<serde_json::Value>().await?
|
||||
.as_str().unwrap_or("").to_string());
|
||||
return Ok(resp
|
||||
.json::<serde_json::Value>()
|
||||
.await?
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string());
|
||||
}
|
||||
}
|
||||
anyhow::bail!("timeout waiting for job result")
|
||||
@@ -268,24 +292,38 @@ async fn test_app_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
|
||||
let app_path = "f/test/email_app";
|
||||
|
||||
in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// Create the app with inline script first
|
||||
create_app_with_inline_script(port, app_path).await?;
|
||||
in_test_worker(
|
||||
Connection::Sql(db.clone()),
|
||||
async move {
|
||||
// Create the app with inline script first
|
||||
create_app_with_inline_script(port, app_path).await?;
|
||||
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(
|
||||
result, SAME_WS_EMAIL,
|
||||
"same workspace user should get their email"
|
||||
);
|
||||
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email");
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(
|
||||
result, OTHER_WS_EMAIL,
|
||||
"other workspace user should get their email"
|
||||
);
|
||||
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email");
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(
|
||||
result, NO_WS_EMAIL,
|
||||
"no workspace user should get their email"
|
||||
);
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}, port).await?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
},
|
||||
port,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -300,24 +338,38 @@ async fn test_raw_app_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()
|
||||
|
||||
let app_path = "f/test/email_raw_app";
|
||||
|
||||
in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// Create the raw app with inline script first
|
||||
create_raw_app_with_inline_script(port, app_path).await?;
|
||||
in_test_worker(
|
||||
Connection::Sql(db.clone()),
|
||||
async move {
|
||||
// Create the raw app with inline script first
|
||||
create_raw_app_with_inline_script(port, app_path).await?;
|
||||
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_raw_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_raw_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(
|
||||
result, SAME_WS_EMAIL,
|
||||
"same workspace user should get their email"
|
||||
);
|
||||
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email");
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(
|
||||
result, OTHER_WS_EMAIL,
|
||||
"other workspace user should get their email"
|
||||
);
|
||||
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email");
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(
|
||||
result, NO_WS_EMAIL,
|
||||
"no workspace user should get their email"
|
||||
);
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}, port).await?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
},
|
||||
port,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ async fn test_error_handler_settings(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
after_set,
|
||||
Some("script/f/test/error_handler".to_string())
|
||||
);
|
||||
assert_eq!(after_set, Some("script/f/test/error_handler".to_string()));
|
||||
|
||||
// Verify extra_args
|
||||
let extra_args = sqlx::query_scalar!(
|
||||
@@ -162,7 +159,8 @@ export async function main(path: string, email: string, job_id: string, is_flow:
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
@@ -285,7 +283,8 @@ async fn test_error_handler_muted_on_script(db: Pool<Postgres>) -> anyhow::Resul
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
@@ -380,7 +379,8 @@ async fn test_error_handler_not_triggered_on_success(db: Pool<Postgres>) -> anyh
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
+851
-13
@@ -49,7 +49,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
@@ -89,7 +90,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete_with(db, false, port, |id| async move {
|
||||
let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", id)
|
||||
@@ -276,19 +278,25 @@ mod job_payload {
|
||||
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_427(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
MIN_VERSION.store(std::sync::Arc::new(MIN_VERSION_IS_AT_LEAST_1_427.version().clone()));
|
||||
MIN_VERSION.store(std::sync::Arc::new(
|
||||
MIN_VERSION_IS_AT_LEAST_1_427.version().clone(),
|
||||
));
|
||||
test_dependencies_payload(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_432(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
MIN_VERSION.store(std::sync::Arc::new(MIN_VERSION_IS_AT_LEAST_1_432.version().clone()));
|
||||
MIN_VERSION.store(std::sync::Arc::new(
|
||||
MIN_VERSION_IS_AT_LEAST_1_432.version().clone(),
|
||||
));
|
||||
test_dependencies_payload(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_440(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
MIN_VERSION.store(std::sync::Arc::new(MIN_VERSION_IS_AT_LEAST_1_440.version().clone()));
|
||||
MIN_VERSION.store(std::sync::Arc::new(
|
||||
MIN_VERSION_IS_AT_LEAST_1_440.version().clone(),
|
||||
));
|
||||
test_dependencies_payload(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -424,7 +432,8 @@ mod job_payload {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: false,
|
||||
version: 1443253234253454, labels: None,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
@@ -473,7 +482,8 @@ mod job_payload {
|
||||
path: "f/system/hello_with_preprocessor".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253456, labels: None,
|
||||
version: 1443253234253456,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete_with(db, false, port, |id| async move {
|
||||
let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", id)
|
||||
@@ -543,7 +553,8 @@ mod job_payload {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454, labels: None,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
@@ -554,6 +565,8 @@ mod job_payload {
|
||||
step_id: "a".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})
|
||||
.arg("iter", json!({ "value": "tests", "index": 0 }))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -727,6 +740,7 @@ mod job_payload {
|
||||
step_id: "a".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
..Default::default()
|
||||
}),
|
||||
json!("foo"),
|
||||
json!([
|
||||
@@ -742,6 +756,7 @@ mod job_payload {
|
||||
step_id: "b".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
..Default::default()
|
||||
}),
|
||||
json!("bar"),
|
||||
json!([
|
||||
@@ -757,6 +772,7 @@ mod job_payload {
|
||||
step_id: "c".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
..Default::default()
|
||||
}),
|
||||
json!("yolo"),
|
||||
json!([
|
||||
@@ -769,6 +785,823 @@ mod job_payload {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Walk a chain of `(step_id, iter)` hops through nested completed jobs to
|
||||
/// reach a deeper child UUID. Used by nested-restart tests to assert that
|
||||
/// preserved iterations / preserved siblings reuse the original child UUID
|
||||
/// (and that re-run ones get a new UUID).
|
||||
async fn nested_child_job_id(
|
||||
db: &Pool<Postgres>,
|
||||
mut job_id: uuid::Uuid,
|
||||
path: &[(&str, Option<usize>)],
|
||||
) -> uuid::Uuid {
|
||||
for (step_id, iter) in path {
|
||||
job_id = child_job_id_for_step(db, job_id, step_id, *iter).await;
|
||||
}
|
||||
job_id
|
||||
}
|
||||
|
||||
/// Look up a single child job UUID from a completed flow's `flow_status` for
|
||||
/// a given top-level step (and optional iteration index for ForLoop /
|
||||
/// BranchAll containers).
|
||||
async fn child_job_id_for_step(
|
||||
db: &Pool<Postgres>,
|
||||
flow_job_id: uuid::Uuid,
|
||||
step_id: &str,
|
||||
iter: Option<usize>,
|
||||
) -> uuid::Uuid {
|
||||
let row = sqlx::query!(
|
||||
"SELECT flow_status FROM v2_job_completed WHERE id = $1",
|
||||
flow_job_id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.unwrap();
|
||||
let raw = row.flow_status.expect("flow_status missing");
|
||||
let status: windmill_common::flow_status::FlowStatus =
|
||||
serde_json::from_value(raw).expect("parse flow_status");
|
||||
let module = status
|
||||
.modules
|
||||
.iter()
|
||||
.find(|m| m.id() == step_id)
|
||||
.expect("step not found in completed flow_status");
|
||||
match iter {
|
||||
Some(i) => module
|
||||
.flow_jobs()
|
||||
.expect("expected flow_jobs on container module")
|
||||
.get(i)
|
||||
.copied()
|
||||
.expect("iteration not found in flow_jobs"),
|
||||
None => module.job().expect("expected single child job on module"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_branchone(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::flow_status::BranchChosen;
|
||||
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// BranchOne with TWO inner steps so we can verify that earlier siblings
|
||||
// inside the branch are preserved when restarting at a later one.
|
||||
let flow_value: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"world": { "type": "javascript", "expr": "flow_input.world" }
|
||||
},
|
||||
"content": "export function main(world: string) { return `pre-${world}` }"
|
||||
}
|
||||
}, {
|
||||
"id": "branch",
|
||||
"value": {
|
||||
"type": "branchone",
|
||||
"default": [],
|
||||
"branches": [{
|
||||
"expr": "true",
|
||||
"modules": [{
|
||||
"id": "inner_first",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"world": { "type": "javascript", "expr": "flow_input.world" }
|
||||
},
|
||||
"content": "export function main(world: string) { return `first-${world}` }"
|
||||
}
|
||||
}, {
|
||||
"id": "inner_second",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"world": { "type": "javascript", "expr": "flow_input.world" },
|
||||
"first": { "type": "javascript", "expr": "results.inner_first" }
|
||||
},
|
||||
"content": "export function main(world: string, first: string) { return `${first}|second-${world}` }"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": { "world": { "type": "string" } },
|
||||
"order": ["world"]
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: None,
|
||||
})
|
||||
.arg("world", json!("foo"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(
|
||||
first_run.json_result().unwrap(),
|
||||
json!("first-foo|second-foo")
|
||||
);
|
||||
|
||||
let original_branch_child =
|
||||
child_job_id_for_step(db, first_run.id, "branch", None).await;
|
||||
// Capture the original inner_first job UUID so we can later assert the
|
||||
// restart-at-inner_second run preserves it byte-for-byte (i.e., the
|
||||
// step is reused, not silently re-executed with the same args).
|
||||
let original_inner_first =
|
||||
nested_child_job_id(db, first_run.id, &[("branch", None), ("inner_first", None)])
|
||||
.await;
|
||||
let original_inner_second = nested_child_job_id(
|
||||
db,
|
||||
first_run.id,
|
||||
&[("branch", None), ("inner_second", None)],
|
||||
)
|
||||
.await;
|
||||
|
||||
// Restart at the FIRST inner step. Both inner steps re-run with new args.
|
||||
let restarted_at_first = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "branch".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: Some(BranchChosen::Branch { branch: 0 }),
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: original_branch_child,
|
||||
step_id: "inner_first".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.arg("world", json!("bar"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(
|
||||
restarted_at_first.json_result().unwrap(),
|
||||
json!("first-bar|second-bar")
|
||||
);
|
||||
|
||||
// Restart at the SECOND inner step (the user-reported case). The first
|
||||
// inner step's original output ("first-foo") must be preserved as a
|
||||
// dependency for the second step's `results.inner_first` reference.
|
||||
let restarted_at_second = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "branch".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: Some(BranchChosen::Branch { branch: 0 }),
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: original_branch_child,
|
||||
step_id: "inner_second".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.arg("world", json!("baz"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// `inner_first` keeps its original "first-foo" result; only `inner_second`
|
||||
// re-runs with the new `world=baz` arg.
|
||||
assert_eq!(
|
||||
restarted_at_second.json_result().unwrap(),
|
||||
json!("first-foo|second-baz")
|
||||
);
|
||||
|
||||
// Identity proof: inner_first's UUID in the restarted run must be the
|
||||
// exact same UUID as in the original run (no re-execution), and
|
||||
// inner_second must be a fresh UUID (was re-executed).
|
||||
let new_inner_first = nested_child_job_id(
|
||||
db,
|
||||
restarted_at_second.id,
|
||||
&[("branch", None), ("inner_first", None)],
|
||||
)
|
||||
.await;
|
||||
let new_inner_second = nested_child_job_id(
|
||||
db,
|
||||
restarted_at_second.id,
|
||||
&[("branch", None), ("inner_second", None)],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
new_inner_first, original_inner_first,
|
||||
"inner_first should reuse the original job UUID"
|
||||
);
|
||||
assert_ne!(
|
||||
new_inner_second, original_inner_second,
|
||||
"inner_second should be a fresh job"
|
||||
);
|
||||
};
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_forloop_iteration(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Sequential ForLoop with an inner step. We restart at the inner step inside
|
||||
// iteration 1, expecting iteration 0 to remain unchanged and only iteration
|
||||
// 1 to re-run with new input.
|
||||
let flow_value: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "loop",
|
||||
"value": {
|
||||
"type": "forloopflow",
|
||||
"iterator": { "type": "javascript", "expr": "['x', 'y']" },
|
||||
"skip_failures": false,
|
||||
"modules": [{
|
||||
"id": "inner",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"iter_val": { "type": "javascript", "expr": "flow_input.iter.value" },
|
||||
"tag": { "type": "javascript", "expr": "flow_input.tag" }
|
||||
},
|
||||
"content": "export function main(iter_val: string, tag: string) { return `${tag}:${iter_val}` }"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": { "tag": { "type": "string" } },
|
||||
"order": ["tag"]
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: None,
|
||||
})
|
||||
.arg("tag", json!("first"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(
|
||||
first_run.json_result().unwrap(),
|
||||
json!(["first:x", "first:y"])
|
||||
);
|
||||
|
||||
// Original child jobs for both iterations.
|
||||
let original_iter0_child =
|
||||
child_job_id_for_step(db, first_run.id, "loop", Some(0)).await;
|
||||
let original_iter1_child =
|
||||
child_job_id_for_step(db, first_run.id, "loop", Some(1)).await;
|
||||
|
||||
let restarted = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "loop".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: original_iter1_child,
|
||||
step_id: "inner".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.arg("tag", json!("second"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// Iteration 0 keeps the original "first:x"; iteration 1 re-ran with "second"
|
||||
assert_eq!(
|
||||
restarted.json_result().unwrap(),
|
||||
json!(["first:x", "second:y"])
|
||||
);
|
||||
// Identity proof: iter 0's child UUID survives intact, iter 1 is fresh.
|
||||
let new_iter0_child = child_job_id_for_step(db, restarted.id, "loop", Some(0)).await;
|
||||
let new_iter1_child = child_job_id_for_step(db, restarted.id, "loop", Some(1)).await;
|
||||
assert_eq!(
|
||||
new_iter0_child, original_iter0_child,
|
||||
"iteration 0 should reuse the original child job"
|
||||
);
|
||||
assert_ne!(
|
||||
new_iter1_child, original_iter1_child,
|
||||
"iteration 1 should be a fresh job"
|
||||
);
|
||||
};
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for the FlowNode case: when a flow is deployed, container
|
||||
/// bodies (BranchOne branches, ForLoop iterations) get compiled into FlowNodes.
|
||||
/// A nested restart targeting a step inside such a container spawns a child as
|
||||
/// `JobPayload::RestartedFlow` against the original FlowNode-kind child. Without
|
||||
/// preserving the original `JobKind` (the bug we just fixed), the new spawn
|
||||
/// would land as `JobKind::Flow` with the FlowNode id misinterpreted as a
|
||||
/// flow_version id, failing with "Flow version not found".
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_deployed_loop_flownode(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// The fixture's `hello_with_nodes_flow` is a deployed flow with a top-level
|
||||
// ForLoop iterating ['foo', 'bar', 'baz'], whose body has modules `b` (greet)
|
||||
// and `c` (echo greeting). After deployment the body is wrapped in a FlowNode.
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::Flow {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
|
||||
// Iteration 1's child job (kind=flownode after FlowDependencies has run).
|
||||
let original_iter1_child = child_job_id_for_step(db, first_run.id, "a", Some(1)).await;
|
||||
|
||||
// Restart at inner step `c` of iteration 1. Iteration 0 keeps its original
|
||||
// result; iteration 1 re-runs starting at `c` (so `b` is preserved as
|
||||
// Success inside the iteration); iteration 2 fresh-runs.
|
||||
let restarted = RunJob::from(JobPayload::RestartedFlow {
|
||||
completed_job_id: first_run.id,
|
||||
step_id: "a".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: original_iter1_child,
|
||||
step_id: "c".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
})
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// Same args, same output as original — the goal is to verify the spawn
|
||||
// doesn't 500 with "Flow version not found" when the inner child is a
|
||||
// FlowNode.
|
||||
assert_eq!(
|
||||
restarted.json_result().unwrap(),
|
||||
json!([
|
||||
"Did you just say \"Hello foo!\"??!",
|
||||
"Did you just say \"Hello bar!\"??!",
|
||||
"Did you just say \"Hello baz!\"??!",
|
||||
])
|
||||
);
|
||||
};
|
||||
// Exercise BOTH the pre-deployment path (raw flow inline, FlowPreview kind)
|
||||
// and the post-deployment path (FlowNode kind) — the latter is the case the
|
||||
// user originally hit.
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
let _ = RunJob::from(JobPayload::FlowDependencies {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
version: 1443253234253454,
|
||||
debouncing_settings: Default::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Two ForLoops nested inside each other. We restart at the leaf step inside
|
||||
/// outer iter K=1 and inner iter M=1. Iters before K stay frozen at the parent
|
||||
/// level; iter K's inner iters before M stay frozen at the inner level; the
|
||||
/// inner-M leaf re-runs with new input.
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_nested_forloops(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Outer iterates [1,2]; for each outer iter, inner iterates ['x','y'];
|
||||
// leaf returns "<tag>:<inner>". Tag comes from flow_input so we can
|
||||
// observe which leaves re-ran.
|
||||
let flow_value: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "outer",
|
||||
"value": {
|
||||
"type": "forloopflow",
|
||||
"iterator": { "type": "javascript", "expr": "[1, 2]" },
|
||||
"skip_failures": false,
|
||||
"modules": [{
|
||||
"id": "inner",
|
||||
"value": {
|
||||
"type": "forloopflow",
|
||||
"iterator": { "type": "javascript", "expr": "['x', 'y']" },
|
||||
"skip_failures": false,
|
||||
"modules": [{
|
||||
"id": "leaf",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"tag": { "type": "javascript", "expr": "flow_input.tag" },
|
||||
"iv": { "type": "javascript", "expr": "flow_input.iter.value" }
|
||||
},
|
||||
"content": "export function main(tag: string, iv: string) { return `${tag}:${iv}` }"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": { "tag": { "type": "string" } },
|
||||
"order": ["tag"]
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: None,
|
||||
})
|
||||
.arg("tag", json!("first"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(
|
||||
first_run.json_result().unwrap(),
|
||||
json!([["first:x", "first:y"], ["first:x", "first:y"]])
|
||||
);
|
||||
|
||||
// Snapshot every original child UUID we'll later compare against.
|
||||
let original_outer_iter0 =
|
||||
child_job_id_for_step(db, first_run.id, "outer", Some(0)).await;
|
||||
let outer_iter1_child = child_job_id_for_step(db, first_run.id, "outer", Some(1)).await;
|
||||
let original_outer1_inner0 =
|
||||
child_job_id_for_step(db, outer_iter1_child, "inner", Some(0)).await;
|
||||
let inner_iter1_child =
|
||||
child_job_id_for_step(db, outer_iter1_child, "inner", Some(1)).await;
|
||||
|
||||
let restarted = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "outer".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: outer_iter1_child,
|
||||
step_id: "inner".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: inner_iter1_child,
|
||||
step_id: "leaf".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.arg("tag", json!("second"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// Outer iter 0 frozen; outer iter 1 has inner iter 0 frozen and inner
|
||||
// iter 1 re-run with new tag.
|
||||
assert_eq!(
|
||||
restarted.json_result().unwrap(),
|
||||
json!([["first:x", "first:y"], ["first:x", "second:y"]])
|
||||
);
|
||||
// Identity proof at every layer:
|
||||
// * outer iter 0's child UUID survives (preserved iteration)
|
||||
// * outer iter 1's child UUID is fresh (re-spawned as RestartedFlow)
|
||||
// * inside that fresh outer-iter-1 child, inner iter 0's UUID equals
|
||||
// the ORIGINAL outer-iter-1's inner-iter-0 UUID (preserved through
|
||||
// the nested chain)
|
||||
// * inner iter 1's UUID is fresh
|
||||
let new_outer_iter0 = child_job_id_for_step(db, restarted.id, "outer", Some(0)).await;
|
||||
let new_outer_iter1 = child_job_id_for_step(db, restarted.id, "outer", Some(1)).await;
|
||||
let new_outer1_inner0 =
|
||||
child_job_id_for_step(db, new_outer_iter1, "inner", Some(0)).await;
|
||||
let new_outer1_inner1 =
|
||||
child_job_id_for_step(db, new_outer_iter1, "inner", Some(1)).await;
|
||||
assert_eq!(
|
||||
new_outer_iter0, original_outer_iter0,
|
||||
"outer iter 0 should reuse original child"
|
||||
);
|
||||
assert_ne!(
|
||||
new_outer_iter1, outer_iter1_child,
|
||||
"outer iter 1 should be a fresh child"
|
||||
);
|
||||
assert_eq!(
|
||||
new_outer1_inner0, original_outer1_inner0,
|
||||
"inner iter 0 inside outer iter 1 should reuse original child"
|
||||
);
|
||||
assert_ne!(
|
||||
new_outer1_inner1, inner_iter1_child,
|
||||
"inner iter 1 inside outer iter 1 should be a fresh child"
|
||||
);
|
||||
};
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BranchOne nested inside another BranchOne. We restart at the deepest
|
||||
/// leaf with both branches locked to their original choices.
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_nested_branchone(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::flow_status::BranchChosen;
|
||||
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let flow_value: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "outer_branch",
|
||||
"value": {
|
||||
"type": "branchone",
|
||||
"default": [],
|
||||
"branches": [{
|
||||
"expr": "true",
|
||||
"modules": [{
|
||||
"id": "inner_branch",
|
||||
"value": {
|
||||
"type": "branchone",
|
||||
"default": [],
|
||||
"branches": [{
|
||||
"expr": "true",
|
||||
"modules": [{
|
||||
"id": "leaf",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"tag": { "type": "javascript", "expr": "flow_input.tag" }
|
||||
},
|
||||
"content": "export function main(tag: string) { return `leaf:${tag}` }"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": { "tag": { "type": "string" } },
|
||||
"order": ["tag"]
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: None,
|
||||
})
|
||||
.arg("tag", json!("first"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(first_run.json_result().unwrap(), json!("leaf:first"));
|
||||
|
||||
let outer_branch_child =
|
||||
child_job_id_for_step(db, first_run.id, "outer_branch", None).await;
|
||||
let inner_branch_child =
|
||||
child_job_id_for_step(db, outer_branch_child, "inner_branch", None).await;
|
||||
|
||||
let restarted = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "outer_branch".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: Some(BranchChosen::Branch { branch: 0 }),
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: outer_branch_child,
|
||||
step_id: "inner_branch".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: Some(BranchChosen::Branch { branch: 0 }),
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: inner_branch_child,
|
||||
step_id: "leaf".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.arg("tag", json!("second"))
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
assert_eq!(restarted.json_result().unwrap(), json!("leaf:second"));
|
||||
};
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restart at a step inside a SUBFLOW (`Flow{path}`) called from a parent flow.
|
||||
/// The parent invokes `f/system/hello_with_nodes_flow` (which has a top-level
|
||||
/// ForLoop). We restart at the inner step `c` of iteration 1 of that loop,
|
||||
/// from the parent's perspective. This exercises the cross-job-spawn chain
|
||||
/// crossing a subflow boundary AND descending into a ForLoop inside the
|
||||
/// subflow.
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_nested_restart_inside_subflow(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let flow_value: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "h",
|
||||
"value": {
|
||||
"path": "f/system/hello_with_nodes_flow",
|
||||
"type": "flow",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}],
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"order": []
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let test = || async {
|
||||
let db = &db;
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: None,
|
||||
})
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// The subflow returns the loop's results; the parent flow returns the
|
||||
// subflow's result as its own output.
|
||||
assert_eq!(
|
||||
first_run.json_result().unwrap(),
|
||||
json!([
|
||||
"Did you just say \"Hello foo!\"??!",
|
||||
"Did you just say \"Hello bar!\"??!",
|
||||
"Did you just say \"Hello baz!\"??!",
|
||||
])
|
||||
);
|
||||
|
||||
let subflow_child = child_job_id_for_step(db, first_run.id, "h", None).await;
|
||||
let original_subflow_iter0 =
|
||||
child_job_id_for_step(db, subflow_child, "a", Some(0)).await;
|
||||
let subflow_iter1_child = child_job_id_for_step(db, subflow_child, "a", Some(1)).await;
|
||||
// Capture the leaf "c" inside iter 0 of the original subflow, which is
|
||||
// a step we explicitly do NOT restart at — its UUID must survive into
|
||||
// the restarted run unchanged, despite crossing two parent layers
|
||||
// (parent's `h` → subflow's `a` → iter 0 → `c`).
|
||||
let original_subflow_iter0_c =
|
||||
nested_child_job_id(db, subflow_child, &[("a", Some(0)), ("c", None)]).await;
|
||||
|
||||
let restarted = RunJob::from(JobPayload::RawFlow {
|
||||
value: flow_value.clone(),
|
||||
path: None,
|
||||
restarted_from: Some(RestartedFrom {
|
||||
flow_job_id: first_run.id,
|
||||
step_id: "h".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: subflow_child,
|
||||
step_id: "a".into(),
|
||||
branch_or_iteration_n: Some(1),
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: Some(Box::new(RestartedFrom {
|
||||
flow_job_id: subflow_iter1_child,
|
||||
step_id: "c".into(),
|
||||
branch_or_iteration_n: None,
|
||||
flow_version: None,
|
||||
branch_chosen: None,
|
||||
nested: None,
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
})
|
||||
.run_until_complete(db, false, port)
|
||||
.await;
|
||||
// Same input → same output, produced via the cross-job restart chain.
|
||||
assert_eq!(
|
||||
restarted.json_result().unwrap(),
|
||||
json!([
|
||||
"Did you just say \"Hello foo!\"??!",
|
||||
"Did you just say \"Hello bar!\"??!",
|
||||
"Did you just say \"Hello baz!\"??!",
|
||||
])
|
||||
);
|
||||
// Identity proof across the subflow boundary:
|
||||
// * the parent's `h` child UUID is fresh (subflow re-spawned as a
|
||||
// RestartedFlow) — we don't compare it directly, but inside it…
|
||||
// * iter 0 of the subflow's `a` loop reuses the original UUID
|
||||
// * iter 1 is a fresh UUID
|
||||
// * the inner step `c` inside the preserved iter 0 also reuses its
|
||||
// original UUID (preservation propagates two layers deep)
|
||||
let new_subflow_child = child_job_id_for_step(db, restarted.id, "h", None).await;
|
||||
let new_subflow_iter0 =
|
||||
child_job_id_for_step(db, new_subflow_child, "a", Some(0)).await;
|
||||
let new_subflow_iter1 =
|
||||
child_job_id_for_step(db, new_subflow_child, "a", Some(1)).await;
|
||||
let new_subflow_iter0_c =
|
||||
nested_child_job_id(db, new_subflow_child, &[("a", Some(0)), ("c", None)]).await;
|
||||
assert_eq!(
|
||||
new_subflow_iter0, original_subflow_iter0,
|
||||
"subflow iter 0 should reuse original child"
|
||||
);
|
||||
assert_ne!(
|
||||
new_subflow_iter1, subflow_iter1_child,
|
||||
"subflow iter 1 should be a fresh child"
|
||||
);
|
||||
assert_eq!(
|
||||
new_subflow_iter0_c, original_subflow_iter0_c,
|
||||
"step `c` inside preserved iter 0 should reuse its original UUID"
|
||||
);
|
||||
};
|
||||
// Pre-deployment: subflow's iteration wrapper is a FlowPreview (inline
|
||||
// RawFlow); leaf `c` runs as a rawscript embedded in the wrapper.
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
// Post-deployment: subflow's body is compiled into a FlowNode; the iteration
|
||||
// wrapper jobs run with kind=FlowNode and the leaf becomes a FlowScript
|
||||
// referencing a flow_node id. Re-running the same scenario exercises the
|
||||
// kind-preservation path through the subflow boundary AND a level deeper.
|
||||
let _ = RunJob::from(JobPayload::FlowDependencies {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
version: 1443253234253454,
|
||||
debouncing_settings: Default::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
test_for_versions(VERSION_FLAGS.iter().copied(), test).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dedicated_worker_preprocessor_bun(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
@@ -789,7 +1622,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -839,7 +1673,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -889,7 +1724,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -939,7 +1775,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -991,7 +1828,8 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
|
||||
@@ -68,6 +68,7 @@ async fn test_list_jobs_without_include_args(db: Pool<Postgres>) -> anyhow::Resu
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.push(&db)
|
||||
@@ -125,6 +126,7 @@ async fn test_list_jobs_with_include_args(db: Pool<Postgres>) -> anyhow::Result<
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.push(&db)
|
||||
@@ -196,6 +198,7 @@ async fn test_list_jobs_completed_with_include_args(db: Pool<Postgres>) -> anyho
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -269,6 +272,7 @@ async fn test_list_jobs_mixed_queue_and_completed(db: Pool<Postgres>) -> anyhow:
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("completed_arg", json!("completed_value"))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -290,6 +294,7 @@ async fn test_list_jobs_mixed_queue_and_completed(db: Pool<Postgres>) -> anyhow:
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("queued_arg", json!("queued_value"))
|
||||
.push(&db)
|
||||
@@ -375,6 +380,7 @@ async fn test_list_jobs_multiple_queued_with_include_args(
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(1))
|
||||
.push(&db)
|
||||
@@ -393,6 +399,7 @@ async fn test_list_jobs_multiple_queued_with_include_args(
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("y", json!(2))
|
||||
.push(&db)
|
||||
@@ -471,6 +478,7 @@ async fn test_queue_list_without_include_args(db: Pool<Postgres>) -> anyhow::Res
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.push(&db)
|
||||
@@ -531,6 +539,7 @@ async fn test_queue_list_with_include_args(db: Pool<Postgres>) -> anyhow::Result
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.push(&db)
|
||||
@@ -601,6 +610,7 @@ async fn test_queue_list_multiple_jobs_with_include_args(db: Pool<Postgres>) ->
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("a", json!("value_a"))
|
||||
.push(&db)
|
||||
@@ -619,6 +629,7 @@ async fn test_queue_list_multiple_jobs_with_include_args(db: Pool<Postgres>) ->
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("b", json!("value_b"))
|
||||
.push(&db)
|
||||
@@ -698,6 +709,7 @@ async fn test_completed_list_without_include_args(db: Pool<Postgres>) -> anyhow:
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -764,6 +776,7 @@ async fn test_completed_list_with_include_args(db: Pool<Postgres>) -> anyhow::Re
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("x", json!(42))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -839,6 +852,7 @@ async fn test_completed_list_multiple_jobs_with_include_args(
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("a", json!("completed_a"))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -860,6 +874,7 @@ async fn test_completed_list_multiple_jobs_with_include_args(
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.arg("b", json!("completed_b"))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -992,6 +1007,7 @@ async fn test_job_without_labels_has_no_labels_field(db: Pool<Postgres>) -> anyh
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}))
|
||||
.push(&db)
|
||||
.await;
|
||||
@@ -1135,6 +1151,7 @@ async fn test_wm_labels_from_result_merged_with_static_labels(
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
tag: None,
|
||||
}));
|
||||
|
||||
let completed = job
|
||||
|
||||
@@ -40,6 +40,7 @@ fn nativets_code(content: &str) -> JobPayload {
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ async fn push_job(db: &Pool<Postgres>, content: &str, args: &serde_json::Value)
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let tx = PushIsolationLevel::IsolatedRoot(db.clone());
|
||||
|
||||
@@ -395,10 +395,7 @@ async fn test_root_job_span_created_on_success() {
|
||||
attrs.contains(&"script_path"),
|
||||
"missing script_path attribute"
|
||||
);
|
||||
assert!(
|
||||
attrs.contains(&"job_kind"),
|
||||
"missing job_kind attribute"
|
||||
);
|
||||
assert!(attrs.contains(&"job_kind"), "missing job_kind attribute");
|
||||
assert!(
|
||||
attrs.contains(&"created_by"),
|
||||
"missing created_by attribute"
|
||||
|
||||
@@ -11,7 +11,7 @@ use windmill_test_utils::*;
|
||||
// Dedicated Worker Protocol Tests (Python)
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg(all(feature = "python", feature = "private"))]
|
||||
mod dedicated_worker_protocol_python {
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
@@ -748,6 +748,7 @@ def main():
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -800,6 +801,7 @@ def main():
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -837,6 +839,7 @@ def main():
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -878,6 +881,7 @@ def main():
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -917,6 +921,7 @@ def main():
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
tag: None,
|
||||
});
|
||||
|
||||
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
|
||||
@@ -985,33 +990,42 @@ async def main(item: str, qty: int, email: str):
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
// WAC requires at least 2 workers (parent + task sub-jobs)
|
||||
let db = &db;
|
||||
in_test_worker(
|
||||
db,
|
||||
async move {
|
||||
let job = Box::pin(
|
||||
RunJob::from(JobPayload::Code(RawCode {
|
||||
language: ScriptLang::Python3,
|
||||
content,
|
||||
..RawCode::default()
|
||||
}))
|
||||
.arg("item", json!("widget"))
|
||||
.arg("qty", json!(5))
|
||||
.arg("email", json!("test@example.com"))
|
||||
.run_until_complete(db, false, port),
|
||||
)
|
||||
.await;
|
||||
// WAC requires at least 2 workers (parent + task sub-jobs).
|
||||
//
|
||||
// Run the heavy `in_test_worker` chain on an isolated OS thread with a
|
||||
// larger stack: the deep nested async chain (test -> in_test_worker ->
|
||||
// run_until_complete -> windmill_queue::push -> ...) composes into one
|
||||
// synchronous poll-stack frame that exceeds the default 2 MB test-thread
|
||||
// stack in debug builds. `Box::pin` at the call site only moves future
|
||||
// *state* to the heap; it can't shrink poll-time stack frames.
|
||||
let db = db.clone();
|
||||
run_in_isolated_thread(move || async move {
|
||||
in_test_worker(
|
||||
&db,
|
||||
async {
|
||||
let job = Box::pin(
|
||||
RunJob::from(JobPayload::Code(RawCode {
|
||||
language: ScriptLang::Python3,
|
||||
content,
|
||||
..RawCode::default()
|
||||
}))
|
||||
.arg("item", json!("widget"))
|
||||
.arg("qty", json!(5))
|
||||
.arg("email", json!("test@example.com"))
|
||||
.run_until_complete(&db, false, port),
|
||||
)
|
||||
.await;
|
||||
|
||||
let result = job.json_result().unwrap();
|
||||
assert_eq!(result["item"], json!("widget"));
|
||||
assert_eq!(result["qty"], json!(5));
|
||||
assert_eq!(result["email"], json!("test@example.com"));
|
||||
assert_eq!(result["greeting"], json!("hello widget x5"));
|
||||
},
|
||||
port,
|
||||
)
|
||||
.await;
|
||||
let result = job.json_result().unwrap();
|
||||
assert_eq!(result["item"], json!("widget"));
|
||||
assert_eq!(result["qty"], json!(5));
|
||||
assert_eq!(result["email"], json!("test@example.com"));
|
||||
assert_eq!(result["greeting"], json!("hello widget x5"));
|
||||
},
|
||||
port,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
//! HTTP-level integration tests for the `restart_flow_at_step` API endpoint.
|
||||
//!
|
||||
//! The other restart tests in `job_payload.rs` exercise the worker by hand-
|
||||
//! constructing `RestartedFrom` chains and calling `push()` directly. These
|
||||
//! tests instead drive the actual HTTP endpoint to lock in the API contract,
|
||||
//! including the validation branches in `resolve_nested_restart` (step lookup,
|
||||
//! parallel rejection, etc.).
|
||||
|
||||
#![cfg(feature = "deno_core")]
|
||||
#![cfg(feature = "enterprise")]
|
||||
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::flows::FlowValue;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", format!("Bearer {}", token))
|
||||
}
|
||||
|
||||
const SUPER_TOKEN: &str = "SECRET_TOKEN";
|
||||
|
||||
/// Happy path: HTTP nested restart targeting a step inside iteration 1 of a
|
||||
/// top-level sequential ForLoop. Verifies the API endpoint accepts a
|
||||
/// `nested_path`, walks the original execution to resolve UUIDs, and the
|
||||
/// resulting job runs successfully.
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_api_restart_at_step_nested_happy(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let first_run = RunJob::from(JobPayload::Flow {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/restart/f/{}",
|
||||
first_run.id
|
||||
)),
|
||||
SUPER_TOKEN,
|
||||
)
|
||||
.json(&json!({
|
||||
"step_id": "a",
|
||||
"branch_or_iteration_n": 1,
|
||||
"nested_path": [{ "step_id": "c" }],
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"expected 201, got {}: {}",
|
||||
resp.status(),
|
||||
resp.text().await.unwrap_or_default()
|
||||
);
|
||||
let new_job_id = resp.text().await?;
|
||||
assert!(!new_job_id.is_empty(), "expected job UUID in response body");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Happy path: top-level (non-nested) restart via HTTP. Same surface as
|
||||
/// `test_restarted_flow_payload` but driven through the actual REST endpoint.
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_api_restart_at_step_top_level_happy(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let first_run = RunJob::from(JobPayload::Flow {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/restart/f/{}",
|
||||
first_run.id
|
||||
)),
|
||||
SUPER_TOKEN,
|
||||
)
|
||||
.json(&json!({ "step_id": "a", "branch_or_iteration_n": 0 }))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rejection: nested step doesn't exist in the original run. The API should
|
||||
/// reject with a 4xx (or surface a clear backend error) rather than silently
|
||||
/// queue an unrunnable job.
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_api_restart_at_step_rejects_unknown_step(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let first_run = RunJob::from(JobPayload::Flow {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/restart/f/{}",
|
||||
first_run.id
|
||||
)),
|
||||
SUPER_TOKEN,
|
||||
)
|
||||
.json(&json!({
|
||||
"step_id": "a",
|
||||
"branch_or_iteration_n": 1,
|
||||
"nested_path": [{ "step_id": "does_not_exist" }],
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"expected error, got {}",
|
||||
resp.status()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rejection: nested restart targets an iteration past the actual count.
|
||||
/// Original ran 3 iterations (index 0..2); requesting `branch_or_iteration_n=5`
|
||||
/// must error.
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_api_restart_at_step_rejects_out_of_range_iteration(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let first_run = RunJob::from(JobPayload::Flow {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/restart/f/{}",
|
||||
first_run.id
|
||||
)),
|
||||
SUPER_TOKEN,
|
||||
)
|
||||
.json(&json!({
|
||||
"step_id": "a",
|
||||
"branch_or_iteration_n": 5,
|
||||
"nested_path": [{ "step_id": "c" }],
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"expected error for out-of-range iteration, got {}: {}",
|
||||
resp.status(),
|
||||
resp.text().await.unwrap_or_default()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rejection: parallel ForLoop ancestor on the nested path. The resolver
|
||||
/// rejects this category outright — `branch_or_iteration_n` only makes sense
|
||||
/// for sequential containers because each iteration runs as a separate
|
||||
/// numbered child.
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_api_restart_at_step_rejects_parallel_loop(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Run a parallel ForLoop with an inner script. We use a `RawFlow` with an
|
||||
// explicit `path` so the API endpoint accepts it as a valid completed job
|
||||
// (the handler requires `runnable_path`).
|
||||
let parallel_flow: FlowValue = serde_json::from_value(json!({
|
||||
"modules": [{
|
||||
"id": "loop",
|
||||
"value": {
|
||||
"type": "forloopflow",
|
||||
"iterator": { "type": "javascript", "expr": "[1, 2]" },
|
||||
"skip_failures": false,
|
||||
"parallel": true,
|
||||
"modules": [{
|
||||
"id": "inner",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"input_transforms": {},
|
||||
"content": "export function main() { return 'ok' }"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}],
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let first_run = RunJob::from(JobPayload::RawFlow {
|
||||
value: parallel_flow,
|
||||
path: Some("u/admin/parallel_test".to_string()),
|
||||
restarted_from: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/restart/f/{}",
|
||||
first_run.id
|
||||
)),
|
||||
SUPER_TOKEN,
|
||||
)
|
||||
.json(&json!({
|
||||
"step_id": "loop",
|
||||
"branch_or_iteration_n": 0,
|
||||
"nested_path": [{ "step_id": "inner" }],
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"expected rejection of parallel-loop nested restart, got {}: {}",
|
||||
resp.status(),
|
||||
resp.text().await.unwrap_or_default()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user