Compare commits
1 Commits
v1.634.3
...
plan-2-scr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78bc6b498c |
8
.github/workflows/backend-check.yml
vendored
8
.github/workflows/backend-check.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
- name: cargo check
|
||||
working-directory: ./backend
|
||||
timeout-minutes: 16
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
- name: cargo check
|
||||
working-directory: ./backend
|
||||
timeout-minutes: 16
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
- name: cargo check
|
||||
working-directory: ./backend
|
||||
timeout-minutes: 16
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
- name: cargo check
|
||||
timeout-minutes: 16
|
||||
working-directory: ./backend
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
|
||||
- name: Substitute EE code
|
||||
shell: bash
|
||||
|
||||
23
.github/workflows/check-org-membership.yml
vendored
23
.github/workflows/check-org-membership.yml
vendored
@@ -4,10 +4,9 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commenter:
|
||||
required: false
|
||||
required: true
|
||||
type: string
|
||||
default: ''
|
||||
description: 'The username to check. Auto-detected from the event context if not provided.'
|
||||
description: 'The username to check for organization membership'
|
||||
organization:
|
||||
required: false
|
||||
type: string
|
||||
@@ -33,27 +32,11 @@ jobs:
|
||||
outputs:
|
||||
is_member: ${{ steps.check-membership.outputs.is_member }}
|
||||
steps:
|
||||
- name: Determine commenter
|
||||
id: determine-commenter
|
||||
run: |
|
||||
COMMENTER="${{ inputs.commenter }}"
|
||||
if [[ -z "$COMMENTER" ]]; then
|
||||
if [[ "${{ github.event_name }}" == "issue_comment" || \
|
||||
"${{ github.event_name }}" == "pull_request_review_comment" ]]; then
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
|
||||
COMMENTER="${{ github.event.review.user.login }}"
|
||||
else
|
||||
COMMENTER="${{ github.event.issue.user.login }}"
|
||||
fi
|
||||
fi
|
||||
echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check organization membership
|
||||
id: check-membership
|
||||
env:
|
||||
ORG_ACCESS_TOKEN: ${{ secrets.access_token }}
|
||||
COMMENTER: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
COMMENTER: ${{ inputs.commenter }}
|
||||
ORG: ${{ inputs.organization }}
|
||||
TRUSTED_BOT: ${{ inputs.trusted_bot }}
|
||||
run: |
|
||||
|
||||
26
.github/workflows/claude-fast.yml
vendored
26
.github/workflows/claude-fast.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
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'))
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
commenter: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
steps:
|
||||
- name: Determine commenter
|
||||
id: determine-commenter
|
||||
run: |
|
||||
# Work out who wrote the comment / review
|
||||
if [[ "${{ github.event_name }}" == "issue_comment" || \
|
||||
"${{ github.event_name }}" == "pull_request_review_comment" ]]; then
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
|
||||
COMMENTER="${{ github.event.review.user.login }}"
|
||||
else
|
||||
COMMENTER="${{ github.event.issue.user.login }}"
|
||||
fi
|
||||
echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT
|
||||
|
||||
check-membership:
|
||||
needs: determine-commenter
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
with:
|
||||
commenter: ${{ needs.determine-commenter.outputs.commenter }}
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
claude-code-action:
|
||||
needs: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
|
||||
26
.github/workflows/claude-plan.yml
vendored
26
.github/workflows/claude-plan.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/plan')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/plan')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/plan')) ||
|
||||
(github.event_name == 'issues' && contains(github.event.issue.body, '/plan'))
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
commenter: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
steps:
|
||||
- name: Determine commenter
|
||||
id: determine-commenter
|
||||
run: |
|
||||
# Work out who wrote the comment / review
|
||||
if [[ "${{ github.event_name }}" == "issue_comment" || \
|
||||
"${{ github.event_name }}" == "pull_request_review_comment" ]]; then
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
|
||||
COMMENTER="${{ github.event.review.user.login }}"
|
||||
else
|
||||
COMMENTER="${{ github.event.issue.user.login }}"
|
||||
fi
|
||||
echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT
|
||||
|
||||
check-membership:
|
||||
needs: determine-commenter
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
with:
|
||||
commenter: ${{ needs.determine-commenter.outputs.commenter }}
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
claude-plan-action:
|
||||
needs: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-4
|
||||
|
||||
34
.github/workflows/claude.yml
vendored
34
.github/workflows/claude.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai')) ||
|
||||
(github.event_name == 'issues' && contains(github.event.issue.body, '/ai'))
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
commenter: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
steps:
|
||||
- name: Determine commenter
|
||||
id: determine-commenter
|
||||
run: |
|
||||
# Work out who wrote the comment / review
|
||||
if [[ "${{ github.event_name }}" == "issue_comment" || \
|
||||
"${{ github.event_name }}" == "pull_request_review_comment" ]]; then
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
|
||||
COMMENTER="${{ github.event.review.user.login }}"
|
||||
else
|
||||
COMMENTER="${{ github.event.issue.user.login }}"
|
||||
fi
|
||||
echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT
|
||||
|
||||
check-membership:
|
||||
needs: determine-commenter
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
with:
|
||||
commenter: ${{ needs.determine-commenter.outputs.commenter }}
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
claude-code-action:
|
||||
needs: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
@@ -56,18 +78,18 @@ jobs:
|
||||
- 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
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev libsasl2-dev libcurl4-openssl-dev
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
|
||||
- name: cargo check
|
||||
working-directory: ./backend
|
||||
timeout-minutes: 16
|
||||
run: |
|
||||
SQLX_OFFLINE=true cargo check --features all_sqlx_features
|
||||
SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh)
|
||||
|
||||
- name: Run Claude PR Action
|
||||
uses: anthropics/claude-code-action@v1
|
||||
@@ -97,7 +119,7 @@ jobs:
|
||||
- Fix all warnings and errors before proceeding
|
||||
|
||||
**Backend Changes:**
|
||||
- Run: \`cargo check --features all_sqlx_features\` in the backend directory
|
||||
- Run: \`cargo check --features $(./all_features_oss.sh)\` in the backend directory
|
||||
- Fix all warnings and errors before proceeding
|
||||
|
||||
**Pull Request Creation:**
|
||||
|
||||
39
.github/workflows/create-docs.yml
vendored
Normal file
39
.github/workflows/create-docs.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') }}
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
with:
|
||||
commenter: ${{ github.event.comment.user.login }}
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
generate-token:
|
||||
needs: check-membership
|
||||
if: ${{ needs.check-membership.outputs.is_member == 'true' }}
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
app_token: ${{ steps.app.outputs.token }}
|
||||
steps:
|
||||
- name: Generate an installation token
|
||||
id: app
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.INTERNAL_APP_ID }}
|
||||
private-key: ${{ secrets.INTERNAL_APP_KEY }}
|
||||
owner: windmill-labs
|
||||
|
||||
trigger-docs:
|
||||
needs: [generate-token, check-membership]
|
||||
if: ${{ needs.check-membership.outputs.is_member == 'true' }}
|
||||
uses: windmill-labs/windmilldocs/.github/workflows/create-docs.yml@main
|
||||
with:
|
||||
pr_number: ${{ github.event.issue.number }}
|
||||
repo: ${{ github.event.repository.name }}
|
||||
comment_text: ${{ github.event.comment.body }}
|
||||
secrets:
|
||||
DOCS_TOKEN: ${{ needs.generate-token.outputs.app_token }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
63
.github/workflows/git-commands.yaml
vendored
63
.github/workflows/git-commands.yaml
vendored
@@ -5,21 +5,8 @@ on:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
if: >-
|
||||
github.event.issue.pull_request && (
|
||||
startsWith(github.event.comment.body, '/updatesqlx') ||
|
||||
startsWith(github.event.comment.body, '/demo') ||
|
||||
startsWith(github.event.comment.body, '/eeref') ||
|
||||
startsWith(github.event.comment.body, '/docs')
|
||||
)
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
update-sqlx:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/updatesqlx')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/updatesqlx')
|
||||
runs-on: ubicloud-standard-8
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -75,17 +62,17 @@ jobs:
|
||||
path: windmill-ee-private
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
|
||||
# Setup Rust toolchain
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# Cache rust dependencies
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
workspaces: "./backend -> target"
|
||||
|
||||
- name: Install xmlsec and gssapi build-time deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config libxml2-dev libssl-dev libkrb5-dev libsasl2-dev libcurl4-openssl-dev mold clang \
|
||||
pkg-config libxml2-dev libssl-dev libkrb5-dev \
|
||||
xmlsec1 libxmlsec1-dev libxmlsec1-openssl
|
||||
|
||||
- name: Run update-sqlx script
|
||||
@@ -134,8 +121,7 @@ jobs:
|
||||
})
|
||||
|
||||
demo:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/demo')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/demo')
|
||||
runs-on: ubicloud-standard-2
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -214,8 +200,7 @@ jobs:
|
||||
fi
|
||||
|
||||
update-ee-ref:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/eeref')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/eeref')
|
||||
runs-on: ubicloud-standard-2
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -298,35 +283,3 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
body: 'Successfully updated ee-repo-ref.txt'
|
||||
})
|
||||
|
||||
update-docs:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/docs')
|
||||
runs-on: ubicloud-standard-2
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: app
|
||||
with:
|
||||
app-id: ${{ vars.INTERNAL_APP_ID }}
|
||||
private-key: ${{ secrets.INTERNAL_APP_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: |
|
||||
windmilldocs
|
||||
|
||||
- name: Trigger docs update
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app.outputs.token }}
|
||||
COMMENT_TEXT: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
jq -n \
|
||||
--argjson pr_number ${{ github.event.issue.number }} \
|
||||
--arg repo "${{ github.event.repository.name }}" \
|
||||
--arg comment "$COMMENT_TEXT" \
|
||||
'{event_type: "create-docs", client_payload: {pr_number: $pr_number, repo: $repo, comment_text: $comment}}' | \
|
||||
gh api repos/windmill-labs/windmilldocs/dispatches \
|
||||
--method POST \
|
||||
--input -
|
||||
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.93.0
|
||||
toolchain: 1.90.0
|
||||
|
||||
- name: Substitute EE code
|
||||
shell: bash
|
||||
|
||||
26
.github/workflows/spawn-ephemeral-backend.yml
vendored
26
.github/workflows/spawn-ephemeral-backend.yml
vendored
@@ -13,16 +13,38 @@ on:
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/spawnbackend')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/spawnbackend'))
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
commenter: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
steps:
|
||||
- name: Determine commenter
|
||||
id: determine-commenter
|
||||
run: |
|
||||
# Work out who wrote the comment / review
|
||||
if [[ "${{ github.event_name }}" == "issue_comment" || \
|
||||
"${{ github.event_name }}" == "pull_request_review_comment" ]]; then
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
|
||||
COMMENTER="${{ github.event.review.user.login }}"
|
||||
else
|
||||
COMMENTER="${{ github.event.issue.user.login }}"
|
||||
fi
|
||||
echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT
|
||||
|
||||
check-membership:
|
||||
needs: determine-commenter
|
||||
uses: ./.github/workflows/check-org-membership.yml
|
||||
with:
|
||||
commenter: ${{ needs.determine-commenter.outputs.commenter }}
|
||||
secrets:
|
||||
access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
|
||||
spawn-backend:
|
||||
needs: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
# Only run on PR comments that contain /spawn-backend, or manual dispatch
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,4 +19,3 @@ backend/target
|
||||
frontend/node_modules
|
||||
typescript-client/node_modules
|
||||
frontend/.svelte-kit
|
||||
backend/chrome_profiler.json
|
||||
|
||||
127
CHANGELOG.md
127
CHANGELOG.md
@@ -1,132 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.634.3](https://github.com/windmill-labs/windmill/compare/v1.634.2...v1.634.3) (2026-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix incorrect oauth base url refresh error ([b3a1629](https://github.com/windmill-labs/windmill/commit/b3a1629e56217605d059d1dceca43c9999a58592))
|
||||
|
||||
## [1.634.2](https://github.com/windmill-labs/windmill/compare/v1.634.1...v1.634.2) (2026-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix hub schedule not set at on-boarding ([beeb19d](https://github.com/windmill-labs/windmill/commit/beeb19db04e8e9059c63007d2402981c3e81f1e2))
|
||||
|
||||
## [1.634.1](https://github.com/windmill-labs/windmill/compare/v1.634.0...v1.634.1) (2026-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* conditionally skip relock on dep job ([#7860](https://github.com/windmill-labs/windmill/issues/7860)) ([d6c72df](https://github.com/windmill-labs/windmill/commit/d6c72df99a0a500bdd925fcdcba8abd8bbe537f5))
|
||||
* improve on-boarding experience ([4e38a4f](https://github.com/windmill-labs/windmill/commit/4e38a4f1083d880b0814e336d5e27cb40187fc28))
|
||||
|
||||
## [1.634.0](https://github.com/windmill-labs/windmill/compare/v1.633.1...v1.634.0) (2026-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add force_sandboxing global setting and #sandbox bash annotation ([#7816](https://github.com/windmill-labs/windmill/issues/7816)) ([2646629](https://github.com/windmill-labs/windmill/commit/2646629194f260d0be3a809be421bbab1307f927))
|
||||
* support for datatables in App Db studio ([#7930](https://github.com/windmill-labs/windmill/issues/7930)) ([6cee34a](https://github.com/windmill-labs/windmill/commit/6cee34a81da389faebb1474957a3989c4aadb00f))
|
||||
|
||||
## [1.633.1](https://github.com/windmill-labs/windmill/compare/v1.633.0...v1.633.1) (2026-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add private registries support for RUST + java home nit ([e2c28e4](https://github.com/windmill-labs/windmill/commit/e2c28e42dbda0f7bf119efc8d587da0d30636a44))
|
||||
|
||||
## [1.633.0](https://github.com/windmill-labs/windmill/compare/v1.632.0...v1.633.0) (2026-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* /health endpoints ([#7727](https://github.com/windmill-labs/windmill/issues/7727)) ([7df4aa4](https://github.com/windmill-labs/windmill/commit/7df4aa4fec021fc728950fc10db4d0401a205087))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* save deployment msg in CE ([#7923](https://github.com/windmill-labs/windmill/issues/7923)) ([e9be616](https://github.com/windmill-labs/windmill/commit/e9be616d3c079a0c6fd98733c66560f2cc1ee40d))
|
||||
|
||||
## [1.632.0](https://github.com/windmill-labs/windmill/compare/v1.631.2...v1.632.0) (2026-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ai:** add AWS bedrock session token support ([#7908](https://github.com/windmill-labs/windmill/issues/7908)) ([d95e4db](https://github.com/windmill-labs/windmill/commit/d95e4db8f31e70a645a5f41e287557933b257db8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add kafka kerberos runtime packages ([#7918](https://github.com/windmill-labs/windmill/issues/7918)) ([22f22c2](https://github.com/windmill-labs/windmill/commit/22f22c26612b904c2b82b415ec9337213ef593c3))
|
||||
* **frontend:** redesign instance settings ([#7916](https://github.com/windmill-labs/windmill/issues/7916)) ([dd419ad](https://github.com/windmill-labs/windmill/commit/dd419ade94a992073dfd1a979bfeb8a5fadfb051))
|
||||
* hash long dedicated worker tags ([#7914](https://github.com/windmill-labs/windmill/issues/7914)) ([aaa1b92](https://github.com/windmill-labs/windmill/commit/aaa1b92300bdc0794de5356ef6750bab5d8d81a0))
|
||||
|
||||
## [1.631.2](https://github.com/windmill-labs/windmill/compare/v1.631.1...v1.631.2) (2026-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** revert CloseButton refactor that broke tag removal in MultiSelect ([#7909](https://github.com/windmill-labs/windmill/issues/7909)) ([b11d6ed](https://github.com/windmill-labs/windmill/commit/b11d6ed7940faddfe74a22b25bcb132527cbcec8))
|
||||
* nix flake libz.so for deno_core ([#7905](https://github.com/windmill-labs/windmill/issues/7905)) ([900c76c](https://github.com/windmill-labs/windmill/commit/900c76ccad09f58fc6adcfa8151db780249617f6))
|
||||
* strip unsupported schema fields for Google AI ([#7894](https://github.com/windmill-labs/windmill/issues/7894)) ([5effb87](https://github.com/windmill-labs/windmill/commit/5effb87a36793e20d17e678619091ef458fe8f0a)), closes [#7759](https://github.com/windmill-labs/windmill/issues/7759)
|
||||
|
||||
## [1.631.1](https://github.com/windmill-labs/windmill/compare/v1.631.0...v1.631.1) (2026-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add kafka-gssapi support to ee builds ([2815cfa](https://github.com/windmill-labs/windmill/commit/2815cfae1a5eefb2c553e893dc926e62ad1df528))
|
||||
|
||||
## [1.631.0](https://github.com/windmill-labs/windmill/compare/v1.630.2...v1.631.0) (2026-02-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ai:** support 1M context window for Anthropic resources ([#7891](https://github.com/windmill-labs/windmill/issues/7891)) ([f22eb96](https://github.com/windmill-labs/windmill/commit/f22eb964e47defe9922ecdb6ab471dd4ca267952))
|
||||
* **uv:** index resolve strategy ([#7885](https://github.com/windmill-labs/windmill/issues/7885)) ([097d928](https://github.com/windmill-labs/windmill/commit/097d9288c58076882f1991e2fb33e4441fe332d3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** improve time picker ([#7893](https://github.com/windmill-labs/windmill/issues/7893)) ([31bfccc](https://github.com/windmill-labs/windmill/commit/31bfccc74588af10fc11fbbb0bc4833d65ff6421))
|
||||
* otel gracefully handle no native ts ([92c6018](https://github.com/windmill-labs/windmill/commit/92c601860f1e1211aa34838cd08900ba7334a20c))
|
||||
* waitJob getJob and streamJob in raw apps ([#7901](https://github.com/windmill-labs/windmill/issues/7901)) ([754b48c](https://github.com/windmill-labs/windmill/commit/754b48cb898dffe196339cea1c1598c9e1765cdc))
|
||||
* worker do not apply migrations anymore but wait for servers to do so ([7eb239f](https://github.com/windmill-labs/windmill/commit/7eb239f1e2eb1b71234a8d4265c7c5813e5861ae))
|
||||
|
||||
## [1.630.2](https://github.com/windmill-labs/windmill/compare/v1.630.1...v1.630.2) (2026-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bump rust version from 1.90.0 to 1.93.0 ([1a109a7](https://github.com/windmill-labs/windmill/commit/1a109a7797d1a50a0d85f3fff236d707b2cfb81d))
|
||||
|
||||
## [1.630.1](https://github.com/windmill-labs/windmill/compare/v1.630.0...v1.630.1) (2026-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enforce self-approval check on flow resume owner endpoint ([#7886](https://github.com/windmill-labs/windmill/issues/7886)) ([7147dde](https://github.com/windmill-labs/windmill/commit/7147dde5118d7b3a179e4b310c74b148838b5afe))
|
||||
|
||||
## [1.630.0](https://github.com/windmill-labs/windmill/compare/v1.629.1...v1.630.0) (2026-02-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add workspace search and runnable details tools to AI chat modes ([#7874](https://github.com/windmill-labs/windmill/issues/7874)) ([a7e269f](https://github.com/windmill-labs/windmill/commit/a7e269f9f3c82db0d7e6a70e174ac19d3df730d2))
|
||||
* **aiagent:** add prompt caching for Anthropic models ([#7878](https://github.com/windmill-labs/windmill/issues/7878)) ([6272cd1](https://github.com/windmill-labs/windmill/commit/6272cd17a4f1300e22e7f0ae27b1a57571deb203))
|
||||
* download encrypted usage ([#7804](https://github.com/windmill-labs/windmill/issues/7804)) ([8363ff1](https://github.com/windmill-labs/windmill/commit/8363ff1eeef06f284e6d165fbf2dfb190ead573d))
|
||||
* **mcp:** add endpoint tools for scripts, flows, apps, and jobs ([#7859](https://github.com/windmill-labs/windmill/issues/7859)) ([03eb16a](https://github.com/windmill-labs/windmill/commit/03eb16a7c6c3cd9411840814940d09e22ce23305))
|
||||
* restriction rulesets for workspaces ([#7879](https://github.com/windmill-labs/windmill/issues/7879)) ([2851b6b](https://github.com/windmill-labs/windmill/commit/2851b6b7caac4a55f5202ace82aba68fd157c52a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** correct early return with stream + prevent delta miss ([#7872](https://github.com/windmill-labs/windmill/issues/7872)) ([1150eec](https://github.com/windmill-labs/windmill/commit/1150eec7571d5828d10b295cb61cca8edfbdffe0))
|
||||
* gate Permissions import behind #[cfg(unix)] for Windows build ([cf596f3](https://github.com/windmill-labs/windmill/commit/cf596f370ae7cc232ca63f4752d7727a74cd449b))
|
||||
* retry js eval up to 3 times on timeout from slow DB ([#7890](https://github.com/windmill-labs/windmill/issues/7890)) ([4c87e7a](https://github.com/windmill-labs/windmill/commit/4c87e7ac2e09ec83cfb998a1cebcb9b9c5ef8027))
|
||||
|
||||
## [1.629.1](https://github.com/windmill-labs/windmill/compare/v1.629.0...v1.629.1) (2026-02-10)
|
||||
|
||||
|
||||
|
||||
21
CLAUDE.md
21
CLAUDE.md
@@ -17,32 +17,19 @@ When implementing new features in Windmill, follow these best practices:
|
||||
|
||||
## Language-Specific Guides
|
||||
|
||||
- Backend (Rust): see `backend/CLAUDE.md` and the `rust-backend` skill: `.claude/skills/rust-backend/SKILL.md`
|
||||
- Frontend (Svelte 5): see `frontend/CLAUDE.md` and the `svelte-frontend` skill: `.claude/skills/svelte-frontend/SKILL.md`
|
||||
|
||||
## Code Validation (MUST DO)
|
||||
|
||||
After making code changes, you MUST run the appropriate checks and fix all errors before considering the work done:
|
||||
|
||||
- **Backend**: Run `cargo check` from the `backend/` directory. Only enable the feature flags needed for the code you changed — check `backend/Cargo.toml` `[features]` section to identify which flags gate the crates/modules you modified. For example: `cargo check --features enterprise,parquet` if you only touched enterprise and parquet code.
|
||||
- **Frontend**: Run `npm run check` from the `frontend/` directory.
|
||||
- Backend (Rust): @backend/rust-best-practices.mdc + @backend/summarized_schema.txt
|
||||
- Frontend (Svelte 5): @frontend/svelte5-best-practices.mdc
|
||||
|
||||
## Querying the Database
|
||||
|
||||
`backend/summarized_schema.txt` provides a compact overview of all tables, columns, types, ENUMs, and foreign keys. Use it to quickly understand the data model and relationships. Note: this file is a simplified summary — it omits indexes, constraints details, and other metadata.
|
||||
|
||||
For exact table definitions (indexes, constraints, column defaults, etc.), query the database directly:
|
||||
To query the database directly, use psql with the following connection string:
|
||||
|
||||
```bash
|
||||
psql postgres://postgres:changeme@localhost:5432/windmill
|
||||
```
|
||||
|
||||
Useful psql commands:
|
||||
- `\d <table_name>` — full table definition with indexes and constraints
|
||||
- `\di <table_name>*` — list indexes for a table
|
||||
- `\d+ <table_name>` — extended table info including storage and descriptions
|
||||
This can be helpful for:
|
||||
|
||||
This is also helpful for:
|
||||
- Inspecting database state during development
|
||||
- Testing queries before implementing them in Rust
|
||||
- Debugging data-related issues
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.93-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.90-slim-bookworm
|
||||
|
||||
FROM debian:bookworm-slim AS nsjail
|
||||
|
||||
@@ -132,7 +132,6 @@ ARG WITH_POWERSHELL=true
|
||||
ARG WITH_KUBECTL=true
|
||||
ARG WITH_HELM=true
|
||||
ARG WITH_GIT=true
|
||||
ARG features=""
|
||||
|
||||
# To change latest stable version:
|
||||
# 1. Change placeholder in instanceSettings.ts
|
||||
@@ -150,8 +149,7 @@ ENV PATH /usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH
|
||||
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini \
|
||||
&& if echo "$features" | grep -q "ee"; then apt-get install -y --no-install-recommends libsasl2-modules-gssapi-mit krb5-user; fi \
|
||||
&& apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini libsasl2-modules-gssapi-mit \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Scripts are turned into sharable UIs automatically, and can be composed together
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.windmill.dev">Try it</a> - <a href="https://www.windmill.dev/">Website</a> - <a href="https://www.windmill.dev/docs/intro/">Docs</a> - <a href="https://discord.gg/V7PM2YHsPB">Discord</a> - <a href="https://hub.windmill.dev">Hub</a> - <a href="https://www.windmill.dev/docs/misc/contributing">Contributor's guide</a>
|
||||
<a href="https://app.windmill.dev">Try it</a> - <a href="https://www.windmill.dev/docs/intro/">Docs</a> - <a href="https://discord.gg/V7PM2YHsPB">Discord</a> - <a href="https://hub.windmill.dev">Hub</a> - <a href="https://www.windmill.dev/docs/misc/contributing">Contributor's guide</a>
|
||||
</p>
|
||||
|
||||
# Windmill - Developer platform for APIs, background jobs, workflows and UIs
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "12c3101e02f197ad731b66f539fe677ad25520fcc9c5a2378a293122956bed4c"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE workspace_settings\n SET error_handler = '{\"path\": \"script/f/test/error_handler\", \"extra_args\": {\"notify\": true}, \"muted_on_cancel\": true, \"muted_on_user_path\": false}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "18d1a6ac94f2c87d5ea8c48a228f061135be4065dca33fbc429d8b0186c5ccb3"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM resource_type WHERE workspace_id = 'admins' AND name = $1 AND schema IS NOT DISTINCT FROM $2 AND description IS NOT DISTINCT FROM $3)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1ea97f9085ec018f779e77e0fdbda3d4ecd67b3fbee9a58228ef577f846607ae"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT (error_handler->>'muted_on_cancel')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "25bf3956b4365a4d4f96368bcc6e33817ae284ab26c195eee53d988d74263e86"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "28b8264a427e8c19be823d2b8e40736a7b7675780672b503d91737cd0c617657"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT logs FROM job_logs WHERE created_at > $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "logs",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3cc7ecab48c379cd845b22012ef1fe1573fc332c9f1c44960084f3784ddb3f54"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM job_result_stream_v2\n WHERE job_id NOT IN (SELECT id FROM v2_job_queue)\n AND job_id NOT IN (\n SELECT id FROM v2_job_completed\n WHERE completed_at > NOW() - INTERVAL '60 seconds'\n )\n RETURNING job_id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "454a611a5a162b2ace137c139bd5383bc7fe142c515dac3edd47991839485e51"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT j.id AS \"id!\", COALESCE(s.flow_status, s.workflow_as_code_status) as flow_status, q.suspend AS \"suspend!\", j.runnable_path as script_path, j.permissioned_as_email as email\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_status s USING (id)\n WHERE j.id = $1\n ",
|
||||
"query": "\n SELECT j.id AS \"id!\", COALESCE(s.flow_status, s.workflow_as_code_status) as flow_status, q.suspend AS \"suspend!\", j.runnable_path as script_path\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_status s USING (id)\n WHERE j.id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,11 +22,6 @@
|
||||
"ordinal": 3,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -38,9 +33,8 @@
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5c6e158aee5db3c4bf41a0aedfa5d6c73f5e04ebfd8703dc631bc49f2e797dd4"
|
||||
"hash": "485dc289a61a06595acae28d4968f3b6b2aeb6a8aee863dc999d6c8d58397814"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource_type (workspace_id, name, schema, description, edited_at)\n VALUES ('admins', $1, $2, $3, now())\n ON CONFLICT (workspace_id, name) DO UPDATE\n SET schema = EXCLUDED.schema, description = EXCLUDED.description, edited_at = now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4b93550c7836fd3643180ade3548faa875e471d3f9ca37fc669f359e7a1818bb"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n workspace_id,\n name,\n rules as \"rules: ProtectionRules\",\n bypass_groups,\n bypass_users\n FROM workspace_protection_rule\n WHERE workspace_id = $1\n ORDER BY name\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "rules: ProtectionRules",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "bypass_groups",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "bypass_users",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4cfb35e423a75ca2701f03d5a30a7c0778af5e548254f3e0f29004d7f2058eef"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', $1, 'f/test/success_script', 'export function main() { return \"ok\"; }', 'deno', 'script', 'test-user', '{}', '', '', '')\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "51f18c817d4014e847486f5a8dbf205ffa0e53d5bd3fdb97777e988389b1d69e"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id, imported_lockfile_hash)\n VALUES ($1, $2, $3::text::IMPORTER_KIND, $4, $5, $6)\n ON CONFLICT (workspace_id, importer_node_id, importer_kind, importer_path, imported_path)\n DO UPDATE SET imported_lockfile_hash = EXCLUDED.imported_lockfile_hash",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "55a7460901c8ccdb478a2b7a3d4bd9d367838fe80844b5a14d2ef66452f36297"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO group_ (workspace_id, name, summary, extra_perms)\n VALUES ('test-workspace', 'error_handler', 'The group the error handler acts on behalf of', '{\"u/test-user\": true}')\n ON CONFLICT DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5f5bdd667f699a5a1c05e07d805a97afb06252541d06b4785063819c3893b674"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT hash FROM script WHERE path = 'f/rel/leaf_2' AND workspace_id = 'test-workspace' AND archived = false ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5f6d0d1b24693db22230ec8322b3174ecc195242a1205131f5d0d32148c0280a"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE workspace_protection_rule\n SET rules = $1, bypass_groups = $2, bypass_users = $3\n WHERE workspace_id = $4 AND name = $5\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"TextArray",
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "65b12bed9438900518b20dc268d71a2dba6ec66aee2971faef76b6ed56a05b6f"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview')) AS \"is_flow_level!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview') THEN ji.id\n ELSE ji.parent_job\n END\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
|
||||
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n (ji.kind IN ('flow', 'flowpreview')) AS \"is_flow_level!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview') THEN ji.id\n ELSE ji.parent_job\n END\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,11 +25,6 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_flow_level!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -44,9 +39,8 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1a0ab65bbf2751f702fc696c1e32a7dd9524cdd806be1ad8e9ab88d4c88d3f82"
|
||||
"hash": "66e66da2ed6eace5d7ec2a41a7b11ae255f5dc212d1ff41c2905b303c8c13b18"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH ins AS (\n INSERT INTO workspace_dependencies(name, workspace_id, content, language, description)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id\n ), lock_ins AS (\n INSERT INTO lock_hash (workspace_id, path, lockfile_hash)\n VALUES ($2, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET lockfile_hash = $7\n )\n SELECT id FROM ins\n ",
|
||||
"query": "\n INSERT INTO workspace_dependencies(name, workspace_id, content, language, description)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -46,14 +46,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Int8"
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8acc8c47123af84d2308042dcd34d04b941e928c7621e66d3bdc510c335f8c20"
|
||||
"hash": "6c10c38a5d5e560d8d76159ce0ae118cf7191b06d72285e71256dd6d70c19451"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted)\n VALUES ('test-workspace', $1, 'f/test/muted_failing_script', 'export function main() { throw new Error(\"fail\"); }', 'deno', 'script', 'test-user', '{}', '', '', '', true)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "782c259ccdf71c5e2fa8dcb27515e7c5ff83009774dc15095ed0dbcb0ecdbdde"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE workspace_settings\n SET error_handler = NULL\n WHERE workspace_id = 'test-workspace'\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "79648f03eb50385183c34d57bda1ac984cf6502609df9ee51b7d684b585b8447"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM workspace_protection_rule WHERE workspace_id = $1 AND name = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7ddc2c3f1ce74f9b7acdd51b94431386e7782c52251cf094e06ddb198a958b43"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 3333333333, 'f/test/error_handler', 'export function main() { return \"handled\"; }', 'deno', 'script', 'test-user', '{}', '', '', '')\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7f98c013682aca6d269ef6ef754a7247b6e2dd4958d0968a3f2eb49cc320e877"
|
||||
}
|
||||
15
backend/.sqlx/query-80864158f61adaad8df934acc54ba523c9f17d106298d8781885134d28553d36.json
generated
Normal file
15
backend/.sqlx/query-80864158f61adaad8df934acc54ba523c9f17d106298d8781885134d28553d36.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts \n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2 THEN\n CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "80864158f61adaad8df934acc54ba523c9f17d106298d8781885134d28553d36"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n COUNT(*) > 0\n AND BOOL_AND(\n CASE\n -- For dependencies/: use IS NOT DISTINCT FROM (NULL = NULL is true for legacy)\n -- And the reason for this, is that for every script we also add dependency on default workspace dependencies by default\n -- that default/unnamed workspace dependencies may not exist, but we still do this.\n -- it is needed for windmill to know what to redeploy when default workspace dependencies are being added\n\n -- naturally for non-existant entries we have no hash of it\n -- so if we compared hash with '=' (instead of IS NOT DISTINCT FROM), it would give false on NULL = NULL,\n -- which would mean that this entire query returns false, which means relock skip cannot happen\n\n -- The solution is to say if both: referenced and current hash are NULLs we treat it as true, so it becomes no longer a blocker from skip.\n --\n -- It is backed up by the fact that server is responsible for deploying new workspace dependencies\n -- so if one was to deploy a new wdeps, server would assign it new hash, and this expression would be invalid and would not approve skip\n WHEN dm.imported_path LIKE 'dependencies/%'\n THEN dm.imported_lockfile_hash IS NOT DISTINCT FROM lh.lockfile_hash\n\n -- For scripts: use = with COALESCE (NULL = NULL becomes false)\n -- unlike w deps, we can't do IS NOT DISTINCT FROM here\n -- the reason is that scripts deployments are issued by other workers instead of the server\n -- which would mean that there is no guarantee that new d job will also write it's lock's hash to the `lock_hash`\n -- which could lead to false positives\n ELSE COALESCE(dm.imported_lockfile_hash = lh.lockfile_hash, false)\n END\n )\n FROM dependency_map dm\n LEFT JOIN lock_hash lh\n ON lh.workspace_id = dm.workspace_id\n AND lh.path = dm.imported_path\n WHERE dm.workspace_id = $1\n AND dm.importer_path = $2\n AND dm.importer_node_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9ca267c5a88f0cf3a4821e7514fe585131a42136c522a0fdde47c63cc0c6bc5a"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9f07510019ebe6f0c5fa17bf31c2d14755474cba82b3b388a47585a8bb325b1a"
|
||||
}
|
||||
20
backend/.sqlx/query-a3e75f0309be42aca0fd74834f34b3f18dbb388bd8b9bc88b99aebedae9c3fec.json
generated
Normal file
20
backend/.sqlx/query-a3e75f0309be42aca0fd74834f34b3f18dbb388bd8b9bc88b99aebedae9c3fec.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM job_result_stream_v2 WHERE job_id NOT IN (SELECT id FROM v2_job_queue) RETURNING job_id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a3e75f0309be42aca0fd74834f34b3f18dbb388bd8b9bc88b99aebedae9c3fec"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return, \n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, \n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, \n flow.tag, \n flow.dedicated_worker, \n flow.on_behalf_of_email, \n flow.edited_by\n FROM \n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE \n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -62,5 +62,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087"
|
||||
"hash": "a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM workspace_protection_rule WHERE workspace_id = $1 AND name = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b1b26cb02dcc0748c63411c933e5e6bdfe82466134ba28408a530e22c66656de"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO group_ (workspace_id, name, summary, extra_perms)\n VALUES ('test-workspace', 'error_handler', 'Error handler group', '{\"u/test-user\": true}')\n ON CONFLICT DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ba0c3dad5d2a77922821c9c6d58e06a65e6666e197f3e59b880470df35c27bec"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE workspace_settings\n SET error_handler = '{\"path\": \"script/f/test/error_handler\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bb4cde84157126f29680cb36075848e7cde2ab7c6b7f0861becd4c0b8c330a8d"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM worker_ping WHERE ping_at > now() - interval '5 minutes'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bbf166552ba15ce7cd76f812cdf7223414fa5c8a4860f3fe829d649baa1c5465"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT lock FROM script WHERE path = $1 AND workspace_id = $2 AND lock IS NOT NULL\n AND deleted = false ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "lock",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "cbbe20745c2b743acc814e8e77c0e4e154b511af676e085bbe88e72e7d14aee3"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 5555555555, 'f/test/error_handler', 'export function main() { return \"handled\"; }', 'deno', 'script', 'test-user', '{}', '', '', '')\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cdc594b30855e9384481f561b2c16200d4e6dacb1eb274513c8f1795235dfe20"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', $1, 'f/test/failing_script', $2, 'deno', 'script', 'test-user', '{}', 'Failing test script', 'A script that always fails', '')\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d2a9a9af4d00c1c05276c14e6f32044437800a4b0c60aa364ab2f362a4fd9a2c"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT error_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d5f1a2bfbac206a33b991d58f9ff4b8e8e4a3840ea1c65d409576379dede41ed"
|
||||
}
|
||||
16
backend/.sqlx/query-d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8.json
generated
Normal file
16
backend/.sqlx/query-d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO workspace_protection_rule (workspace_id, name, rules, bypass_groups, bypass_users)\n VALUES ($1, $2, $3, $4, $5)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"TextArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dbf2982f43577999dec0c488dfb67f56e5bd0e5fbf8da5132f6fc5a282d2b0e1"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e004ebd5b5532a4b85984a62f8ad48a81aa3460c1ca07701f386135d72cdecf5"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts\n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2 THEN\n CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e00f7877d7bd33cf81e7bf48b1a37a507ab0a8f8b4bd7a38cf44d181f8f2d940"
|
||||
}
|
||||
18
backend/.sqlx/query-e32d6c6ae4e0d824c4cf19128182d67f36d1fd87fe4cf4f002e18367088c497c.json
generated
Normal file
18
backend/.sqlx/query-e32d6c6ae4e0d824c4cf19128182d67f36d1fd87fe4cf4f002e18367088c497c.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id)\n VALUES ($1, $2, $3::text::IMPORTER_KIND, $4, $5) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e32d6c6ae4e0d824c4cf19128182d67f36d1fd87fe4cf4f002e18367088c497c"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND permissioned_as_email = 'error_handler@windmill.dev'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e4c9f3bca6bd636c3ee31fc643182ed5ca48a45da895fb992d820682f5169f7c"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM dependency_map\n WHERE workspace_id = $1\n AND importer_path = $2\n AND importer_kind = $3::text::IMPORTER_KIND\n AND importer_node_id = $4\n AND imported_path = $5\n AND imported_lockfile_hash IS NOT DISTINCT FROM $6 -- we don't want to delete other entries with other lockfile hash.\n ",
|
||||
"query": "\n DELETE FROM dependency_map\n WHERE workspace_id = $1\n AND importer_path = $2\n AND importer_kind = $3::text::IMPORTER_KIND\n AND importer_node_id = $4\n AND imported_path = $5\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -9,11 +9,10 @@
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c8dee3bd1ce03d8ee3c05345a0bdd5bdbd75396fe5bc24fd2d42edc79261169c"
|
||||
"hash": "ead84a63cb965e36155605434c9e3670e50344f39b0d61eef8e60c2cdb57ae1f"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH update_lock AS (\n UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3\n )\n INSERT INTO lock_hash (workspace_id, path, lockfile_hash)\n VALUES ($3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET lockfile_hash = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f8c08997d7fd0158b4d3cc093fb0a2aadd4473e98873d5a7873f4fa944b99d58"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n worker_group,\n wm_version\n FROM worker_ping\n WHERE ping_at > now() - interval '5 minutes'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "worker_group",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "wm_version",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f912b91c940900b9961920ac0f71b7ba1e8cd50ce90c0c17336e9dd9c684f348"
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n id,\n runnable_path,\n permissioned_as_email,\n parent_job\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND permissioned_as_email = 'error_handler@windmill.dev'\n ORDER BY created_at DESC\n LIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "runnable_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "permissioned_as_email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "parent_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "fdf0a16a72bc2eca2025d64750e2e6a7e7ff90b5ad4b42e6aab53992b447a20e"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 1111111111, 'f/test/error_handler', $1, 'deno', 'script', 'test-user', '{}', 'Error handler script', 'Handles failed job completions', '')\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fe17aff543f28cf4d6d09ea7c4c7d18ff6a95a8402468131f5a5f33df91c59e4"
|
||||
}
|
||||
@@ -37,40 +37,14 @@ Windmill uses a workspace-based architecture with multiple crates:
|
||||
- Update database schema with migration if necessary
|
||||
- Use `sqlx` for database operations with prepared statements
|
||||
- Use transactions for multi-step operations
|
||||
- To apply pending migrations: `sqlx migrate run` (never manually run .sql files)
|
||||
- **Never use `SQLX_OFFLINE=true`** — a live database is always available for compilation
|
||||
- After all code changes are done, run `./update-sqlx` to regenerate the offline query cache
|
||||
|
||||
## Enterprise Features
|
||||
|
||||
- Enterprise files use the `*_ee.rs` suffix
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory at `../../windmill-ee-private`), symlinked into each crate's `src/`
|
||||
- You can and should modify `windmill-ee-private` directly when needed (e.g., when creating new crates that need EE code, mirror the package structure there)
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory), symlinked into each crate's `src/`
|
||||
- Use feature flags: `#[cfg(feature = "enterprise")]`
|
||||
- Isolate enterprise code in separate modules
|
||||
|
||||
## Code Validation (MUST DO)
|
||||
|
||||
After making backend changes, you MUST run `cargo check` and fix all errors and warnings before considering the work done.
|
||||
|
||||
Only enable the feature flags relevant to your changes — do NOT use `all_sqlx_features` as it compiles the entire codebase and is very slow. Check the `[features]` section in `Cargo.toml` to identify which flags gate the crates/modules you modified.
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
# Changed core code (no feature-gated modules)
|
||||
cargo check
|
||||
|
||||
# Changed code behind the enterprise feature
|
||||
cargo check --features enterprise
|
||||
|
||||
# Changed kafka trigger code
|
||||
cargo check --features kafka
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
||||
- **Never push directly to main** — always create a branch and open a pull request
|
||||
|
||||
## Testing
|
||||
|
||||
- Write unit tests for core functionality
|
||||
|
||||
803
backend/Cargo.lock
generated
803
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.634.3"
|
||||
version = "1.629.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ resolver = "2"
|
||||
members = [
|
||||
"./windmill-api",
|
||||
"./windmill-api-scripts",
|
||||
"./windmill-api-flows",
|
||||
"./windmill-api-users",
|
||||
"./windmill-api-workspaces",
|
||||
"./windmill-api-groups",
|
||||
@@ -27,19 +26,7 @@ members = [
|
||||
"./windmill-trigger-gcp",
|
||||
"./windmill-trigger-http",
|
||||
"./windmill-native-triggers",
|
||||
"./windmill-alerting",
|
||||
"./windmill-api-agent-workers",
|
||||
"./windmill-api-assets",
|
||||
"./windmill-api-configs",
|
||||
"./windmill-api-debug",
|
||||
"./windmill-api-embeddings",
|
||||
"./windmill-api-flow-conversations",
|
||||
"./windmill-api-inputs",
|
||||
"./windmill-api-npm-proxy",
|
||||
"./windmill-api-openapi",
|
||||
"./windmill-api-schedule",
|
||||
"./windmill-api-settings",
|
||||
"./windmill-api-workers",
|
||||
"./windmill-store",
|
||||
"./windmill-queue",
|
||||
"./windmill-worker",
|
||||
@@ -67,14 +54,12 @@ members = [
|
||||
"./parsers/windmill-parser-py",
|
||||
"./parsers/windmill-parser-py-imports",
|
||||
"./parsers/windmill-sql-datatype-parser-wasm",
|
||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
|
||||
"./windmill-test-utils",
|
||||
"./windmill-api-integration-tests",
|
||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu"
|
||||
]
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.634.3"
|
||||
version = "1.629.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -95,9 +80,9 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private", "windmill-test-utils/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server", "dep:windmill-api-agent-workers", "windmill-test-utils/agent_worker_server"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-api-agent-workers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
private = ["windmill-api/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
local_reports = ["windmill-common/local_reports"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
stripe = ["windmill-api/stripe"]
|
||||
@@ -112,7 +97,7 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "windmill-common/tantivy", "enterprise", "parquet"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:windmill-runtime-nativets", "windmill-test-utils/deno_core"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:windmill-runtime-nativets"]
|
||||
deno_core_mac = ["deno_core", "windmill-worker/libffi_mac"]
|
||||
kafka = ["windmill-api/kafka"]
|
||||
kafka-gssapi = ["windmill-api/kafka-gssapi"]
|
||||
@@ -129,7 +114,7 @@ native_trigger = ["windmill-api/native_trigger"]
|
||||
sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"]
|
||||
gcp_trigger = ["windmill-api/gcp_trigger"]
|
||||
smtp = ["windmill-api/smtp", "windmill-common/smtp", "windmill-queue/smtp"]
|
||||
license = ["windmill-api/license", "windmill-api-settings/license"]
|
||||
license = ["windmill-api/license"]
|
||||
oauth2 = ["windmill-api/oauth2"]
|
||||
zip = ["windmill-api/zip"]
|
||||
static_frontend = ["windmill-api/static_frontend"]
|
||||
@@ -137,7 +122,7 @@ scoped_cache = ["windmill-common/scoped_cache"]
|
||||
no_auth = ["windmill-api/no_auth"]
|
||||
private_registry_test = []
|
||||
# Languages
|
||||
python = ["windmill-worker/python", "windmill-api/python", "windmill-test-utils/python"]
|
||||
python = ["windmill-worker/python", "windmill-api/python"]
|
||||
rust = ["windmill-worker/rust"]
|
||||
mysql = ["windmill-worker/mysql"]
|
||||
oracledb = ["windmill-worker/oracledb"]
|
||||
@@ -175,14 +160,13 @@ oss = ["oss_core", "all_languages", "no_auth"]
|
||||
ce_rpi = ["ce_core", "all_languages"]
|
||||
ce = ["ce_rpi", "jemalloc", "dind", "agent_worker_server"]
|
||||
# Edition meta-features: EE variants
|
||||
ee = ["ce", "ee_core", "ee_server", "kafka-gssapi"]
|
||||
ee_rhel = ["ce_core", "ee_core", "kafka-gssapi", "all_languages"]
|
||||
ee = ["ce", "ee_core", "ee_server"]
|
||||
ee_rhel = ["ce_core", "ee_core", "all_languages"]
|
||||
ee_windows = ["ce_core", "ee_core", "all_languages_windows"]
|
||||
all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing",
|
||||
"openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "kafka-gssapi", "nats", "otel", "dind", "websocket", "http_trigger",
|
||||
"postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe",
|
||||
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs",
|
||||
"windmill-git-sync/all_sqlx_features"]
|
||||
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs"]
|
||||
|
||||
[patch.crates-io]
|
||||
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
|
||||
@@ -198,8 +182,6 @@ windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-api = { workspace = true, default-features = false }
|
||||
windmill-api-agent-workers = { workspace = true, optional = true }
|
||||
windmill-api-settings.workspace = true
|
||||
windmill-worker.workspace = true
|
||||
windmill-indexer = { workspace = true, optional = true }
|
||||
windmill-autoscaling = { workspace = true, optional = true }
|
||||
@@ -243,7 +225,6 @@ serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
windmill-queue = { workspace = true, features = ["failpoints"] }
|
||||
windmill-dep-map.workspace = true
|
||||
windmill-test-utils.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
windmill-api-client.workspace = true
|
||||
@@ -272,7 +253,6 @@ windmill-oauth = {path = "./windmill-oauth"}
|
||||
windmill-macros = {path = "./windmill-macros"}
|
||||
windmill-api-auth = { path = "./windmill-api-auth" }
|
||||
windmill-api-scripts = { path = "./windmill-api-scripts" }
|
||||
windmill-api-flows = { path = "./windmill-api-flows" }
|
||||
windmill-api-users = { path = "./windmill-api-users" }
|
||||
windmill-api-workspaces = { path = "./windmill-api-workspaces" }
|
||||
windmill-api-groups = { path = "./windmill-api-groups" }
|
||||
@@ -289,19 +269,7 @@ windmill-trigger-sqs = { path = "./windmill-trigger-sqs" }
|
||||
windmill-trigger-gcp = { path = "./windmill-trigger-gcp" }
|
||||
windmill-trigger-http = { path = "./windmill-trigger-http" }
|
||||
windmill-native-triggers = { path = "./windmill-native-triggers" }
|
||||
windmill-alerting = { path = "./windmill-alerting" }
|
||||
windmill-api-agent-workers = { path = "./windmill-api-agent-workers" }
|
||||
windmill-api-assets = { path = "./windmill-api-assets" }
|
||||
windmill-api-configs = { path = "./windmill-api-configs" }
|
||||
windmill-api-debug = { path = "./windmill-api-debug" }
|
||||
windmill-api-embeddings = { path = "./windmill-api-embeddings" }
|
||||
windmill-api-flow-conversations = { path = "./windmill-api-flow-conversations" }
|
||||
windmill-api-inputs = { path = "./windmill-api-inputs" }
|
||||
windmill-api-npm-proxy = { path = "./windmill-api-npm-proxy" }
|
||||
windmill-api-openapi = { path = "./windmill-api-openapi" }
|
||||
windmill-api-schedule = { path = "./windmill-api-schedule" }
|
||||
windmill-api-settings = { path = "./windmill-api-settings" }
|
||||
windmill-api-workers = { path = "./windmill-api-workers" }
|
||||
windmill-store = { path = "./windmill-store" }
|
||||
windmill-parser = { path = "./parsers/windmill-parser" }
|
||||
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
|
||||
@@ -321,7 +289,6 @@ windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
||||
windmill-jseval = { path = "./windmill-jseval" }
|
||||
windmill-runtime-nativets = { path = "./windmill-runtime-nativets" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
windmill-test-utils = { path = "./windmill-test-utils" }
|
||||
|
||||
reqwest-retry = "^0"
|
||||
reqwest-middleware = { version = "^0", features = ["json"] }
|
||||
@@ -502,7 +469,7 @@ nkeys = "0.4.4"
|
||||
nu-parser = { version = "0.101.0", default-features = false }
|
||||
globset = "0.4.16"
|
||||
croner = "2.2.0"
|
||||
rmcp = { version = "=0.15.0", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
|
||||
rmcp = { version = "^0", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
|
||||
rquickjs = { version = "0.8", features = ["futures", "parallel", "macro"] }
|
||||
process-wrap = { version = "8.2.1", features = ["tokio1"] }
|
||||
|
||||
|
||||
21
backend/all_features_oss.sh
Executable file
21
backend/all_features_oss.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script outputs all features except private. Usage :
|
||||
# > cargo build --features $(./all_features_oss.sh)
|
||||
|
||||
# Path to the Cargo.toml file
|
||||
CARGO_TOML_PATH="./Cargo.toml"
|
||||
|
||||
# Extract features from Cargo.toml and output them separated by commas
|
||||
if [[ -f "$CARGO_TOML_PATH" ]]; then
|
||||
grep -A 100 '\[features\]' "$CARGO_TOML_PATH" | \
|
||||
sed -n '/\[features\]/,/^\[/p' | \
|
||||
grep -E '^[a-zA-Z0-9_-]+' | \
|
||||
grep -v 'private' | \
|
||||
grep -v 'benchmark' | \
|
||||
cut -d' ' -f1 | \
|
||||
paste -sd ',' -
|
||||
else
|
||||
echo "Cargo.toml not found at $CARGO_TOML_PATH"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +1 @@
|
||||
e7f80bca9320580e1cb96b4f4ca9942649abce7f
|
||||
7596cefdba81482c0b0c0b61be26369f112d8009
|
||||
|
||||
@@ -29,39 +29,7 @@ def load_openapi_spec(file_path: str) -> Dict[str, Any]:
|
||||
print(f"Error loading OpenAPI spec: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def flatten_allof_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Flatten an allOf schema into a single object schema by merging all properties."""
|
||||
if 'allOf' not in schema:
|
||||
return schema
|
||||
|
||||
merged = {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
def collect_from(s: Dict[str, Any]):
|
||||
if 'allOf' in s:
|
||||
for item in s['allOf']:
|
||||
if isinstance(item, dict):
|
||||
collect_from(item)
|
||||
if 'properties' in s:
|
||||
merged['properties'].update(s['properties'])
|
||||
if 'required' in s and isinstance(s['required'], list):
|
||||
merged['required'].extend(s['required'])
|
||||
if 'description' in s and 'description' not in merged:
|
||||
merged['description'] = s['description']
|
||||
|
||||
collect_from(schema)
|
||||
|
||||
# Preserve additional top-level keys from the original schema
|
||||
preserved_keys = {'additionalProperties', 'title', 'nullable', 'default', 'example'}
|
||||
for key in preserved_keys:
|
||||
if key in schema and key not in merged:
|
||||
merged[key] = schema[key]
|
||||
|
||||
if not merged['required']:
|
||||
del merged['required']
|
||||
|
||||
return merged
|
||||
|
||||
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any], required_fields: Optional[List[str]] = None, base_path: str = "", include_fields: Optional[List[str]] = None, opaque_fields: Optional[List[str]] = None, include_query_params: Optional[List[str]] = None) -> tuple:
|
||||
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any], required_fields: Optional[List[str]] = None, base_path: str = "") -> tuple:
|
||||
"""Extract separate schemas for path parameters, query parameters, and request body."""
|
||||
path_params_schema = {
|
||||
"type": "object",
|
||||
@@ -104,7 +72,7 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
|
||||
path_params_schema['properties'][param_name] = param_schema
|
||||
if param_required:
|
||||
path_params_schema['required'].append(param_name)
|
||||
elif param_in == 'query' and (include_query_params is None or param_name in include_query_params):
|
||||
elif param_in == 'query':
|
||||
query_params_schema['properties'][param_name] = param_schema
|
||||
if param_required:
|
||||
query_params_schema['required'].append(param_name)
|
||||
@@ -112,28 +80,7 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
|
||||
# Process request body if present
|
||||
if request_body:
|
||||
body_schema = extract_request_body_schema(request_body, spec, base_path)
|
||||
|
||||
# Flatten allOf schemas into a single object schema for filtering
|
||||
if body_schema and (include_fields is not None or opaque_fields):
|
||||
body_schema = flatten_allof_schema(body_schema)
|
||||
|
||||
# Apply include_fields filter: only keep listed top-level properties
|
||||
if body_schema and include_fields is not None and 'properties' in body_schema:
|
||||
body_schema['properties'] = {
|
||||
k: v for k, v in body_schema['properties'].items()
|
||||
if k in include_fields
|
||||
}
|
||||
if 'required' in body_schema:
|
||||
body_schema['required'] = [
|
||||
r for r in body_schema['required'] if r in include_fields
|
||||
]
|
||||
|
||||
# Apply opaque_fields: simplify listed properties to {"type": "object"}
|
||||
if body_schema and opaque_fields and 'properties' in body_schema:
|
||||
for field in opaque_fields:
|
||||
if field in body_schema['properties']:
|
||||
body_schema['properties'][field] = {"type": "object"}
|
||||
|
||||
|
||||
# If we have required fields specified and a body schema, update the required array
|
||||
if body_schema and required_fields:
|
||||
if 'required' not in body_schema:
|
||||
@@ -148,60 +95,11 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
|
||||
# Log warning when a required field is missing from schema properties
|
||||
print(f"Warning: Required field '{field}' not found in body schema properties", file=sys.stderr)
|
||||
|
||||
# Sanitize empty schemas for JSON Schema draft 2020-12 compliance
|
||||
path_params_schema = sanitize_empty_schemas(path_params_schema)
|
||||
query_params_schema = sanitize_empty_schemas(query_params_schema)
|
||||
body_schema = sanitize_empty_schemas(body_schema)
|
||||
|
||||
# Convert enums to descriptions for client compatibility
|
||||
path_params_schema = convert_enums_to_descriptions(path_params_schema)
|
||||
query_params_schema = convert_enums_to_descriptions(query_params_schema)
|
||||
body_schema = convert_enums_to_descriptions(body_schema)
|
||||
|
||||
# Detect overlapping property names across schemas and rename with suffixes
|
||||
path_keys = set(path_params_schema['properties'].keys()) if path_params_schema and path_params_schema.get('properties') else set()
|
||||
query_keys = set(query_params_schema['properties'].keys()) if query_params_schema and query_params_schema.get('properties') else set()
|
||||
body_keys = set(body_schema['properties'].keys()) if body_schema and body_schema.get('properties') else set()
|
||||
|
||||
conflicts = (path_keys & query_keys) | (path_keys & body_keys) | (query_keys & body_keys)
|
||||
|
||||
path_field_renames = {}
|
||||
query_field_renames = {}
|
||||
body_field_renames = {}
|
||||
|
||||
for field in conflicts:
|
||||
schemas_and_renames = [
|
||||
(path_params_schema, path_keys, '__path', path_field_renames),
|
||||
(query_params_schema, query_keys, '__query', query_field_renames),
|
||||
(body_schema, body_keys, '__body', body_field_renames),
|
||||
]
|
||||
for schema, keys, suffix, renames_map in schemas_and_renames:
|
||||
if field in keys and schema and 'properties' in schema:
|
||||
new_name = field + suffix
|
||||
# Rename in properties
|
||||
schema['properties'][new_name] = schema['properties'].pop(field)
|
||||
# Update description to clarify the renamed field
|
||||
if 'description' not in schema['properties'][new_name]:
|
||||
schema['properties'][new_name] = dict(schema['properties'][new_name])
|
||||
prop = schema['properties'][new_name]
|
||||
if isinstance(prop, dict):
|
||||
existing_desc = prop.get('description', '')
|
||||
location = suffix.lstrip('_')
|
||||
if not existing_desc:
|
||||
prop['description'] = f"({location} parameter)"
|
||||
else:
|
||||
prop['description'] = f"{existing_desc} ({location} parameter)"
|
||||
# Rename in required array
|
||||
if 'required' in schema and field in schema['required']:
|
||||
schema['required'] = [new_name if r == field else r for r in schema['required']]
|
||||
# Store the reverse mapping: renamed -> original
|
||||
renames_map[new_name] = field
|
||||
|
||||
# Return None for empty schemas
|
||||
path_params_schema = path_params_schema if path_params_schema and path_params_schema.get('properties') else None
|
||||
query_params_schema = query_params_schema if query_params_schema and query_params_schema.get('properties') else None
|
||||
|
||||
return (path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames)
|
||||
path_params_schema = path_params_schema if path_params_schema['properties'] else None
|
||||
query_params_schema = query_params_schema if query_params_schema['properties'] else None
|
||||
|
||||
return (path_params_schema, query_params_schema, body_schema)
|
||||
|
||||
# Cache for loaded external files
|
||||
_external_file_cache: Dict[str, Dict[str, Any]] = {}
|
||||
@@ -262,25 +160,18 @@ def resolve_ref(ref_path: str, spec: Dict[str, Any], base_path: str = "") -> tup
|
||||
|
||||
return (current if isinstance(current, dict) else None), spec
|
||||
|
||||
def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any], base_path: str = "", _visited_refs: Optional[set] = None) -> Dict[str, Any]:
|
||||
def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any], base_path: str = "") -> Dict[str, Any]:
|
||||
"""Recursively resolve all $ref references in a schema."""
|
||||
if _visited_refs is None:
|
||||
_visited_refs = set()
|
||||
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
# If this is a $ref, resolve it
|
||||
if '$ref' in schema:
|
||||
ref_path = schema['$ref']
|
||||
if ref_path in _visited_refs:
|
||||
# Circular reference detected - return empty object to break the cycle
|
||||
return {"type": "object"}
|
||||
_visited_refs = _visited_refs | {ref_path}
|
||||
resolved, resolved_spec = resolve_ref(ref_path, spec, base_path)
|
||||
if resolved:
|
||||
# Recursively resolve any refs in the resolved schema using the appropriate spec
|
||||
return resolve_schema_refs(resolved, resolved_spec, base_path, _visited_refs)
|
||||
return resolve_schema_refs(resolved, resolved_spec, base_path)
|
||||
else:
|
||||
print(f"Warning: Could not resolve $ref: {ref_path}")
|
||||
return schema
|
||||
@@ -289,10 +180,10 @@ def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any], base_path:
|
||||
resolved_schema = {}
|
||||
for key, value in schema.items():
|
||||
if isinstance(value, dict):
|
||||
resolved_schema[key] = resolve_schema_refs(value, spec, base_path, _visited_refs)
|
||||
resolved_schema[key] = resolve_schema_refs(value, spec, base_path)
|
||||
elif isinstance(value, list):
|
||||
resolved_schema[key] = [
|
||||
resolve_schema_refs(item, spec, base_path, _visited_refs) if isinstance(item, dict) else item
|
||||
resolve_schema_refs(item, spec, base_path) if isinstance(item, dict) else item
|
||||
for item in value
|
||||
]
|
||||
else:
|
||||
@@ -300,56 +191,6 @@ def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any], base_path:
|
||||
|
||||
return resolved_schema
|
||||
|
||||
def convert_enums_to_descriptions(schema: Any) -> Any:
|
||||
"""Recursively convert enum arrays into description text to avoid client compatibility issues."""
|
||||
if isinstance(schema, list):
|
||||
return [convert_enums_to_descriptions(item) for item in schema]
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
result = {}
|
||||
enum_value = None
|
||||
# First pass: copy all non-enum keys so 'description' is available before enum processing
|
||||
for key, value in schema.items():
|
||||
if key == 'enum':
|
||||
enum_value = value
|
||||
else:
|
||||
result[key] = convert_enums_to_descriptions(value)
|
||||
|
||||
# Second pass: process enum using the already-copied description
|
||||
if enum_value is not None:
|
||||
values_str = ', '.join(str(v) for v in enum_value)
|
||||
existing = result.get('description', '')
|
||||
enum_desc = f"Possible values: {values_str}"
|
||||
result['description'] = f"{existing}. {enum_desc}" if existing else enum_desc
|
||||
|
||||
return result
|
||||
|
||||
def sanitize_empty_schemas(schema: Any) -> Any:
|
||||
"""Replace empty {} schemas with valid JSON Schema draft 2020-12 equivalents.
|
||||
|
||||
In OpenAPI, {} means 'any value' but strict JSON Schema validators (e.g. Claude's API)
|
||||
reject empty objects. This converts them to proper schemas.
|
||||
"""
|
||||
if isinstance(schema, list):
|
||||
return [sanitize_empty_schemas(item) for item in schema]
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
result = {}
|
||||
for key, value in schema.items():
|
||||
if key == 'additionalProperties' and isinstance(value, dict) and len(value) == 0:
|
||||
result[key] = True
|
||||
elif key == 'properties' and isinstance(value, dict):
|
||||
# properties is a map of name -> schema; sanitize each property schema
|
||||
result[key] = {
|
||||
k: {"type": "object"} if isinstance(v, dict) and len(v) == 0 else sanitize_empty_schemas(v)
|
||||
for k, v in value.items()
|
||||
}
|
||||
else:
|
||||
result[key] = sanitize_empty_schemas(value)
|
||||
return result
|
||||
|
||||
def extract_request_body_schema(request_body: Dict[str, Any], spec: Dict[str, Any], base_path: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""Extract request body schema from OpenAPI requestBody definition and resolve refs."""
|
||||
if not request_body:
|
||||
@@ -402,9 +243,6 @@ def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
'parameters': operation.get('parameters', []),
|
||||
'requestBody': operation.get('requestBody'),
|
||||
'required_fields': operation.get('x-mcp-required-fields', []),
|
||||
'include_fields': operation.get('x-mcp-tool-include-fields'),
|
||||
'opaque_fields': operation.get('x-mcp-tool-opaque-fields'),
|
||||
'include_query_params': operation.get('x-mcp-tool-include-query-params'),
|
||||
}
|
||||
tools.append(tool)
|
||||
|
||||
@@ -440,18 +278,14 @@ export const mcpEndpointTools: EndpointTool[] = [];
|
||||
method = tool['method'].upper()
|
||||
|
||||
# Generate separate schemas
|
||||
path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames = extract_separate_schemas(
|
||||
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path,
|
||||
tool.get('include_fields'), tool.get('opaque_fields'), tool.get('include_query_params')
|
||||
path_params_schema, query_params_schema, body_schema = extract_separate_schemas(
|
||||
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path
|
||||
)
|
||||
|
||||
# Convert schemas to TypeScript - use 'as const' for better type inference
|
||||
path_params_ts = json.dumps(path_params_schema, indent=8) if path_params_schema else "undefined"
|
||||
query_params_ts = json.dumps(query_params_schema, indent=8) if query_params_schema else "undefined"
|
||||
body_schema_ts = json.dumps(body_schema, indent=8) if body_schema else "undefined"
|
||||
path_field_renames_ts = json.dumps(path_field_renames, indent=8) if path_field_renames else "undefined"
|
||||
query_field_renames_ts = json.dumps(query_field_renames, indent=8) if query_field_renames else "undefined"
|
||||
body_field_renames_ts = json.dumps(body_field_renames, indent=8) if body_field_renames else "undefined"
|
||||
|
||||
# Generate tool definition
|
||||
tool_def = f""" {{
|
||||
@@ -462,10 +296,7 @@ export const mcpEndpointTools: EndpointTool[] = [];
|
||||
method: "{method}",
|
||||
pathParamsSchema: {path_params_ts},
|
||||
queryParamsSchema: {query_params_ts},
|
||||
bodySchema: {body_schema_ts},
|
||||
pathFieldRenames: {path_field_renames_ts},
|
||||
queryFieldRenames: {query_field_renames_ts},
|
||||
bodyFieldRenames: {body_field_renames_ts}
|
||||
bodySchema: {body_schema_ts}
|
||||
}}"""
|
||||
tool_definitions.append(tool_def)
|
||||
|
||||
@@ -484,9 +315,6 @@ export interface EndpointTool {{
|
||||
pathParamsSchema?: object;
|
||||
queryParamsSchema?: object;
|
||||
bodySchema?: object;
|
||||
pathFieldRenames?: Record<string, string>;
|
||||
queryFieldRenames?: Record<string, string>;
|
||||
bodyFieldRenames?: Record<string, string>;
|
||||
}}
|
||||
|
||||
export const mcpEndpointTools: EndpointTool[] = [
|
||||
@@ -516,17 +344,13 @@ pub fn all_tools() -> Vec<EndpointTool> {{
|
||||
method = tool['method'].upper()
|
||||
|
||||
# Generate separate schemas
|
||||
path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames = extract_separate_schemas(
|
||||
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path,
|
||||
tool.get('include_fields'), tool.get('opaque_fields'), tool.get('include_query_params')
|
||||
path_params_schema, query_params_schema, body_schema = extract_separate_schemas(
|
||||
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path
|
||||
)
|
||||
|
||||
path_params_rust = schema_to_rust_value(path_params_schema)
|
||||
query_params_rust = schema_to_rust_value(query_params_schema)
|
||||
body_schema_rust = schema_to_rust_value(body_schema)
|
||||
path_field_renames_rust = schema_to_rust_value(path_field_renames if path_field_renames else None)
|
||||
query_field_renames_rust = schema_to_rust_value(query_field_renames if query_field_renames else None)
|
||||
body_field_renames_rust = schema_to_rust_value(body_field_renames if body_field_renames else None)
|
||||
|
||||
# Generate tool definition
|
||||
tool_def = f""" EndpointTool {{
|
||||
@@ -538,9 +362,6 @@ pub fn all_tools() -> Vec<EndpointTool> {{
|
||||
path_params_schema: {path_params_rust},
|
||||
query_params_schema: {query_params_rust},
|
||||
body_schema: {body_schema_rust},
|
||||
path_field_renames: {path_field_renames_rust},
|
||||
query_field_renames: {query_field_renames_rust},
|
||||
body_field_renames: {body_field_renames_rust},
|
||||
}}"""
|
||||
tool_definitions.append(tool_def)
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Drop the workspace_protection_rule table and its indexes
|
||||
DROP TABLE IF EXISTS workspace_protection_rule;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Add workspace_protection_rule table for fine-grained access control
|
||||
CREATE TABLE workspace_protection_rule (
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
rules INTEGER NOT NULL,
|
||||
bypass_groups TEXT[] NOT NULL DEFAULT '{}',
|
||||
bypass_users TEXT[] NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (workspace_id, name)
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
DROP TABLE IF EXISTS lock_hash;
|
||||
DROP INDEX IF EXISTS dependency_map_importer_path_idx;
|
||||
ALTER TABLE dependency_map DROP COLUMN IF EXISTS imported_lockfile_hash;
|
||||
@@ -1,14 +0,0 @@
|
||||
-- Add column to track lockfile hash of imported scripts
|
||||
-- Used to skip re-locking when imports' lockfiles haven't changed
|
||||
ALTER TABLE dependency_map ADD COLUMN imported_lockfile_hash BIGINT;
|
||||
|
||||
-- Index for queries filtering by importer (skip-relock check, load(), clear_map_for_item)
|
||||
CREATE INDEX IF NOT EXISTS dependency_map_importer_path_idx ON dependency_map (workspace_id, importer_path);
|
||||
|
||||
-- Stores lockfile/content hashes to detect when imports' locks have changed
|
||||
CREATE TABLE lock_hash (
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
path VARCHAR(255) NOT NULL,
|
||||
lockfile_hash BIGINT NOT NULL,
|
||||
PRIMARY KEY (workspace_id, path)
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
-- The setup_app was a complex app created via multiple migrations.
|
||||
-- Restoring it would require re-running the original creation and update migrations.
|
||||
-- This is a no-op down migration since the app is no longer needed.
|
||||
@@ -1 +0,0 @@
|
||||
DELETE FROM app WHERE workspace_id = 'admins' AND path = 'g/all/setup_app';
|
||||
@@ -328,7 +328,6 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
|
||||
"azure.keyvault.secrets" => "azure-keyvault-secrets",
|
||||
"azure.storage.blob" => "azure-storage-blob",
|
||||
"azure.storage.filedatalake" => "azure-storage-file-datalake",
|
||||
"azure.identity" => "azure-identity",
|
||||
// Add new entry here ^
|
||||
};
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
get_database_url, DatabaseUrl,
|
||||
};
|
||||
|
||||
pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_INDEXER: u32 = 5;
|
||||
|
||||
pub async fn initial_connection() -> Result<sqlx::Pool<sqlx::Postgres>, error::Error> {
|
||||
let connect_options = get_database_url().await?.connect_options().await?;
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(2)
|
||||
.connect_with(connect_options)
|
||||
.await
|
||||
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))
|
||||
}
|
||||
|
||||
pub async fn connect_db(
|
||||
server_mode: bool,
|
||||
indexer_mode: bool,
|
||||
worker_mode: bool,
|
||||
#[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> anyhow::Result<sqlx::Pool<sqlx::Postgres>> {
|
||||
use anyhow::Context;
|
||||
|
||||
let database_url = get_database_url().await?;
|
||||
|
||||
let max_connections = match std::env::var("DATABASE_CONNECTIONS") {
|
||||
Ok(n) => n.parse::<u32>().context("invalid DATABASE_CONNECTIONS")?,
|
||||
Err(_) => {
|
||||
if server_mode {
|
||||
DEFAULT_MAX_CONNECTIONS_SERVER
|
||||
} else if indexer_mode {
|
||||
DEFAULT_MAX_CONNECTIONS_INDEXER
|
||||
} else {
|
||||
DEFAULT_MAX_CONNECTIONS_WORKER
|
||||
+ std::env::var("NUM_WORKERS")
|
||||
.ok()
|
||||
.map(|x| x.parse().ok())
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
- 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let pool = connect(database_url.clone(), max_connections, worker_mode).await?;
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
let pool2 = pool.clone();
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
if let DatabaseUrl::IamRds(database_url) = database_url {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = killpill_rx.recv() => {
|
||||
break;
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
|
||||
let needs_refresh = {
|
||||
let read_guard = database_url.read().await;
|
||||
read_guard.needs_refresh()
|
||||
};
|
||||
if needs_refresh {
|
||||
let new_url = tokio::time::timeout(std::time::Duration::from_secs(10), get_database_url()).await;
|
||||
match new_url {
|
||||
Ok(Ok(new_url)) => {
|
||||
match new_url.connect_options().await {
|
||||
Ok(connect_options) => {
|
||||
pool2.set_connect_options(connect_options);
|
||||
tracing::info!("Refreshed IAM RDS URL successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error getting IAM RDS connect options, retrying in 10s: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::error!("Error refreshing IAM RDS URL, trying again in 10s: {}", e);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Timeout after 10s refreshing IAM RDS URL, trying again in 10 seconds: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
database_url: DatabaseUrl,
|
||||
max_connections: u32,
|
||||
worker_mode: bool,
|
||||
) -> Result<sqlx::Pool<sqlx::Postgres>, error::Error> {
|
||||
use sqlx::Executor;
|
||||
use std::time::Duration;
|
||||
let mut pool_options = sqlx::postgres::PgPoolOptions::new()
|
||||
.min_connections((max_connections / 5).clamp(1, max_connections))
|
||||
.max_connections(max_connections)
|
||||
.max_lifetime(Duration::from_secs(30 * 60)); // 30 mins
|
||||
if worker_mode {
|
||||
pool_options = pool_options.idle_timeout(Duration::from_secs(60));
|
||||
}
|
||||
pool_options
|
||||
.after_connect(move |conn, _| {
|
||||
if worker_mode {
|
||||
Box::pin(async move {
|
||||
if let Err(e) = conn
|
||||
.execute(
|
||||
r#"
|
||||
SET enable_seqscan = OFF;
|
||||
SET statement_timeout = '5min';
|
||||
SET idle_in_transaction_session_timeout = '10min';
|
||||
SET tcp_keepalives_idle = 300;
|
||||
SET tcp_keepalives_interval = 60;
|
||||
SET tcp_keepalives_count = 10;"#,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error setting postgres settings: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Box::pin(async move {
|
||||
if let Err(e) = conn
|
||||
.execute(
|
||||
r#"
|
||||
SET statement_timeout = '5min';
|
||||
SET idle_in_transaction_session_timeout = '10min';
|
||||
SET tcp_keepalives_idle = 300;
|
||||
SET tcp_keepalives_interval = 60;
|
||||
SET tcp_keepalives_count = 10;"#,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error setting postgres settings: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
})
|
||||
.connect_with(
|
||||
database_url
|
||||
.connect_options()
|
||||
.await?
|
||||
.statement_cache_capacity(400),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))
|
||||
}
|
||||
@@ -41,13 +41,13 @@ use windmill_common::{
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
JOB_ISOLATION_SETTING, HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
|
||||
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
|
||||
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, UV_INDEX_STRATEGY_SETTING,
|
||||
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
|
||||
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
RUBY_REPOS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
@@ -99,12 +99,11 @@ use crate::monitor::{
|
||||
reload_app_workspaced_route_setting, reload_base_url_setting,
|
||||
reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting,
|
||||
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
|
||||
reload_job_isolation_setting, reload_hub_api_secret_setting, reload_hub_base_url_setting,
|
||||
reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key,
|
||||
reload_npm_config_registry_setting,
|
||||
reload_hub_api_secret_setting, reload_hub_base_url_setting, reload_job_default_timeout_setting,
|
||||
reload_jwt_secret_setting, reload_license_key, reload_npm_config_registry_setting,
|
||||
reload_otel_tracing_proxy_setting, reload_pip_index_url_setting,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config,
|
||||
reload_uv_index_strategy_setting, reload_worker_config, MonitorIteration,
|
||||
reload_worker_config, MonitorIteration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -118,7 +117,6 @@ const BIND_ADDR_ENV: &str = "SERVER_BIND_ADDR";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod cgroups;
|
||||
mod db_connect;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
mod ee_oss;
|
||||
@@ -667,7 +665,7 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
} else {
|
||||
println!("Connecting to database...");
|
||||
|
||||
let db = crate::db_connect::initial_connection().await?;
|
||||
let db = windmill_common::initial_connection().await?;
|
||||
|
||||
let num_version = sqlx::query_scalar!("SELECT version()").fetch_one(&db).await;
|
||||
|
||||
@@ -736,12 +734,8 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
.unwrap_or(false);
|
||||
|
||||
if !skip_migration {
|
||||
if mode == Mode::Worker {
|
||||
windmill_api::wait_for_db_migrations(&db, killpill_rx.resubscribe()).await?;
|
||||
} else {
|
||||
migration_handle =
|
||||
windmill_api::migrate_db(&db, killpill_rx.resubscribe()).await?;
|
||||
}
|
||||
// migration code to avoid break
|
||||
migration_handle = windmill_api::migrate_db(&db, killpill_rx.resubscribe()).await?;
|
||||
} else {
|
||||
tracing::info!("SKIP_MIGRATION set, skipping db migration...")
|
||||
}
|
||||
@@ -776,12 +770,8 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
let conn = if mode == Mode::Agent {
|
||||
conn
|
||||
} else {
|
||||
// Drop the initial connection pool before creating the main one.
|
||||
// With low PostgreSQL max_connections, both pools existing simultaneously
|
||||
// can exhaust all available connection slots, causing connect_db to hang.
|
||||
drop(conn);
|
||||
|
||||
let db = crate::db_connect::connect_db(
|
||||
// This time we use a pool of connections
|
||||
let db = windmill_common::connect_db(
|
||||
server_mode,
|
||||
indexer_mode,
|
||||
worker_mode,
|
||||
@@ -1217,15 +1207,11 @@ Windmill Community Edition {GIT_VERSION}
|
||||
last_settings_reload = Instant::now();
|
||||
}
|
||||
|
||||
let monitor_start = Instant::now();
|
||||
let warn_handle = if server_mode {
|
||||
Some(tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
tracing::warn!("monitor task has been running for more than 5s");
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if server_mode {
|
||||
if !*windmill_common::QUIET_LOGS {
|
||||
tracing::info!("monitor task started");
|
||||
}
|
||||
}
|
||||
monitor_db(
|
||||
&conn,
|
||||
&base_internal_url,
|
||||
@@ -1240,12 +1226,10 @@ Windmill Community Edition {GIT_VERSION}
|
||||
)
|
||||
.await;
|
||||
monitor_iteration += 1;
|
||||
if let Some(handle) = warn_handle {
|
||||
handle.abort();
|
||||
}
|
||||
let elapsed = monitor_start.elapsed();
|
||||
if server_mode && elapsed >= Duration::from_secs(5) {
|
||||
tracing::info!("monitor task finished in {elapsed:.1?}");
|
||||
if server_mode {
|
||||
if !*windmill_common::QUIET_LOGS {
|
||||
tracing::info!("monitor task finished");
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1562,7 +1546,6 @@ async fn process_notify_event(
|
||||
reload_delete_logs_periodically_setting(conn).await
|
||||
}
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING => reload_job_default_timeout_setting(conn).await,
|
||||
JOB_ISOLATION_SETTING => reload_job_isolation_setting(conn).await,
|
||||
#[cfg(feature = "parquet")]
|
||||
OBJECT_STORE_CONFIG_SETTING => {
|
||||
if !disable_s3_store {
|
||||
@@ -1572,7 +1555,6 @@ async fn process_notify_event(
|
||||
SCIM_TOKEN_SETTING => reload_scim_token_setting(conn).await,
|
||||
EXTRA_PIP_INDEX_URL_SETTING => reload_extra_pip_index_url_setting(conn).await,
|
||||
PIP_INDEX_URL_SETTING => reload_pip_index_url_setting(conn).await,
|
||||
UV_INDEX_STRATEGY_SETTING => reload_uv_index_strategy_setting(conn).await,
|
||||
INSTANCE_PYTHON_VERSION_SETTING => {
|
||||
reload_instance_python_version_setting(conn).await
|
||||
}
|
||||
|
||||
@@ -54,14 +54,12 @@ use windmill_common::{
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
|
||||
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OTEL_SETTING,
|
||||
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
|
||||
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
|
||||
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
UV_INDEX_STRATEGY_SETTING,
|
||||
},
|
||||
indexer::load_indexer_config,
|
||||
jwt::JWT_SECRET,
|
||||
@@ -86,12 +84,11 @@ use windmill_common::{
|
||||
use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING};
|
||||
use windmill_queue::{cancel_job, get_queued_job_v2, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
result_processor::handle_job_error, JobCompletedSender, JobIsolationLevel,
|
||||
OtelTracingProxySettings, SameWorkerSender, BUNFIG_INSTALL_SCOPES, CARGO_REGISTRIES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, JOB_ISOLATION, KEEP_JOB_DIR, MAVEN_REPOS,
|
||||
NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NSJAIL_AVAILABLE, NUGET_CONFIG,
|
||||
result_processor::handle_job_error, JobCompletedSender, OtelTracingProxySettings,
|
||||
SameWorkerSender, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT,
|
||||
KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NUGET_CONFIG,
|
||||
OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, POWERSHELL_REPO_PAT,
|
||||
POWERSHELL_REPO_URL, UV_INDEX_STRATEGY,
|
||||
POWERSHELL_REPO_URL,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -318,10 +315,8 @@ pub async fn initial_load(
|
||||
|
||||
if worker_mode {
|
||||
reload_job_default_timeout_setting(&conn).await;
|
||||
reload_job_isolation_setting(&conn).await;
|
||||
reload_extra_pip_index_url_setting(&conn).await;
|
||||
reload_pip_index_url_setting(&conn).await;
|
||||
reload_uv_index_strategy_setting(&conn).await;
|
||||
reload_npm_config_registry_setting(&conn).await;
|
||||
reload_bunfig_install_scopes_setting(&conn).await;
|
||||
reload_instance_python_version_setting(&conn).await;
|
||||
@@ -331,7 +326,6 @@ pub async fn initial_load(
|
||||
reload_maven_repos_setting(&conn).await;
|
||||
reload_no_default_maven_setting(&conn).await;
|
||||
reload_ruby_repos_setting(&conn).await;
|
||||
reload_cargo_registries_setting(&conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,16 +1250,6 @@ pub async fn reload_pip_index_url_setting(conn: &Connection) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_uv_index_strategy_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
UV_INDEX_STRATEGY_SETTING,
|
||||
"UV_INDEX_STRATEGY",
|
||||
UV_INDEX_STRATEGY.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_instance_python_version_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
@@ -1362,16 +1346,6 @@ pub async fn reload_ruby_repos_setting(conn: &Connection) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_cargo_registries_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
windmill_common::global_settings::CARGO_REGISTRIES_SETTING,
|
||||
"CARGO_REGISTRIES",
|
||||
CARGO_REGISTRIES.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_hub_api_secret_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
@@ -1421,32 +1395,6 @@ pub async fn reload_job_default_timeout_setting(conn: &Connection) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_job_isolation_setting(conn: &Connection) {
|
||||
let value =
|
||||
match load_value_from_global_settings_with_conn(conn, JOB_ISOLATION_SETTING, true).await {
|
||||
Ok(Some(v)) => JobIsolationLevel::from_str(v.as_str().unwrap_or("")),
|
||||
Ok(None) => JobIsolationLevel::Undefined,
|
||||
Err(e) => {
|
||||
tracing::error!("Error reloading job_isolation setting: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let old_value = JobIsolationLevel::from_u8(JOB_ISOLATION.swap(value as u8, Ordering::Relaxed));
|
||||
if old_value != value {
|
||||
tracing::info!(
|
||||
"job_isolation setting changed from {:?} to {:?}",
|
||||
old_value,
|
||||
value
|
||||
);
|
||||
}
|
||||
if value == JobIsolationLevel::NsjailSandboxing && NSJAIL_AVAILABLE.is_none() {
|
||||
tracing::error!(
|
||||
"job_isolation is set to nsjail_sandboxing but nsjail is not available on this worker. \
|
||||
All jobs will fail until nsjail is installed or the setting is changed."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reload_request_size(conn: &Connection) {
|
||||
if let Err(e) = reload_setting(
|
||||
conn,
|
||||
@@ -3144,13 +3092,7 @@ RETURNING job_id"
|
||||
|
||||
async fn cleanup_job_result_stream_orphaned_jobs(db: &DB) -> error::Result<()> {
|
||||
let result = sqlx::query!(
|
||||
"DELETE FROM job_result_stream_v2
|
||||
WHERE job_id NOT IN (SELECT id FROM v2_job_queue)
|
||||
AND job_id NOT IN (
|
||||
SELECT id FROM v2_job_completed
|
||||
WHERE completed_at > NOW() - INTERVAL '60 seconds'
|
||||
)
|
||||
RETURNING job_id",
|
||||
"DELETE FROM job_result_stream_v2 WHERE job_id NOT IN (SELECT id FROM v2_job_queue) RETURNING job_id",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await?;
|
||||
|
||||
@@ -11,10 +11,12 @@ EE_CODE_DIR="../windmill-ee-private/"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-r|--revert)
|
||||
# Removes all _ee.rs files from the backend directory. Symlinks are deleted.
|
||||
# Regular files with content differing from windmill-ee-private are moved back
|
||||
# to the EE repo so nothing is lost.
|
||||
# If EE files have been substituted, this will revert them to their initial content.
|
||||
# This relies on `git restore` so the EE files must not be committed to the repo for
|
||||
# this to work (commit hooks should prevent this from happening, as well as the fact
|
||||
# that we're using symlinks by default).
|
||||
REVERT="YES"
|
||||
MOVE_NEW_FILES="YES"
|
||||
shift
|
||||
;;
|
||||
-c|--copy)
|
||||
@@ -64,28 +66,12 @@ if [ ! -d "${EE_CODE_DIR}" ]; then
|
||||
fi
|
||||
|
||||
if [ "$REVERT" == "YES" ]; then
|
||||
backend_dirpath="${root_dirpath}/backend/"
|
||||
for ce_file in $(find "${root_dirpath}/backend" -name "*_ee.rs"); do
|
||||
if [ -L "${ce_file}" ]; then
|
||||
rm "${ce_file}"
|
||||
echo "Deleted symlink '${ce_file}'"
|
||||
else
|
||||
ee_file="${ce_file/${backend_dirpath}/}"
|
||||
ee_file="${EE_CODE_DIR}${ee_file}"
|
||||
if [ ! -f "${ee_file}" ] || ! diff -q "${ce_file}" "${ee_file}" > /dev/null 2>&1; then
|
||||
mkdir -p "$(dirname "${ee_file}")"
|
||||
mv "${ce_file}" "${ee_file}"
|
||||
echo "Moved '${ce_file}' -->> '${ee_file}'"
|
||||
else
|
||||
rm "${ce_file}"
|
||||
echo "Deleted '${ce_file}' (identical to EE)"
|
||||
fi
|
||||
fi
|
||||
for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
rm ${ce_file} || true
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
elif [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
# This replaces all files in current repo with alternative EE files in windmill-ee-private
|
||||
for ee_file in $(find "${EE_CODE_DIR}" -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This script is used to summarize the database schema.
|
||||
# You can use pg_dump to dump the schema to a file.
|
||||
# pg_dump --file "schema.sql" --format=p --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "postgresql://postgres:changeme@localhost:5432/windmill"
|
||||
# pg_dump --file "schema.sql" --host "localhost" --port "5432" --username "postgres" --no-password --format=c --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "windmill"
|
||||
# Then you can run python summarize_schema.py schema.sql to get the summarized schema.
|
||||
|
||||
import re
|
||||
@@ -17,7 +17,6 @@ def summarize_schema(file_path):
|
||||
# Use state variables to parse multi-line definitions
|
||||
current_table = None
|
||||
current_enum = None
|
||||
pending_alter_table = None # For multi-line ALTER TABLE ... ADD CONSTRAINT
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
@@ -55,9 +54,6 @@ def summarize_schema(file_path):
|
||||
match_column = re.match(r'^"?(\w+)"?\s+([\w\d\.\[\]\(\)]+)', line)
|
||||
if match_column:
|
||||
col_name = match_column.group(1)
|
||||
# Skip CONSTRAINT definitions (e.g., CHECK, UNIQUE) mistakenly matched as columns
|
||||
if col_name.upper() == 'CONSTRAINT':
|
||||
continue
|
||||
col_type = match_column.group(2)
|
||||
tables[current_table]['columns'].append(f"{col_name} ({col_type})")
|
||||
|
||||
@@ -69,22 +65,16 @@ def summarize_schema(file_path):
|
||||
tables[current_table]['pks'].update(pk_cols)
|
||||
continue
|
||||
|
||||
# --- Parse Foreign Keys (defined outside CREATE TABLE, may span 2 lines) ---
|
||||
match_alter = re.match(r"ALTER TABLE ONLY public\.(\w+)$", line)
|
||||
if match_alter:
|
||||
pending_alter_table = match_alter.group(1)
|
||||
continue
|
||||
|
||||
if pending_alter_table:
|
||||
match_fk = re.match(r"ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\)", line)
|
||||
if match_fk:
|
||||
from_cols, to_table, to_cols = match_fk.groups()
|
||||
from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')])
|
||||
to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')])
|
||||
fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})"
|
||||
tables[pending_alter_table]['fks'].append(fk_string)
|
||||
pending_alter_table = None
|
||||
continue
|
||||
# --- Parse Foreign Keys (defined outside CREATE TABLE) ---
|
||||
match_fk = re.match(r"ALTER TABLE ONLY public\.(\w+)\s+ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\);", line)
|
||||
if match_fk:
|
||||
from_table, from_cols, to_table, to_cols = match_fk.groups()
|
||||
# Clean up column names
|
||||
from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')])
|
||||
to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')])
|
||||
|
||||
fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})"
|
||||
tables[from_table]['fks'].append(fk_string)
|
||||
|
||||
# --- Parse Index definitions ---
|
||||
match_index = re.match(r"CREATE (UNIQUE )?INDEX (\w+) ON public\.(\w+) USING (\w+) \((.+)\);", line)
|
||||
@@ -104,73 +94,44 @@ def summarize_schema(file_path):
|
||||
|
||||
return enums, tables
|
||||
|
||||
TYPE_ABBREVIATIONS = {
|
||||
'character': 'char',
|
||||
'integer': 'int',
|
||||
'bigint': 'bigint',
|
||||
'smallint': 'smallint',
|
||||
'boolean': 'bool',
|
||||
'timestamp': 'ts',
|
||||
'bytea': 'bytes',
|
||||
'real': 'float',
|
||||
'json': 'json',
|
||||
'jsonb': 'jsonb',
|
||||
'text': 'text',
|
||||
'uuid': 'uuid',
|
||||
'bit(64)': 'bit64',
|
||||
}
|
||||
|
||||
def shorten_type(col_str):
|
||||
"""Shorten a 'name (type)' string to 'name(short_type)'."""
|
||||
match = re.match(r'^(\w+) \((.+)\)$', col_str)
|
||||
if not match:
|
||||
return col_str
|
||||
name, typ = match.group(1), match.group(2)
|
||||
# Strip public. prefix from enum types
|
||||
typ = re.sub(r'^public\.', '', typ)
|
||||
# Apply abbreviations (prefix-based to handle parametrized types like character(64) and array types like integer[])
|
||||
for prefix, abbr in TYPE_ABBREVIATIONS.items():
|
||||
if typ.startswith(prefix):
|
||||
typ = abbr + typ[len(prefix):]
|
||||
break
|
||||
return f"{name}({typ})"
|
||||
|
||||
def format_output(enums, tables):
|
||||
"""
|
||||
Formats the parsed schema data into a compact, LLM-friendly string.
|
||||
Formats the parsed schema data into a clean, readable string.
|
||||
"""
|
||||
output = []
|
||||
|
||||
output.append("# Database Schema")
|
||||
output.append("")
|
||||
output.append("## ENUMs")
|
||||
|
||||
output.append("### Simplified Database Schema ###")
|
||||
output.append("\n--- Custom Data Types (ENUMs) ---\n")
|
||||
if not enums:
|
||||
output.append("(none)")
|
||||
output.append("No custom ENUM types found.")
|
||||
else:
|
||||
for name, values in sorted(enums.items()):
|
||||
output.append(f"{name}: {', '.join(values)}")
|
||||
output.append(f"{name}:")
|
||||
for v in values:
|
||||
output.append(f" - {v}")
|
||||
output.append("")
|
||||
|
||||
output.append("")
|
||||
output.append("## Tables")
|
||||
output.append("\n--- Tables and Relationships ---\n")
|
||||
if not tables:
|
||||
output.append("(none)")
|
||||
output.append("No tables found.")
|
||||
else:
|
||||
for name, data in sorted(tables.items()):
|
||||
# Columns: inline, comma-separated, with PK marker and shortened types
|
||||
cols = []
|
||||
output.append(f"TABLE: {name}")
|
||||
for col in data['columns']:
|
||||
col_short = shorten_type(col)
|
||||
col_name = col.split(' ')[0]
|
||||
if col_name in data['pks']:
|
||||
col_short += " PK"
|
||||
cols.append(col_short)
|
||||
output.append(f"{name}: {', '.join(cols)}")
|
||||
|
||||
# Foreign keys on one indented line
|
||||
marker = " (PK)" if col_name in data['pks'] else ""
|
||||
output.append(f" - {col}{marker}")
|
||||
|
||||
if data['fks']:
|
||||
output.append(f" FK: {' | '.join(data['fks'])}")
|
||||
|
||||
# Indexes omitted for brevity — query the database for exact index info
|
||||
output.append(" Relationships:")
|
||||
for fk in data['fks']:
|
||||
output.append(f" - {fk}")
|
||||
|
||||
if data['indexes']:
|
||||
output.append(" Indexes:")
|
||||
for idx in data['indexes']:
|
||||
output.append(f" - {idx}")
|
||||
output.append("-" * 20)
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,267 +0,0 @@
|
||||
#![cfg(all(feature = "private", feature = "agent_worker_server"))]
|
||||
|
||||
use windmill_test_utils::*;
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::{
|
||||
jobs::{JobPayload, RawCode},
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
fn bun_code(code: &str) -> RawCode {
|
||||
RawCode {
|
||||
hash: None,
|
||||
content: code.to_string(),
|
||||
path: None,
|
||||
language: ScriptLang::Bun,
|
||||
lock: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_simple_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main() { return 42; }",
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(42)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_with_args(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main(x: number, y: number) { return x + y; }",
|
||||
)))
|
||||
.arg("x", json!(10))
|
||||
.arg("y", json!(32))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(42)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_with_logs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
r#"export function main() {
|
||||
console.log("hello from agent worker");
|
||||
console.log("processing step 1");
|
||||
console.log("processing step 2");
|
||||
return "done";
|
||||
}"#,
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!("done")));
|
||||
|
||||
let logs = sqlx::query_scalar::<_, String>(
|
||||
"SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = 'test-workspace'",
|
||||
)
|
||||
.bind(result.id)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let logs = logs.expect("logs should exist");
|
||||
assert!(
|
||||
logs.contains("hello from agent worker"),
|
||||
"logs should contain the printed output, got: {logs}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main() { throw new Error('test error'); }",
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(!result.success, "job should fail");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_complex_result(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
r#"export function main() {
|
||||
return {
|
||||
items: [1, 2, 3],
|
||||
metadata: { key: "value" },
|
||||
count: 3,
|
||||
};
|
||||
}"#,
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
let json = result.json_result().unwrap();
|
||||
assert_eq!(json["items"], json!([1, 2, 3]));
|
||||
assert_eq!(json["metadata"]["key"], json!("value"));
|
||||
assert_eq!(json["count"], json!(3));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_token_creation(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (client, _port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
// client.baseurl() already includes /api
|
||||
let resp = client
|
||||
.client()
|
||||
.post(format!(
|
||||
"{}/agent_workers/create_agent_token",
|
||||
client.baseurl()
|
||||
))
|
||||
.json(&json!({
|
||||
"worker_group": "lifecycle-test",
|
||||
"tags": ["bun", "flow", "dependency"],
|
||||
"exp": usize::MAX
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"create_agent_token should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
let token = resp.text().await?;
|
||||
let token = token.trim_matches('"');
|
||||
assert!(
|
||||
token.starts_with("jwt_agent_"),
|
||||
"token should start with jwt_agent_ prefix, got: {token}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_token_and_ping(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let resp = client
|
||||
.client()
|
||||
.post(format!(
|
||||
"{}/agent_workers/create_agent_token",
|
||||
client.baseurl()
|
||||
))
|
||||
.json(&json!({
|
||||
"worker_group": "lifecycle-test",
|
||||
"tags": ["bun", "flow", "dependency"],
|
||||
"exp": usize::MAX
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(resp.status().is_success());
|
||||
let token = resp.text().await?;
|
||||
let token = token.trim_matches('"');
|
||||
|
||||
let suffix = windmill_common::utils::create_default_worker_suffix("lifecycle-test");
|
||||
let base_url = format!("http://localhost:{port}");
|
||||
let http_client =
|
||||
windmill_common::agent_workers::build_agent_http_client(&suffix, &token, &base_url);
|
||||
|
||||
// Initial ping inserts the worker record into the database
|
||||
let resp = http_client
|
||||
.client
|
||||
.post(format!("{}/api/agent_workers/update_ping", base_url))
|
||||
.json(&json!({
|
||||
"worker_instance": "test-instance",
|
||||
"ip": "127.0.0.1",
|
||||
"tags": ["bun"],
|
||||
"version": "test",
|
||||
"vcpus": 4,
|
||||
"memory": 8192,
|
||||
"ping_type": "Initial"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"initial ping should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
// Verify the ping was recorded in the database
|
||||
let worker_count = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM worker_ping WHERE worker_instance = 'test-instance'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
assert!(worker_count > 0, "worker ping should be recorded in database");
|
||||
|
||||
// MainLoop ping updates the existing record
|
||||
let resp = http_client
|
||||
.client
|
||||
.post(format!("{}/api/agent_workers/update_ping", base_url))
|
||||
.json(&json!({
|
||||
"tags": ["bun"],
|
||||
"vcpus": 4,
|
||||
"memory": 8192,
|
||||
"jobs_executed": 0,
|
||||
"ping_type": "MainLoop"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"main loop ping should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_multiple_jobs_sequential(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
for i in 0..3 {
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(&format!(
|
||||
"export function main() {{ return {i}; }}"
|
||||
))))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job {i} should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(i)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn app_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/apps/{endpoint}/{path}")
|
||||
@@ -39,7 +40,7 @@ fn new_app(path: &str, summary: &str) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_app_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,4 +1,5 @@
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use crate::common::*;
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx::Pool;
|
||||
use windmill_common::jobs::{JobPayload, RawCode};
|
||||
@@ -859,7 +860,7 @@ export function main() {
|
||||
// ============================================================================
|
||||
|
||||
mod dedicated_worker_protocol {
|
||||
use windmill_test_utils::{parse_dedicated_worker_line, DedicatedWorkerResult};
|
||||
use crate::common::{parse_dedicated_worker_line, DedicatedWorkerResult};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
use windmill_worker::{
|
||||
|
||||
@@ -227,52 +227,6 @@ impl RunJob {
|
||||
uuid
|
||||
}
|
||||
|
||||
/// Push the job as a specific user (for testing permissions)
|
||||
pub async fn push_as(self, db: &Pool<Postgres>, username: &str, email: &str) -> Uuid {
|
||||
let RunJob { payload, args, scheduled_for_o, .. } = self;
|
||||
let mut hm_args = std::collections::HashMap::new();
|
||||
for (k, v) in args {
|
||||
hm_args.insert(k, windmill_common::worker::to_raw_value(&v));
|
||||
}
|
||||
|
||||
let tx = PushIsolationLevel::IsolatedRoot(db.clone());
|
||||
let (uuid, tx) = windmill_queue::push(
|
||||
db,
|
||||
tx,
|
||||
"test-workspace",
|
||||
payload,
|
||||
windmill_queue::PushArgs::from(&hm_args),
|
||||
username,
|
||||
email,
|
||||
format!("u/{}", username),
|
||||
/* token_prefix */ None,
|
||||
scheduled_for_o,
|
||||
/* schedule_path */ None,
|
||||
/* parent_job */ None,
|
||||
/* root job */ None,
|
||||
/* flow_innermost_root_job */ None,
|
||||
/* job_id */ None,
|
||||
/* is_flow_step */ false,
|
||||
/* same_worker */ false,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("push has to succeed");
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
uuid
|
||||
}
|
||||
|
||||
/// push the job, spawn a worker, wait until the job is in completed_job
|
||||
pub async fn run_until_complete(
|
||||
self,
|
||||
@@ -574,6 +528,7 @@ pub async fn test_for_versions<F: Future<Output = ()>>(
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
// #[cfg(feature = "python")]
|
||||
pub async fn assert_lockfile(
|
||||
db: &Pool<Postgres>,
|
||||
script_content: String,
|
||||
@@ -837,7 +792,7 @@ pub async fn testing_http_connection(port: u16) -> Connection {
|
||||
"{}{}",
|
||||
windmill_common::agent_workers::AGENT_JWT_PREFIX,
|
||||
windmill_common::jwt::encode_with_internal_secret(
|
||||
windmill_api_agent_workers::AgentAuth {
|
||||
windmill_api::agent_workers_ee::AgentAuth {
|
||||
worker_group: "testing-agent".to_owned(),
|
||||
suffix: Some(suffix.clone()),
|
||||
tags: vec!["flow".into(), "python3".into(), "dependency".into()],
|
||||
@@ -2,9 +2,8 @@ use sqlx::{Pool, Postgres};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use windmill_api_client::types::NewScript;
|
||||
use windmill_test_utils::{
|
||||
in_test_worker, init_client, listen_for_completed_jobs, rebuild_dmap, ApiServer,
|
||||
};
|
||||
mod common;
|
||||
use common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer};
|
||||
|
||||
mod dependency_map {
|
||||
use super::*;
|
||||
@@ -171,7 +170,7 @@ mod dependency_map {
|
||||
let (client, _port, _s) = init(db.clone()).await;
|
||||
assert_dmap(&db, None, CORRECT_DMAP.clone()).await;
|
||||
// rebuild map
|
||||
assert!(rebuild_dmap(&client).await);
|
||||
assert!(common::rebuild_dmap(&client).await);
|
||||
assert_dmap(&db, None, CORRECT_DMAP.clone()).await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -184,7 +183,7 @@ mod dependency_map {
|
||||
// Spawn first rebuild
|
||||
let handle = {
|
||||
let client = client.clone();
|
||||
tokio::spawn(async move { rebuild_dmap(&client).await })
|
||||
tokio::spawn(async move { common::rebuild_dmap(&client).await })
|
||||
};
|
||||
|
||||
// Immidiately spawn another
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
@@ -11,7 +12,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_draft_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,410 +0,0 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Test that workspace error handler can be set and removed via database operations
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_settings(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let _server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Initially error_handler should be NULL
|
||||
let initial = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(initial.is_none());
|
||||
|
||||
// Set error handler with all options
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler", "extra_args": {"notify": true}, "muted_on_cancel": true, "muted_on_user_path": false}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let after_set = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
after_set,
|
||||
Some("script/f/test/error_handler".to_string())
|
||||
);
|
||||
|
||||
// Verify extra_args
|
||||
let extra_args = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(extra_args.is_some());
|
||||
|
||||
// Verify muted_on_cancel
|
||||
let muted_on_cancel = sqlx::query_scalar!(
|
||||
r#"SELECT (error_handler->>'muted_on_cancel')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(muted_on_cancel, Some(true));
|
||||
|
||||
// Verify muted_on_user_path
|
||||
let muted_on_user_path = sqlx::query_scalar!(
|
||||
r#"SELECT (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(muted_on_user_path, Some(false));
|
||||
|
||||
// Remove error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = NULL
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let after_remove = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(after_remove.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that a failed job triggers the workspace error handler
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_triggered_on_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
let error_handler_code = r#"
|
||||
export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, error: any) {
|
||||
console.log("Error handler called for job:", job_id);
|
||||
return { handled: true, original_path: path };
|
||||
}
|
||||
"#;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 1111111111, 'f/test/error_handler', $1, 'deno', 'script', 'test-user', '{}', 'Error handler script', 'Handles failed job completions', '')
|
||||
"#,
|
||||
error_handler_code
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a script that will fail
|
||||
let failing_script_code = "export function main() { throw new Error('intentional failure'); }";
|
||||
let failing_script_hash: i64 = 2222222222;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', $1, 'f/test/failing_script', $2, 'deno', 'script', 'test-user', '{}', 'Failing test script', 'A script that always fails', '')
|
||||
"#,
|
||||
failing_script_hash,
|
||||
failing_script_code
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler in workspace_settings
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create the error_handler group
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'The group the error handler acts on behalf of', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the failing script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(failing_script_hash),
|
||||
path: "f/test/failing_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
// Verify the job actually failed
|
||||
assert!(!completed_job.success, "Job should have failed");
|
||||
|
||||
let main_job_id = completed_job.id;
|
||||
|
||||
// Wait for the error handler job to be created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
// Verify the error handler job was created
|
||||
let error_handler_job = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
runnable_path,
|
||||
permissioned_as_email,
|
||||
parent_job
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_some(),
|
||||
"Error handler job should have been created"
|
||||
);
|
||||
|
||||
let handler_job = error_handler_job.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
handler_job.runnable_path.as_deref(),
|
||||
Some("f/test/error_handler"),
|
||||
"Error handler should run the configured script"
|
||||
);
|
||||
assert_eq!(
|
||||
handler_job.permissioned_as_email.as_str(),
|
||||
"error_handler@windmill.dev",
|
||||
"Error handler should run as error_handler user"
|
||||
);
|
||||
assert_eq!(
|
||||
handler_job.parent_job,
|
||||
Some(main_job_id),
|
||||
"Error handler should have the failed job as parent"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that error handler is NOT triggered when ws_error_handler_muted is set on the script
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_muted_on_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 3333333333, 'f/test/error_handler', 'export function main() { return "handled"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a failing script with ws_error_handler_muted = true
|
||||
let failing_script_hash: i64 = 4444444444;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted)
|
||||
VALUES ('test-workspace', $1, 'f/test/muted_failing_script', 'export function main() { throw new Error("fail"); }', 'deno', 'script', 'test-user', '{}', '', '', '', true)
|
||||
"#,
|
||||
failing_script_hash,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the muted failing script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(failing_script_hash),
|
||||
path: "f/test/muted_failing_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
assert!(!completed_job.success, "Job should have failed");
|
||||
|
||||
// Wait and check that NO error handler job was created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
let error_handler_job = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_none(),
|
||||
"Error handler should NOT have been triggered for a muted script"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that error handler is NOT triggered on successful job completion
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_not_triggered_on_success(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 5555555555, 'f/test/error_handler', 'export function main() { return "handled"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a successful script
|
||||
let success_script_hash: i64 = 6666666666;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', $1, 'f/test/success_script', 'export function main() { return "ok"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
success_script_hash,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the successful script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(success_script_hash),
|
||||
path: "f/test/success_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
assert!(completed_job.success, "Job should have succeeded");
|
||||
|
||||
// Wait and check that NO error handler job was created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
let error_handler_job = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_none(),
|
||||
"Error handler should NOT have been triggered for a successful job"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -25,7 +25,8 @@ use windmill_common::{
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
/// Helper to create a FlowModule with default fields
|
||||
fn flow_module(id: &str, value: FlowModuleValue) -> FlowModule {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn flow_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/flows/{endpoint}/{path}")
|
||||
@@ -39,7 +40,7 @@ fn new_flow(path: &str, summary: &str) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_flow_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn folder_url(port: u16, endpoint: &str, name: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/folders/{endpoint}/{name}")
|
||||
@@ -15,7 +16,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_folder_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn group_url(port: u16, endpoint: &str, name: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/groups/{endpoint}/{name}")
|
||||
@@ -15,7 +16,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_group_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -31,7 +31,7 @@ mod tests {
|
||||
|
||||
/// Test that configuring instance groups for a workspace auto-adds existing group members
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_configure_instance_groups_adds_existing_members(db: Pool<Postgres>) {
|
||||
// Configure workspace to auto-add users from 'engineering' group with 'developer' role
|
||||
let groups = vec!["engineering".to_string()];
|
||||
@@ -158,7 +158,7 @@ mod tests {
|
||||
|
||||
/// Test role assignment based on instance group configuration
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_assignment_admin(db: Pool<Postgres>) {
|
||||
// Configure workspace with admins group having admin role
|
||||
let groups = vec!["admins".to_string()];
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
|
||||
/// Test role assignment for operator
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_assignment_operator(db: Pool<Postgres>) {
|
||||
// Configure workspace with sales group having operator role
|
||||
let groups = vec!["sales".to_string()];
|
||||
@@ -260,7 +260,7 @@ mod tests {
|
||||
|
||||
/// Test role precedence when user is in multiple instance groups
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_precedence_multiple_groups(db: Pool<Postgres>) {
|
||||
// Configure workspace with multiple groups: engineering (admin), sales (operator)
|
||||
// Bob is in both groups, should get admin role (highest precedence)
|
||||
@@ -322,7 +322,7 @@ mod tests {
|
||||
|
||||
/// Test removing user from instance group removes them from workspace
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_remove_user_from_instance_group(db: Pool<Postgres>) {
|
||||
// First, add alice to the workspace via engineering group
|
||||
let added_via = json!({"source": "instance_group", "group": "engineering"});
|
||||
@@ -416,7 +416,7 @@ mod tests {
|
||||
|
||||
/// Test that users added via domain are not affected by instance group removal
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_domain_added_users_not_affected_by_group_removal(db: Pool<Postgres>) {
|
||||
// Add alice via domain (not instance group)
|
||||
let added_via = json!({"source": "domain", "domain": "example.com"});
|
||||
@@ -469,7 +469,7 @@ mod tests {
|
||||
|
||||
/// Test cleanup when instance group is removed from workspace configuration
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_cleanup_removed_instance_groups(db: Pool<Postgres>) {
|
||||
// First, add users via engineering group
|
||||
for (username, email) in &[("alice", "alice@example.com"), ("bob", "bob@example.com")] {
|
||||
@@ -588,7 +588,7 @@ mod tests {
|
||||
|
||||
/// Test that users are not duplicated if already in workspace
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_no_duplicate_users(db: Pool<Postgres>) {
|
||||
// Add alice to workspace first (without instance group tracking)
|
||||
sqlx::query!(
|
||||
@@ -636,7 +636,7 @@ mod tests {
|
||||
|
||||
/// Test workspace without auto-add configured is not affected
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_workspace_without_auto_add_not_affected(db: Pool<Postgres>) {
|
||||
// ws-no-auto-add has no instance_groups configured
|
||||
|
||||
@@ -672,7 +672,7 @@ mod tests {
|
||||
|
||||
/// Test querying workspaces configured with a specific instance group
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_query_workspaces_with_instance_group(db: Pool<Postgres>) {
|
||||
// Configure ws-with-auto-add to use engineering group
|
||||
let groups = vec!["engineering".to_string()];
|
||||
@@ -1,3 +1,5 @@
|
||||
mod common;
|
||||
|
||||
mod job_payload {
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
@@ -6,7 +8,7 @@ mod job_payload {
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
use windmill_common::flow_status::RestartedFrom;
|
||||
|
||||
use windmill_test_utils::*;
|
||||
use crate::common::*;
|
||||
use windmill_common::min_version::{
|
||||
MIN_VERSION, MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432,
|
||||
MIN_VERSION_IS_AT_LEAST_1_440,
|
||||
|
||||
@@ -7,7 +7,8 @@ use windmill_common::{
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ListJobsResponse {
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
*/
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use common::*;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use futures::StreamExt;
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
*/
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use common::*;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::notify_events::{cleanup_old_events, get_latest_event_id, poll_notify_events};
|
||||
|
||||
mod common;
|
||||
|
||||
/// Helper to insert a test event directly
|
||||
async fn insert_test_event(db: &Pool<Postgres>, channel: &str, payload: &str) -> i64 {
|
||||
sqlx::query_scalar::<_, i64>(
|
||||
@@ -37,7 +39,7 @@ async fn count_events_for_channel(db: &Pool<Postgres>, channel: &str) -> i64 {
|
||||
// Basic Functionality Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_get_latest_event_id_returns_valid_id(db: Pool<Postgres>) {
|
||||
// Get current latest id
|
||||
let latest_id = get_latest_event_id(&db).await.expect("Should get latest event id");
|
||||
@@ -49,7 +51,7 @@ async fn test_get_latest_event_id_returns_valid_id(db: Pool<Postgres>) {
|
||||
assert!(new_latest_id >= new_id, "Latest id should be >= new event id");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_get_latest_event_id_with_events(db: Pool<Postgres>) {
|
||||
let _id1 = insert_test_event(&db, "test_channel_1", "payload1").await;
|
||||
let _id2 = insert_test_event(&db, "test_channel_2", "payload2").await;
|
||||
@@ -59,7 +61,7 @@ async fn test_get_latest_event_id_with_events(db: Pool<Postgres>) {
|
||||
assert!(latest_id >= id3, "Latest id should be >= last inserted id");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_poll_notify_events_no_new_events(db: Pool<Postgres>) {
|
||||
// Get latest id first
|
||||
let latest_id = get_latest_event_id(&db).await.unwrap();
|
||||
@@ -69,7 +71,7 @@ async fn test_poll_notify_events_no_new_events(db: Pool<Postgres>) {
|
||||
assert!(events.is_empty(), "Should return empty vec when polling from latest id");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_poll_notify_events_returns_new_events(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -90,7 +92,7 @@ async fn test_poll_notify_events_returns_new_events(db: Pool<Postgres>) {
|
||||
assert!(our_events[0].id < our_events[1].id, "Events should be ordered by id ascending");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_poll_notify_events_respects_last_event_id(db: Pool<Postgres>) {
|
||||
let id1 = insert_test_event(&db, "test_respect_id", "payload1").await;
|
||||
let _id2 = insert_test_event(&db, "test_respect_id", "payload2").await;
|
||||
@@ -107,7 +109,7 @@ async fn test_poll_notify_events_respects_last_event_id(db: Pool<Postgres>) {
|
||||
assert!(our_events.iter().all(|e| e.id > id1), "All events should have id > id1");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_cleanup_old_events(db: Pool<Postgres>) {
|
||||
// Use unique channel names to avoid interference from other tests
|
||||
let old_channel = format!("test_cleanup_old_{}", uuid::Uuid::new_v4());
|
||||
@@ -154,7 +156,7 @@ async fn test_cleanup_old_events(db: Pool<Postgres>) {
|
||||
// Database Trigger Tests - Verify triggers insert events correctly
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_config_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -176,7 +178,7 @@ async fn test_trigger_notify_config_change(db: Pool<Postgres>) {
|
||||
assert!(!config_events.is_empty(), "Should have notify_config_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_insert(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -199,7 +201,7 @@ async fn test_trigger_notify_global_setting_change_insert(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on insert");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_update(db: Pool<Postgres>) {
|
||||
// Use a unique setting name for testing
|
||||
let setting_name = format!("test_setting_update_{}", uuid::Uuid::new_v4());
|
||||
@@ -229,7 +231,7 @@ async fn test_trigger_notify_global_setting_change_update(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on update");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_delete(db: Pool<Postgres>) {
|
||||
// Use a unique setting name for testing
|
||||
let setting_name = format!("test_setting_delete_{}", uuid::Uuid::new_v4());
|
||||
@@ -259,7 +261,7 @@ async fn test_trigger_notify_global_setting_change_delete(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on delete");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_envs_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -281,7 +283,7 @@ async fn test_trigger_notify_workspace_envs_change(db: Pool<Postgres>) {
|
||||
assert!(!env_events.is_empty(), "Should have notify_workspace_envs_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_key_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -303,7 +305,7 @@ async fn test_trigger_notify_workspace_key_change(db: Pool<Postgres>) {
|
||||
assert!(!key_events.is_empty(), "Should have notify_workspace_key_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_token_invalidation(db: Pool<Postgres>) {
|
||||
// First insert a session token
|
||||
let token = format!("test_token_{}", uuid::Uuid::new_v4());
|
||||
@@ -334,7 +336,7 @@ async fn test_trigger_notify_token_invalidation(db: Pool<Postgres>) {
|
||||
assert!(!token_events.is_empty(), "Should have notify_token_invalidation event");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_webhook_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -353,7 +355,7 @@ async fn test_trigger_notify_webhook_change(db: Pool<Postgres>) {
|
||||
assert!(!webhook_events.is_empty(), "Should have notify_webhook_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_premium_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -376,7 +378,7 @@ async fn test_trigger_notify_workspace_premium_change(db: Pool<Postgres>) {
|
||||
// HTTP Trigger Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_http_trigger_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -406,7 +408,7 @@ async fn test_trigger_notify_http_trigger_change(db: Pool<Postgres>) {
|
||||
// Script/Flow Version Change Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_runnable_version_change_script(db: Pool<Postgres>) {
|
||||
// First create a script without lock
|
||||
let script_path = format!("f/test/script_{}", uuid::Uuid::new_v4());
|
||||
@@ -447,7 +449,7 @@ async fn test_trigger_notify_runnable_version_change_script(db: Pool<Postgres>)
|
||||
assert_eq!(parts[1], "script", "Second part should be 'script'");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_trigger_notify_runnable_version_change_flow(db: Pool<Postgres>) {
|
||||
// First create a flow with empty versions array
|
||||
let flow_path = format!("f/test/flow_{}", uuid::Uuid::new_v4());
|
||||
@@ -492,7 +494,7 @@ async fn test_trigger_notify_runnable_version_change_flow(db: Pool<Postgres>) {
|
||||
// Concurrent Access Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_concurrent_event_insertion(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test run
|
||||
let channel = format!("test_concurrent_{}", uuid::Uuid::new_v4());
|
||||
@@ -534,7 +536,7 @@ async fn test_concurrent_event_insertion(db: Pool<Postgres>) {
|
||||
assert_eq!(ids.len(), 10, "All events should have unique IDs");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_polling_isolation(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test
|
||||
let channel = format!("test_isolation_{}", uuid::Uuid::new_v4());
|
||||
@@ -589,7 +591,7 @@ async fn test_polling_isolation(db: Pool<Postgres>) {
|
||||
// Edge Case Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_empty_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -605,7 +607,7 @@ async fn test_empty_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(empty_events[0].payload, "", "Payload should be empty string");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_large_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -623,7 +625,7 @@ async fn test_large_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(large_events[0].payload.len(), 1024, "Payload should be preserved");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_special_characters_in_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -640,7 +642,7 @@ async fn test_special_characters_in_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(special_events[0].payload, special_payload, "Special characters should be preserved");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_cleanup_with_no_old_events(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test
|
||||
let channel = format!("test_no_old_{}", uuid::Uuid::new_v4());
|
||||
@@ -1,13 +1,10 @@
|
||||
#[cfg(feature = "deno_core")]
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "deno_core")]
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_test_utils::*;
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
/// Helper to create a client authenticated as a specific user
|
||||
#[cfg(feature = "deno_core")]
|
||||
async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
@@ -21,14 +18,12 @@ async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client {
|
||||
}
|
||||
|
||||
/// Test helper to check if a GET request succeeds
|
||||
#[cfg(feature = "deno_core")]
|
||||
async fn can_read(client: &reqwest::Client, url: &str) -> bool {
|
||||
let resp = client.get(url).send().await.unwrap();
|
||||
resp.status().is_success()
|
||||
}
|
||||
|
||||
/// Test helper to check if a POST request succeeds (for write operations)
|
||||
#[cfg(feature = "deno_core")]
|
||||
async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value) -> bool {
|
||||
let resp = client.post(url).json(&body).send().await.unwrap();
|
||||
let status = resp.status();
|
||||
@@ -49,7 +44,7 @@ async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value)
|
||||
/// `cargo test --features deno_core permissions -- --ignored`
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
async fn test_permissions_exhaustive(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -482,7 +477,7 @@ async fn test_permissions_exhaustive(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// Additional test for verifying group permission inheritance
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
async fn test_group_permission_inheritance(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -552,7 +547,7 @@ async fn test_group_permission_inheritance(db: Pool<Postgres>) -> anyhow::Result
|
||||
/// Test that permissions work correctly for all item types
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
async fn test_all_item_types_permissions(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -593,7 +588,7 @@ async fn test_all_item_types_permissions(db: Pool<Postgres>) -> anyhow::Result<(
|
||||
/// Operators have limited permissions - they can execute but cannot manage resources
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
async fn test_operator_cannot_create_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user