mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'n8n-io:master' into MongoDB_vector_store
This commit is contained in:
commit
00e78a6a86
8
.github/scripts/trim-fe-packageJson.js
vendored
8
.github/scripts/trim-fe-packageJson.js
vendored
|
@ -7,12 +7,12 @@ const trimPackageJson = (packageName) => {
|
||||||
const { scripts, peerDependencies, devDependencies, dependencies, ...packageJson } = require(
|
const { scripts, peerDependencies, devDependencies, dependencies, ...packageJson } = require(
|
||||||
filePath,
|
filePath,
|
||||||
);
|
);
|
||||||
if (packageName === '@n8n/chat') {
|
if (packageName === 'frontend/@n8n/chat') {
|
||||||
packageJson.dependencies = dependencies;
|
packageJson.dependencies = dependencies;
|
||||||
}
|
}
|
||||||
writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
||||||
};
|
};
|
||||||
|
|
||||||
trimPackageJson('@n8n/chat');
|
trimPackageJson('frontend/@n8n/chat');
|
||||||
trimPackageJson('design-system');
|
trimPackageJson('frontend/@n8n/design-system');
|
||||||
trimPackageJson('editor-ui');
|
trimPackageJson('frontend/editor-ui');
|
||||||
|
|
17
.github/workflows/release-publish.yml
vendored
17
.github/workflows/release-publish.yml
vendored
|
@ -51,14 +51,20 @@ jobs:
|
||||||
- name: Dry-run publishing
|
- name: Dry-run publishing
|
||||||
run: pnpm publish -r --no-git-checks --dry-run
|
run: pnpm publish -r --no-git-checks --dry-run
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Pre publishing changes
|
||||||
run: |
|
run: |
|
||||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||||
node .github/scripts/trim-fe-packageJson.js
|
node .github/scripts/trim-fe-packageJson.js
|
||||||
node .github/scripts/ensure-provenance-fields.mjs
|
node .github/scripts/ensure-provenance-fields.mjs
|
||||||
|
cp README.md packages/cli/README.md
|
||||||
sed -i "s/default: 'dev'/default: 'stable'/g" packages/cli/dist/config/schema.js
|
sed -i "s/default: 'dev'/default: 'stable'/g" packages/cli/dist/config/schema.js
|
||||||
pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public --tag rc --no-git-checks
|
|
||||||
npm dist-tag rm n8n rc
|
- name: Publish to NPM
|
||||||
|
run: pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public --tag rc --no-git-checks
|
||||||
|
|
||||||
|
- name: Cleanup rc tag
|
||||||
|
run: npm dist-tag rm n8n rc
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- id: set-release
|
- id: set-release
|
||||||
run: echo "release=${{ env.RELEASE }}" >> $GITHUB_OUTPUT
|
run: echo "release=${{ env.RELEASE }}" >> $GITHUB_OUTPUT
|
||||||
|
@ -68,7 +74,7 @@ jobs:
|
||||||
needs: [publish-to-npm]
|
needs: [publish-to-npm]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.pull_request.merged == true
|
if: github.event.pull_request.merged == true
|
||||||
timeout-minutes: 10
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -103,6 +109,7 @@ jobs:
|
||||||
context: ./docker/images/n8n
|
context: ./docker/images/n8n
|
||||||
build-args: |
|
build-args: |
|
||||||
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
|
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
|
||||||
|
N8N_RELEASE_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
provenance: false
|
provenance: false
|
||||||
push: true
|
push: true
|
||||||
|
@ -155,7 +162,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
projects: ${{ secrets.SENTRY_FRONTEND_PROJECT }}
|
projects: ${{ secrets.SENTRY_FRONTEND_PROJECT }}
|
||||||
version: ${{ needs.publish-to-npm.outputs.release }}
|
version: ${{ needs.publish-to-npm.outputs.release }}
|
||||||
sourcemaps: packages/editor-ui/dist
|
sourcemaps: packages/frontend/editor-ui/dist
|
||||||
|
|
||||||
- name: Create a backend release
|
- name: Create a backend release
|
||||||
uses: getsentry/action-release@v1.7.0
|
uses: getsentry/action-release@v1.7.0
|
||||||
|
|
41
.github/workflows/release-push-to-channel.yml
vendored
41
.github/workflows/release-push-to-channel.yml
vendored
|
@ -11,10 +11,10 @@ on:
|
||||||
description: 'Release channel'
|
description: 'Release channel'
|
||||||
required: true
|
required: true
|
||||||
type: choice
|
type: choice
|
||||||
default: 'next'
|
default: 'beta'
|
||||||
options:
|
options:
|
||||||
- next
|
- beta
|
||||||
- latest
|
- stable
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-to-npm:
|
release-to-npm:
|
||||||
|
@ -25,9 +25,18 @@ jobs:
|
||||||
- uses: actions/setup-node@v4.2.0
|
- uses: actions/setup-node@v4.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
- run: |
|
|
||||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
- run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||||
npm dist-tag add n8n@${{ github.event.inputs.version }} ${{ github.event.inputs.release-channel }}
|
|
||||||
|
- if: github.event.inputs.release-channel == 'beta'
|
||||||
|
run: |
|
||||||
|
npm dist-tag add n8n@${{ github.event.inputs.version }} next
|
||||||
|
npm dist-tag add n8n@${{ github.event.inputs.version }} beta
|
||||||
|
|
||||||
|
- if: github.event.inputs.release-channel == 'stable'
|
||||||
|
run: |
|
||||||
|
npm dist-tag add n8n@${{ github.event.inputs.version }} latest
|
||||||
|
npm dist-tag add n8n@${{ github.event.inputs.version }} stable
|
||||||
|
|
||||||
release-to-docker-hub:
|
release-to-docker-hub:
|
||||||
name: Release to DockerHub
|
name: Release to DockerHub
|
||||||
|
@ -39,7 +48,15 @@ jobs:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- run: docker buildx imagetools create -t ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.release-channel }} ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.version }}
|
- if: github.event.inputs.release-channel == 'stable'
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create -t ${{ secrets.DOCKER_USERNAME }}/n8n:stable ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
docker buildx imagetools create -t ${{ secrets.DOCKER_USERNAME }}/n8n:latest ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
|
||||||
|
- if: github.event.inputs.release-channel == 'beta'
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create -t ${{ secrets.DOCKER_USERNAME }}/n8n:beta ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
docker buildx imagetools create -t ${{ secrets.DOCKER_USERNAME }}/n8n:next ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
|
||||||
release-to-github-container-registry:
|
release-to-github-container-registry:
|
||||||
name: Release to GitHub Container Registry
|
name: Release to GitHub Container Registry
|
||||||
|
@ -52,7 +69,15 @@ jobs:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- run: docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.release-channel }} ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.version }}
|
- if: github.event.inputs.release-channel == 'stable'
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/n8n:stable ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/n8n:latest ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
|
||||||
|
- if: github.event.inputs.release-channel == 'beta'
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/n8n:beta ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/n8n:next ghcr.io/${{ github.repository_owner }}/n8n:${{ github.event.inputs.version }}
|
||||||
|
|
||||||
update-docs:
|
update-docs:
|
||||||
name: Update latest and next in the docs
|
name: Update latest and next in the docs
|
||||||
|
|
|
@ -2,7 +2,7 @@ coverage
|
||||||
dist
|
dist
|
||||||
package.json
|
package.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
packages/editor-ui/index.html
|
packages/frontend/editor-ui/index.html
|
||||||
packages/nodes-base/nodes/**/test
|
packages/nodes-base/nodes/**/test
|
||||||
packages/cli/templates/form-trigger.handlebars
|
packages/cli/templates/form-trigger.handlebars
|
||||||
packages/cli/templates/form-trigger-completion.handlebars
|
packages/cli/templates/form-trigger-completion.handlebars
|
||||||
|
|
64
CHANGELOG.md
64
CHANGELOG.md
|
@ -1,3 +1,67 @@
|
||||||
|
# [1.81.0](https://github.com/n8n-io/n8n/compare/n8n@1.80.0...n8n@1.81.0) (2025-02-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Always clear popupWindowState before showing popup from form trigger ([#13363](https://github.com/n8n-io/n8n/issues/13363)) ([b7f1265](https://github.com/n8n-io/n8n/commit/b7f12650f1f42c0ff15c1da3e5ade350fb1e23d2))
|
||||||
|
* **Code Node:** Fix `$items` in Code node when using task runner ([#13368](https://github.com/n8n-io/n8n/issues/13368)) ([87b3c50](https://github.com/n8n-io/n8n/commit/87b3c508b3d5a7d6f3b9f8377de66567a04fa970))
|
||||||
|
* **core:** Avoid renewing the license on init to prevent excessive duplicate renewal calls ([#13347](https://github.com/n8n-io/n8n/issues/13347)) ([1e1f528](https://github.com/n8n-io/n8n/commit/1e1f52846641515ad4479ab1088e78a9266e452d))
|
||||||
|
* **core:** Ensure that 'workflow-post-execute' event has userId whenever it's available ([#13326](https://github.com/n8n-io/n8n/issues/13326)) ([f41e353](https://github.com/n8n-io/n8n/commit/f41e353887fef4269510d25fa87b73da4cf925f9))
|
||||||
|
* **core:** Fix DB migrations for MySQL ([#13261](https://github.com/n8n-io/n8n/issues/13261)) ([d0968a1](https://github.com/n8n-io/n8n/commit/d0968a10d56ac5c97974129742ba8f8a85997dac))
|
||||||
|
* **core:** Fix resuming executions on test webhooks from Wait forms ([#13410](https://github.com/n8n-io/n8n/issues/13410)) ([8ffd316](https://github.com/n8n-io/n8n/commit/8ffd3167d58d30f087fd31010e6f79f1398d8f49))
|
||||||
|
* **core:** Handle connections for missing nodes in a workflow ([#13362](https://github.com/n8n-io/n8n/issues/13362)) ([1e5feb1](https://github.com/n8n-io/n8n/commit/1e5feb195d50054939f85c9e1b5a32885c579901))
|
||||||
|
* **core:** Make sure middleware works with legacy API Keys ([#13390](https://github.com/n8n-io/n8n/issues/13390)) ([ca76ef4](https://github.com/n8n-io/n8n/commit/ca76ef4bc248a3bcde844bc8378d38eed269f032))
|
||||||
|
* **core:** Return original hooks errors to the frontend ([#13365](https://github.com/n8n-io/n8n/issues/13365)) ([5439181](https://github.com/n8n-io/n8n/commit/5439181e92f20fef1423575cabec7acbe1740b26))
|
||||||
|
* **editor:** Correctly close node creator when selecting/deselecting a node ([#13338](https://github.com/n8n-io/n8n/issues/13338)) ([c3dc66e](https://github.com/n8n-io/n8n/commit/c3dc66ee7372927fcfd6baac3b9d853690e39c99))
|
||||||
|
* **editor:** Do not show credential details popup for users without necessary scopes with direct link ([#13264](https://github.com/n8n-io/n8n/issues/13264)) ([a5401d0](https://github.com/n8n-io/n8n/commit/a5401d06a58ef026f44499d05b42a8d0dbe2520e))
|
||||||
|
* **editor:** Do not show project settings for users without permission with direct link ([#13246](https://github.com/n8n-io/n8n/issues/13246)) ([fa488f1](https://github.com/n8n-io/n8n/commit/fa488f15619f798a0360c96492f2928ac661d9ee))
|
||||||
|
* **editor:** Don't open form popup window if different trigger node is used ([#13391](https://github.com/n8n-io/n8n/issues/13391)) ([57a9a5b](https://github.com/n8n-io/n8n/commit/57a9a5b15f55aae0301851e93847ed87feb081e8))
|
||||||
|
* **editor:** Fix configurable node description margins and text alignment ([#13318](https://github.com/n8n-io/n8n/issues/13318)) ([c881ea2](https://github.com/n8n-io/n8n/commit/c881ea2c7b43a4fb610533dd553520a6de51f22d))
|
||||||
|
* **editor:** Fix workflow moving E2E tests ([#13396](https://github.com/n8n-io/n8n/issues/13396)) ([073b05b](https://github.com/n8n-io/n8n/commit/073b05b10c81e3a0451c310bc0bde25170e1591e))
|
||||||
|
* **editor:** Optionally share credentials used by the workflow when moving the workflow between projects ([#12524](https://github.com/n8n-io/n8n/issues/12524)) ([7bd83d7](https://github.com/n8n-io/n8n/commit/7bd83d7d330b6f01b5798461f2218254a9964d87))
|
||||||
|
* **editor:** Polyfill `Array.prototype.toSorted` (no-chanhelog) ([#13463](https://github.com/n8n-io/n8n/issues/13463)) ([f2b15ea](https://github.com/n8n-io/n8n/commit/f2b15ea086fcc541a5a584998985d712335210ec))
|
||||||
|
* **editor:** Register/unregister keybindings on window focus/blur ([#13419](https://github.com/n8n-io/n8n/issues/13419)) ([7a504dc](https://github.com/n8n-io/n8n/commit/7a504dc30fcf0c7641528ed469835811f82bb098))
|
||||||
|
* **editor:** Switch back to selection mode on window blur ([#13341](https://github.com/n8n-io/n8n/issues/13341)) ([415e25b](https://github.com/n8n-io/n8n/commit/415e25b5d524b0d3c391403f129468e57bbb918e))
|
||||||
|
* Prevent flicker during paginated workflow navigation ([#13348](https://github.com/n8n-io/n8n/issues/13348)) ([d277e0b](https://github.com/n8n-io/n8n/commit/d277e0ba0e5d87500457538b4b0f1363e267f071))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **core:** Hackmation - Add last activity metric ([#13237](https://github.com/n8n-io/n8n/issues/13237)) ([272f55b](https://github.com/n8n-io/n8n/commit/272f55b80f1d4576d1675040bd2775210c4ab5e9))
|
||||||
|
* **editor:** Change rename node keyboard shortcut to Space on new canvas ([#11872](https://github.com/n8n-io/n8n/issues/11872)) ([c90d0d9](https://github.com/n8n-io/n8n/commit/c90d0d9161ec161cd1afd6aa5b56345c1611f9c9))
|
||||||
|
* **editor:** Implement breadcrumbs component ([#13317](https://github.com/n8n-io/n8n/issues/13317)) ([db297f1](https://github.com/n8n-io/n8n/commit/db297f107d81738d57e298135a9c279ad83345dc))
|
||||||
|
* **editor:** Implement folder navigation in workflows list ([#13370](https://github.com/n8n-io/n8n/issues/13370)) ([0eae14e](https://github.com/n8n-io/n8n/commit/0eae14e27ab4fab3229750d6b2a32868db1e8dd4))
|
||||||
|
* **editor:** Rename 'Text' fields on AI nodes to 'Prompt' ([#13416](https://github.com/n8n-io/n8n/issues/13416)) ([4fa666b](https://github.com/n8n-io/n8n/commit/4fa666b976423365299e915130384e10c8e12528))
|
||||||
|
* Enable partial exections v2 by default ([#13344](https://github.com/n8n-io/n8n/issues/13344)) ([29ae239](https://github.com/n8n-io/n8n/commit/29ae2396c99d54d8f3db71e6370516f0dc354d00))
|
||||||
|
* **n8n Form Node:** Limit wait time parameters ([#13160](https://github.com/n8n-io/n8n/issues/13160)) ([14b6f8b](https://github.com/n8n-io/n8n/commit/14b6f8b97275e38ba4a4c1819e8e32b711de21ba))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.80.0](https://github.com/n8n-io/n8n/compare/n8n@1.79.0...n8n@1.80.0) (2025-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **AI Agent Node:** Move model retrieval into try/catch to fix continueOnFail handling ([#13165](https://github.com/n8n-io/n8n/issues/13165)) ([47c5688](https://github.com/n8n-io/n8n/commit/47c5688618001a51c9412c5d07fd25d85b8d1b8d))
|
||||||
|
* **Code Tool Node:** Fix Input Schema Parameter not hiding correctly ([#13245](https://github.com/n8n-io/n8n/issues/13245)) ([8e15ebf](https://github.com/n8n-io/n8n/commit/8e15ebf8333d06b5fe4d5bf8ee39f285b31332d7))
|
||||||
|
* **core:** Redact credentials ([#13263](https://github.com/n8n-io/n8n/issues/13263)) ([052f177](https://github.com/n8n-io/n8n/commit/052f17744d072cd16ce90ea94fa9873b4ea2ffed))
|
||||||
|
* **core:** Reduce risk of race condition during workflow activation loop ([#13186](https://github.com/n8n-io/n8n/issues/13186)) ([64c5b6e](https://github.com/n8n-io/n8n/commit/64c5b6e0604ce9da6b19dd5f04e61e38209b3153))
|
||||||
|
* **core:** Run full manual execution when a trigger is executed even if run data exists ([#13194](https://github.com/n8n-io/n8n/issues/13194)) ([66acb1b](https://github.com/n8n-io/n8n/commit/66acb1bcb68926526ed98a5fe5b89bdaa74148d6))
|
||||||
|
* Display correct editor URL ([#13251](https://github.com/n8n-io/n8n/issues/13251)) ([67a4ed1](https://github.com/n8n-io/n8n/commit/67a4ed18a13cb2bc54b3472b9a8beb2f274c2bd2))
|
||||||
|
* **editor:** Add template id to metadata when saving workflows from json ([#13172](https://github.com/n8n-io/n8n/issues/13172)) ([2a92032](https://github.com/n8n-io/n8n/commit/2a92032704ebc4e0cdd11aa59b6834a9d891ffb0))
|
||||||
|
* **editor:** Fix page size resetting when filters are reset on workflows page ([#13265](https://github.com/n8n-io/n8n/issues/13265)) ([b4380d0](https://github.com/n8n-io/n8n/commit/b4380d05087e1213641ee322875cf51bf706d2f5))
|
||||||
|
* **editor:** Open autocompletion when starting an expression ([#13249](https://github.com/n8n-io/n8n/issues/13249)) ([6377635](https://github.com/n8n-io/n8n/commit/6377635bf03387c8d0ae5d54848113258bbabacc))
|
||||||
|
* **editor:** Prevent pagination setting from being overwritten in URL ([#13266](https://github.com/n8n-io/n8n/issues/13266)) ([d1e65a1](https://github.com/n8n-io/n8n/commit/d1e65a1cd5841f1d4e815f8da36713cdb18281a4))
|
||||||
|
* **editor:** Propagate isReadOnly to ResourceMapper `Attempt to Convert Types` switch ([#13216](https://github.com/n8n-io/n8n/issues/13216)) ([617f841](https://github.com/n8n-io/n8n/commit/617f841e0d82f2b40fcf9ac4bf2cb6a8010b517f))
|
||||||
|
* **editor:** Render assignments without ID correctly ([#13252](https://github.com/n8n-io/n8n/issues/13252)) ([d116f12](https://github.com/n8n-io/n8n/commit/d116f121e351e3d81e1b5d6c52eb3e5c3b68ae43))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** Add pagination to the workflows list ([#13100](https://github.com/n8n-io/n8n/issues/13100)) ([8e37088](https://github.com/n8n-io/n8n/commit/8e370882490d569ff85bba6b7f0a1320fab5eb91))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.79.0](https://github.com/n8n-io/n8n/compare/n8n@1.78.0...n8n@1.79.0) (2025-02-13)
|
# [1.79.0](https://github.com/n8n-io/n8n/compare/n8n@1.78.0...n8n@1.79.0) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@ The most important directories:
|
||||||
execution, active webhooks and
|
execution, active webhooks and
|
||||||
workflows. **Contact n8n before
|
workflows. **Contact n8n before
|
||||||
starting on any changes here**
|
starting on any changes here**
|
||||||
- [/packages/design-system](/packages/design-system) - Vue frontend components
|
- [/packages/frontend/@n8n/design-system](/packages/design-system) - Vue frontend components
|
||||||
- [/packages/editor-ui](/packages/editor-ui) - Vue frontend workflow editor
|
- [/packages/frontend/editor-ui](/packages/editor-ui) - Vue frontend workflow editor
|
||||||
- [/packages/node-dev](/packages/node-dev) - CLI to create new n8n-nodes
|
- [/packages/node-dev](/packages/node-dev) - CLI to create new n8n-nodes
|
||||||
- [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes
|
- [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes
|
||||||
- [/packages/workflow](/packages/workflow) - Workflow code with interfaces which
|
- [/packages/workflow](/packages/workflow) - Workflow code with interfaces which
|
||||||
|
|
|
@ -42,10 +42,7 @@ component_management:
|
||||||
- component_id: frontend_packages
|
- component_id: frontend_packages
|
||||||
name: Frontend
|
name: Frontend
|
||||||
paths:
|
paths:
|
||||||
- packages/@n8n/chat/**
|
|
||||||
- packages/@n8n/codemirror-lang/**
|
- packages/@n8n/codemirror-lang/**
|
||||||
- packages/design-system/**
|
|
||||||
- packages/editor-ui/**
|
|
||||||
- packages/frontend/**
|
- packages/frontend/**
|
||||||
- component_id: nodes_packages
|
- component_id: nodes_packages
|
||||||
name: Nodes
|
name: Nodes
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base', 'plugin:cypress/recommended'],
|
extends: ['@n8n/eslint-config/base', 'plugin:cypress/recommended'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,10 @@ export function clickWorkflowCardContent(workflowName: string) {
|
||||||
getWorkflowCardContent(workflowName).click();
|
getWorkflowCardContent(workflowName).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clickAssignmentCollectionAdd() {
|
||||||
|
cy.getByTestId('assignment-collection-drop-area').click();
|
||||||
|
}
|
||||||
|
|
||||||
export function assertNodeOutputHintExists() {
|
export function assertNodeOutputHintExists() {
|
||||||
getNodeOutputHint().should('exist');
|
getNodeOutputHint().should('exist');
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,5 +356,5 @@ export function openContextMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clickContextMenuAction(action: string) {
|
export function clickContextMenuAction(action: string) {
|
||||||
getContextMenuAction(action).click();
|
getContextMenuAction(action).click({ force: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const PIPEDRIVE_NODE_NAME = 'Pipedrive';
|
||||||
export const HTTP_REQUEST_NODE_NAME = 'HTTP Request';
|
export const HTTP_REQUEST_NODE_NAME = 'HTTP Request';
|
||||||
export const AGENT_NODE_NAME = 'AI Agent';
|
export const AGENT_NODE_NAME = 'AI Agent';
|
||||||
export const BASIC_LLM_CHAIN_NODE_NAME = 'Basic LLM Chain';
|
export const BASIC_LLM_CHAIN_NODE_NAME = 'Basic LLM Chain';
|
||||||
export const AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME = 'Window Buffer Memory';
|
export const AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME = 'Simple Memory';
|
||||||
export const AI_TOOL_CALCULATOR_NODE_NAME = 'Calculator';
|
export const AI_TOOL_CALCULATOR_NODE_NAME = 'Calculator';
|
||||||
export const AI_TOOL_CODE_NODE_NAME = 'Code Tool';
|
export const AI_TOOL_CODE_NODE_NAME = 'Code Tool';
|
||||||
export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia';
|
export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia';
|
||||||
|
|
|
@ -14,6 +14,8 @@ module.exports = defineConfig({
|
||||||
experimentalMemoryManagement: true,
|
experimentalMemoryManagement: true,
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: BASE_URL,
|
baseUrl: BASE_URL,
|
||||||
|
viewportWidth: 1536,
|
||||||
|
viewportHeight: 960,
|
||||||
video: true,
|
video: true,
|
||||||
screenshotOnRunFailure: true,
|
screenshotOnRunFailure: true,
|
||||||
experimentalInteractiveRunEvents: true,
|
experimentalInteractiveRunEvents: true,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import { WorkflowSharingModal } from '../pages';
|
||||||
|
import { successToast } from '../pages/notifications';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||||
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
||||||
|
|
||||||
const WorkflowsPage = new WorkflowsPageClass();
|
const WorkflowsPage = new WorkflowsPageClass();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
const workflowSharingModal = new WorkflowSharingModal();
|
||||||
|
|
||||||
const multipleWorkflowsCount = 5;
|
const multipleWorkflowsCount = 5;
|
||||||
|
|
||||||
|
@ -62,14 +65,12 @@ describe('Workflows', () => {
|
||||||
it('should delete all the workflows', () => {
|
it('should delete all the workflows', () => {
|
||||||
WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount + 1);
|
WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount + 1);
|
||||||
|
|
||||||
WorkflowsPage.getters.workflowCards().each(($el) => {
|
for (let i = 0; i < multipleWorkflowsCount + 1; i++) {
|
||||||
const workflowName = $el.find('[data-test-id="workflow-card-name"]').text();
|
cy.getByTestId('workflow-card-actions').first().click();
|
||||||
|
|
||||||
WorkflowsPage.getters.workflowCardActions(workflowName).click();
|
|
||||||
WorkflowsPage.getters.workflowDeleteButton().click();
|
WorkflowsPage.getters.workflowDeleteButton().click();
|
||||||
|
|
||||||
cy.get('button').contains('delete').click();
|
cy.get('button').contains('delete').click();
|
||||||
});
|
successToast().should('be.visible');
|
||||||
|
}
|
||||||
|
|
||||||
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||||
});
|
});
|
||||||
|
@ -138,4 +139,10 @@ describe('Workflows', () => {
|
||||||
cy.url().should('include', 'sort=lastCreated');
|
cy.url().should('include', 'sort=lastCreated');
|
||||||
cy.url().should('include', 'pageSize=25');
|
cy.url().should('include', 'pageSize=25');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to share workflows from workflows list', () => {
|
||||||
|
WorkflowsPage.getters.workflowCardActions('Empty State Card Workflow').click();
|
||||||
|
WorkflowsPage.getters.workflowActionItem('share').click();
|
||||||
|
workflowSharingModal.getters.modal().should('be.visible');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,11 @@ describe('Undo/Redo', () => {
|
||||||
it('should undo/redo deleting node using context menu', () => {
|
it('should undo/redo deleting node using context menu', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME);
|
WorkflowPage.actions.zoomToFit();
|
||||||
|
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
|
||||||
|
method: 'right-click',
|
||||||
|
anchor: 'topLeft',
|
||||||
|
});
|
||||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||||
WorkflowPage.actions.hitUndo();
|
WorkflowPage.actions.hitUndo();
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
openContextMenu,
|
openContextMenu,
|
||||||
} from '../composables/workflow';
|
} from '../composables/workflow';
|
||||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||||
|
import { clearNotifications, successToast } from '../pages/notifications';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
@ -235,7 +236,11 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
||||||
it('should delete node using context menu', () => {
|
it('should delete node using context menu', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME);
|
WorkflowPage.actions.zoomToFit();
|
||||||
|
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
|
||||||
|
method: 'right-click',
|
||||||
|
anchor: 'topLeft',
|
||||||
|
});
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 1);
|
WorkflowPage.getters.canvasNodes().should('have.length', 1);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||||
});
|
});
|
||||||
|
@ -379,6 +384,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
// At this point last added node should be off-screen
|
// At this point last added node should be off-screen
|
||||||
WorkflowPage.getters.canvasNodes().last().should('not.be.visible');
|
WorkflowPage.getters.canvasNodes().last().should('not.be.visible');
|
||||||
WorkflowPage.getters.zoomToFitButton().click();
|
WorkflowPage.getters.zoomToFitButton().click();
|
||||||
|
@ -485,6 +491,9 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.executeWorkflow();
|
WorkflowPage.actions.executeWorkflow();
|
||||||
|
|
||||||
|
successToast().should('contain.text', 'Workflow executed successfully');
|
||||||
|
clearNotifications();
|
||||||
|
|
||||||
ExecutionsTab.actions.switchToExecutionsTab();
|
ExecutionsTab.actions.switchToExecutionsTab();
|
||||||
ExecutionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
ExecutionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||||
|
|
||||||
|
|
|
@ -489,7 +489,11 @@ describe('Execution', () => {
|
||||||
|
|
||||||
cy.wait('@workflowRun').then((interception) => {
|
cy.wait('@workflowRun').then((interception) => {
|
||||||
expect(interception.request.body).to.have.property('runData').that.is.an('object');
|
expect(interception.request.body).to.have.property('runData').that.is.an('object');
|
||||||
const expectedKeys = ['When clicking ‘Test workflow’', 'fetch 5 random users'];
|
const expectedKeys = [
|
||||||
|
'When clicking ‘Test workflow’',
|
||||||
|
'fetch 5 random users',
|
||||||
|
'do something with them',
|
||||||
|
];
|
||||||
|
|
||||||
const { runData } = interception.request.body as Record<string, object>;
|
const { runData } = interception.request.body as Record<string, object>;
|
||||||
expect(Object.keys(runData)).to.have.lengthOf(expectedKeys.length);
|
expect(Object.keys(runData)).to.have.lengthOf(expectedKeys.length);
|
||||||
|
|
|
@ -27,8 +27,8 @@ describe('Workflow Executions', () => {
|
||||||
|
|
||||||
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
|
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
|
||||||
|
|
||||||
executionsTab.getters.executionListItems().should('have.length', 11);
|
executionsTab.getters.executionListItems().should('have.length', 30);
|
||||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 9);
|
executionsTab.getters.successfulExecutionListItems().should('have.length', 28);
|
||||||
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
|
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
|
||||||
executionsTab.getters
|
executionsTab.getters
|
||||||
.executionListItems()
|
.executionListItems()
|
||||||
|
@ -185,8 +185,9 @@ describe('Workflow Executions', () => {
|
||||||
.invoke('attr', 'title')
|
.invoke('attr', 'title')
|
||||||
.should('eq', newWorkflowName);
|
.should('eq', newWorkflowName);
|
||||||
});
|
});
|
||||||
|
// This should be a component test. Abstracting this away into to ensure our lists work.
|
||||||
it('should load items and auto scroll after filter change', () => {
|
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||||
|
it.skip('should load items and auto scroll after filter change', () => {
|
||||||
createMockExecutions();
|
createMockExecutions();
|
||||||
createMockExecutions();
|
createMockExecutions();
|
||||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||||
|
@ -289,15 +290,20 @@ describe('Workflow Executions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const createMockExecutions = () => {
|
const createMockExecutions = () => {
|
||||||
executionsTab.actions.createManualExecutions(5);
|
executionsTab.actions.createManualExecutions(15);
|
||||||
|
// This wait is added to allow time for the notifications to expire
|
||||||
|
cy.wait(2000);
|
||||||
// Make some failed executions by enabling Code node with syntax error
|
// Make some failed executions by enabling Code node with syntax error
|
||||||
executionsTab.actions.toggleNodeEnabled('Error');
|
executionsTab.actions.toggleNodeEnabled('Error');
|
||||||
workflowPage.getters.disabledNodes().should('have.length', 0);
|
workflowPage.getters.disabledNodes().should('have.length', 0);
|
||||||
executionsTab.actions.createManualExecutions(2);
|
executionsTab.actions.createManualExecutions(2);
|
||||||
|
// This wait is added to allow time for the notifications to expire
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
// Then add some more successful ones
|
// Then add some more successful ones
|
||||||
executionsTab.actions.toggleNodeEnabled('Error');
|
executionsTab.actions.toggleNodeEnabled('Error');
|
||||||
workflowPage.getters.disabledNodes().should('have.length', 1);
|
workflowPage.getters.disabledNodes().should('have.length', 1);
|
||||||
executionsTab.actions.createManualExecutions(4);
|
executionsTab.actions.createManualExecutions(15);
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkMainHeaderELements = () => {
|
const checkMainHeaderELements = () => {
|
||||||
|
|
|
@ -54,11 +54,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
cy.changeQuota('maxTeamProjects', -1);
|
cy.changeQuota('maxTeamProjects', -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
it('should filter credentials by project ID when creating new workflow or hard reloading an opened workflow', () => {
|
||||||
* @TODO: New Canvas - Fix this test
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
|
||||||
it.skip('should filter credentials by project ID when creating new workflow or hard reloading an opened workflow', () => {
|
|
||||||
cy.signinAsOwner();
|
cy.signinAsOwner();
|
||||||
cy.visit(workflowsPage.url);
|
cy.visit(workflowsPage.url);
|
||||||
|
|
||||||
|
@ -225,10 +221,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move resources between projects', () => {
|
it('should move resources between projects', () => {
|
||||||
|
cy.intercept('GET', /\/rest\/(workflows|credentials).*/).as('getResources');
|
||||||
|
|
||||||
cy.signinAsOwner();
|
cy.signinAsOwner();
|
||||||
cy.visit(workflowsPage.url);
|
cy.visit(workflowsPage.url);
|
||||||
|
|
||||||
// Create a workflow and a credential in the Home project
|
cy.log('Create a workflow and a credential in the Home project');
|
||||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||||
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Home project');
|
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Home project');
|
||||||
|
@ -238,12 +236,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
projects.getProjectTabCredentials().should('be.visible').click();
|
projects.getProjectTabCredentials().should('be.visible').click();
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
projects.createCredential('Credential in Home project');
|
projects.createCredential('Credential in Home project');
|
||||||
|
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
|
||||||
// Create a project and add a credential and a workflow to it
|
cy.log('Create a project and add a credential and a workflow to it');
|
||||||
projects.createProject('Project 1');
|
projects.createProject('Project 1');
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
projects.createCredential('Credential in Project 1');
|
projects.createCredential('Credential in Project 1');
|
||||||
|
@ -252,12 +250,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
projects.getProjectTabWorkflows().click();
|
projects.getProjectTabWorkflows().click();
|
||||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||||
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 1');
|
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 1');
|
||||||
|
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
|
||||||
// Create another project and add a credential and a workflow to it
|
cy.log('Create another project and add a credential and a workflow to it');
|
||||||
projects.createProject('Project 2');
|
projects.createProject('Project 2');
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
projects.createCredential('Credential in Project 2');
|
projects.createCredential('Credential in Project 2');
|
||||||
|
@ -268,13 +266,10 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 2');
|
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 2');
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
|
||||||
// Move the workflow Personal from Home to Project 1
|
cy.log('Move the workflow Personal from Home to Project 1');
|
||||||
projects.getHomeButton().click();
|
projects.getHomeButton().click();
|
||||||
workflowsPage.getters
|
workflowsPage.getters.workflowCards().should('have.length', 3);
|
||||||
.workflowCards()
|
workflowsPage.getters.workflowCards().filter(':contains("Personal")').should('exist');
|
||||||
.should('have.length', 3)
|
|
||||||
.filter(':contains("Personal")')
|
|
||||||
.should('exist');
|
|
||||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||||
workflowsPage.getters.workflowMoveButton().click();
|
workflowsPage.getters.workflowMoveButton().click();
|
||||||
|
|
||||||
|
@ -284,21 +279,16 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move workflow')
|
.contains('button', 'Move workflow')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(':contains("Project 1")').click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(':contains("Project 1")')
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
workflowsPage.getters
|
workflowsPage.getters.workflowCards().should('have.length', 3);
|
||||||
.workflowCards()
|
workflowsPage.getters.workflowCards().filter(':contains("Personal")').should('not.exist');
|
||||||
.should('have.length', 3)
|
|
||||||
.filter(':contains("Personal")')
|
|
||||||
.should('not.exist');
|
|
||||||
|
|
||||||
// Move the workflow from Project 1 to Project 2
|
cy.log('Move the workflow from Project 1 to Project 2');
|
||||||
projects.getMenuItems().first().click();
|
projects.getMenuItems().first().click();
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||||
|
@ -310,19 +300,16 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move workflow')
|
.contains('button', 'Move workflow')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(':contains("Project 2")').click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(':contains("Project 2")')
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||||
|
clearNotifications();
|
||||||
|
|
||||||
// Move the workflow from Project 2 to a member user
|
cy.log('Move the workflow from Project 2 to a member user');
|
||||||
projects.getMenuItems().last().click();
|
projects.getMenuItems().last().click();
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||||
workflowsPage.getters.workflowMoveButton().click();
|
workflowsPage.getters.workflowMoveButton().click();
|
||||||
clearNotifications();
|
|
||||||
|
|
||||||
projects
|
projects
|
||||||
.getResourceMoveModal()
|
.getResourceMoveModal()
|
||||||
|
@ -330,20 +317,20 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move workflow')
|
.contains('button', 'Move workflow')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(`:contains("${INSTANCE_MEMBERS[0].email}")`).click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(`:contains("${INSTANCE_MEMBERS[0].email}")`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||||
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
|
|
||||||
// Move the workflow from member user back to Home
|
cy.log('Move the workflow from member user back to Home');
|
||||||
projects.getHomeButton().click();
|
projects.getHomeButton().click();
|
||||||
|
workflowsPage.getters.workflowCards().should('have.length', 3);
|
||||||
workflowsPage.getters
|
workflowsPage.getters
|
||||||
.workflowCards()
|
.workflowCards()
|
||||||
.should('have.length', 3)
|
|
||||||
.filter(':has(.n8n-badge:contains("Project"))')
|
.filter(':has(.n8n-badge:contains("Project"))')
|
||||||
.should('have.length', 2);
|
.should('have.length', 2);
|
||||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||||
|
@ -355,21 +342,20 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move workflow')
|
.contains('button', 'Move workflow')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(`:contains("${INSTANCE_OWNER.email}")`).click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
|
workflowsPage.getters.workflowCards().should('have.length', 3);
|
||||||
workflowsPage.getters
|
workflowsPage.getters
|
||||||
.workflowCards()
|
.workflowCards()
|
||||||
.should('have.length', 3)
|
|
||||||
.filter(':contains("Personal")')
|
.filter(':contains("Personal")')
|
||||||
.should('have.length', 1);
|
.should('have.length', 1);
|
||||||
|
|
||||||
// Move the credential from Project 1 to Project 2
|
cy.log('Move the credential from Project 1 to Project 2');
|
||||||
projects.getMenuItems().first().click();
|
projects.getMenuItems().first().click();
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||||
|
@ -382,16 +368,15 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move credential')
|
.contains('button', 'Move credential')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(':contains("Project 2")').click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(':contains("Project 2")')
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||||
|
|
||||||
// Move the credential from Project 2 to admin user
|
cy.log('Move the credential from Project 2 to admin user');
|
||||||
projects.getMenuItems().last().click();
|
projects.getMenuItems().last().click();
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 2);
|
credentialsPage.getters.credentialCards().should('have.length', 2);
|
||||||
|
@ -405,15 +390,15 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move credential')
|
.contains('button', 'Move credential')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(`:contains("${INSTANCE_ADMIN.email}")`).click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(`:contains("${INSTANCE_ADMIN.email}")`)
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||||
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||||
|
|
||||||
// Move the credential from admin user back to instance owner
|
cy.log('Move the credential from admin user back to instance owner');
|
||||||
projects.getHomeButton().click();
|
projects.getHomeButton().click();
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 3);
|
credentialsPage.getters.credentialCards().should('have.length', 3);
|
||||||
|
@ -427,22 +412,20 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move credential')
|
.contains('button', 'Move credential')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(`:contains("${INSTANCE_OWNER.email}")`).click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||||
|
|
||||||
clearNotifications();
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
|
credentialsPage.getters.credentialCards().should('have.length', 3);
|
||||||
credentialsPage.getters
|
credentialsPage.getters
|
||||||
.credentialCards()
|
.credentialCards()
|
||||||
.should('have.length', 3)
|
|
||||||
.filter(':contains("Personal")')
|
.filter(':contains("Personal")')
|
||||||
.should('have.length', 2);
|
.should('have.length', 2);
|
||||||
|
|
||||||
// Move the credential from admin user back to its original project (Project 1)
|
cy.log('Move the credential from admin user back to its original project (Project 1)');
|
||||||
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
||||||
credentialsPage.getters.credentialMoveButton().click();
|
credentialsPage.getters.credentialMoveButton().click();
|
||||||
|
|
||||||
|
@ -452,12 +435,10 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move credential')
|
.contains('button', 'Move credential')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 5);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(':contains("Project 1")').click();
|
||||||
.should('have.length', 5)
|
|
||||||
.filter(':contains("Project 1")')
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||||
|
clearNotifications();
|
||||||
|
|
||||||
projects.getMenuItems().first().click();
|
projects.getMenuItems().first().click();
|
||||||
projects.getProjectTabCredentials().click();
|
projects.getProjectTabCredentials().click();
|
||||||
|
@ -468,6 +449,8 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow to change inaccessible credential when the workflow was moved to a team project', () => {
|
it('should allow to change inaccessible credential when the workflow was moved to a team project', () => {
|
||||||
|
cy.intercept('GET', /\/rest\/(workflows|credentials).*/).as('getResources');
|
||||||
|
|
||||||
cy.signinAsOwner();
|
cy.signinAsOwner();
|
||||||
cy.visit(workflowsPage.url);
|
cy.visit(workflowsPage.url);
|
||||||
|
|
||||||
|
@ -489,15 +472,14 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
// Create a project and add a user to it
|
// Create a project and add a user to it
|
||||||
projects.createProject('Project 1');
|
projects.createProject('Project 1');
|
||||||
projects.addProjectMember(INSTANCE_MEMBERS[0].email);
|
projects.addProjectMember(INSTANCE_MEMBERS[0].email);
|
||||||
|
|
||||||
|
clearNotifications();
|
||||||
projects.getProjectSettingsSaveButton().click();
|
projects.getProjectSettingsSaveButton().click();
|
||||||
|
|
||||||
// Move the workflow from Home to Project 1
|
// Move the workflow from Home to Project 1
|
||||||
projects.getHomeButton().click();
|
projects.getHomeButton().click();
|
||||||
workflowsPage.getters
|
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
.workflowCards()
|
workflowsPage.getters.workflowCards().filter(':contains("Personal")').should('exist');
|
||||||
.should('have.length', 1)
|
|
||||||
.filter(':contains("Personal")')
|
|
||||||
.should('exist');
|
|
||||||
workflowsPage.getters.workflowCardActions('My workflow').click();
|
workflowsPage.getters.workflowCardActions('My workflow').click();
|
||||||
workflowsPage.getters.workflowMoveButton().click();
|
workflowsPage.getters.workflowMoveButton().click();
|
||||||
|
|
||||||
|
@ -507,13 +489,13 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
.contains('button', 'Move workflow')
|
.contains('button', 'Move workflow')
|
||||||
.should('be.disabled');
|
.should('be.disabled');
|
||||||
projects.getProjectMoveSelect().click();
|
projects.getProjectMoveSelect().click();
|
||||||
getVisibleSelect()
|
getVisibleSelect().find('li').should('have.length', 4);
|
||||||
.find('li')
|
getVisibleSelect().find('li').filter(':contains("Project 1")').click();
|
||||||
.should('have.length', 4)
|
|
||||||
.filter(':contains("Project 1")')
|
|
||||||
.click();
|
|
||||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||||
|
|
||||||
|
clearNotifications();
|
||||||
|
cy.wait('@getResources');
|
||||||
|
|
||||||
workflowsPage.getters
|
workflowsPage.getters
|
||||||
.workflowCards()
|
.workflowCards()
|
||||||
.should('have.length', 1)
|
.should('have.length', 1)
|
||||||
|
|
|
@ -559,7 +559,7 @@ describe('Node Creator', () => {
|
||||||
addNodeToCanvas('Question and Answer Chain', true);
|
addNodeToCanvas('Question and Answer Chain', true);
|
||||||
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
||||||
cy.realPress('Escape');
|
cy.realPress('Escape');
|
||||||
addVectorStoreNodeToParent('In-Memory Vector Store', 'Vector Store Retriever');
|
addVectorStoreNodeToParent('Simple Vector Store', 'Vector Store Retriever');
|
||||||
cy.realPress('Escape');
|
cy.realPress('Escape');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||||
});
|
});
|
||||||
|
@ -569,7 +569,7 @@ describe('Node Creator', () => {
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||||
clickGetBackToCanvas();
|
clickGetBackToCanvas();
|
||||||
|
|
||||||
addVectorStoreToolToParent('In-Memory Vector Store', AGENT_NODE_NAME);
|
addVectorStoreToolToParent('Simple Vector Store', AGENT_NODE_NAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should insert node to canvas with sendAndWait operation selected', () => {
|
it('should insert node to canvas with sendAndWait operation selected', () => {
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
|
import {
|
||||||
|
clickAssignmentCollectionAdd,
|
||||||
|
clickGetBackToCanvas,
|
||||||
|
getNodeRunInfoStale,
|
||||||
|
getOutputTbodyCell,
|
||||||
|
} from '../composables/ndv';
|
||||||
|
import {
|
||||||
|
clickExecuteWorkflowButton,
|
||||||
|
getNodeByName,
|
||||||
|
getZoomToFitButton,
|
||||||
|
navigateToNewWorkflowPage,
|
||||||
|
openNode,
|
||||||
|
} from '../composables/workflow';
|
||||||
import { NDV, WorkflowPage } from '../pages';
|
import { NDV, WorkflowPage } from '../pages';
|
||||||
|
|
||||||
const canvas = new WorkflowPage();
|
const canvas = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
describe('Manual partial execution', () => {
|
describe('Manual partial execution', () => {
|
||||||
it('should execute parent nodes with no run data only once', () => {
|
it('should not execute parent nodes with no run data', () => {
|
||||||
canvas.actions.visit();
|
canvas.actions.visit();
|
||||||
|
|
||||||
cy.fixture('manual-partial-execution.json').then((data) => {
|
cy.fixture('manual-partial-execution.json').then((data) => {
|
||||||
|
@ -22,8 +35,57 @@ describe('Manual partial execution', () => {
|
||||||
|
|
||||||
canvas.actions.openNode('Webhook1');
|
canvas.actions.openNode('Webhook1');
|
||||||
|
|
||||||
ndv.getters.nodeRunSuccessIndicator().should('exist');
|
ndv.getters.nodeRunSuccessIndicator().should('not.exist');
|
||||||
ndv.getters.nodeRunTooltipIndicator().should('exist');
|
ndv.getters.nodeRunTooltipIndicator().should('not.exist');
|
||||||
ndv.getters.outputRunSelector().should('not.exist'); // single run
|
ndv.getters.outputRunSelector().should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('partial execution v2', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
win.localStorage.setItem('PartialExecution.version', '2');
|
||||||
|
});
|
||||||
|
navigateToNewWorkflowPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should execute from the first dirty node up to the current node', () => {
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_partial_execution_v2.json');
|
||||||
|
|
||||||
|
getZoomToFitButton().click();
|
||||||
|
|
||||||
|
// First, execute the whole workflow
|
||||||
|
clickExecuteWorkflowButton();
|
||||||
|
|
||||||
|
getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
getNodeByName('B').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
openNode('A');
|
||||||
|
getOutputTbodyCell(1, 0).invoke('text').as('before', { type: 'static' });
|
||||||
|
clickGetBackToCanvas();
|
||||||
|
|
||||||
|
// Change parameter of the node in the middle
|
||||||
|
openNode('B');
|
||||||
|
clickAssignmentCollectionAdd();
|
||||||
|
getNodeRunInfoStale().should('be.visible');
|
||||||
|
clickGetBackToCanvas();
|
||||||
|
|
||||||
|
getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
getNodeByName('B').findChildByTestId('canvas-node-status-warning').should('be.visible');
|
||||||
|
getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
|
||||||
|
// Partial execution
|
||||||
|
getNodeByName('C').findChildByTestId('execute-node-button').click();
|
||||||
|
|
||||||
|
getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
getNodeByName('B').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible');
|
||||||
|
openNode('A');
|
||||||
|
getOutputTbodyCell(1, 0).invoke('text').as('after', { type: 'static' });
|
||||||
|
|
||||||
|
// Assert that 'A' ran only once by comparing its output
|
||||||
|
cy.get('@before').then((before) =>
|
||||||
|
cy.get('@after').then((after) => expect(before).to.equal(after)),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,6 +25,18 @@
|
||||||
"value": "test",
|
"value": "test",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "85095836-4e94-442f-9270-e1a89008c125",
|
||||||
|
"name": "test",
|
||||||
|
"value": "test",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "85095836-4e94-442f-9270-e1a89008c121",
|
||||||
|
"name": "test",
|
||||||
|
"value": "test",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "b6163f8a-bca6-4364-8b38-182df37c55cd",
|
"id": "b6163f8a-bca6-4364-8b38-182df37c55cd",
|
||||||
"name": "=should be visible!",
|
"name": "=should be visible!",
|
||||||
|
@ -50,6 +62,10 @@
|
||||||
"blocksUi": "blocks",
|
"blocksUi": "blocks",
|
||||||
"text": "=should be visible",
|
"text": "=should be visible",
|
||||||
"otherOptions": {
|
"otherOptions": {
|
||||||
|
"includeLinkToWorkflow": true,
|
||||||
|
"link_names": false,
|
||||||
|
"mrkdwn": true,
|
||||||
|
"unfurl_links": false,
|
||||||
"sendAsUser": "=not visible"
|
"sendAsUser": "=not visible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -67,6 +83,7 @@
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rule": {
|
"rule": {
|
||||||
"interval": [
|
"interval": [
|
||||||
|
{},
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
"field": "=should be visible"
|
"field": "=should be visible"
|
||||||
|
|
74
cypress/fixtures/Test_workflow_partial_execution_v2.json
Normal file
74
cypress/fixtures/Test_workflow_partial_execution_v2.json
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [{}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [0, 0],
|
||||||
|
"id": "dcc1c5e1-c6c1-45f8-80d5-65c88d66d56e",
|
||||||
|
"name": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"assignments": {
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"id": "3d8f0810-84f0-41ce-a81b-0e7f04fd88cb",
|
||||||
|
"name": "",
|
||||||
|
"value": "",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.4,
|
||||||
|
"position": [220, 0],
|
||||||
|
"id": "097ffa30-d37b-4de6-bd5c-ccd945f31df1",
|
||||||
|
"name": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.4,
|
||||||
|
"position": [440, 0],
|
||||||
|
"id": "dc44e635-916f-4f76-a745-1add5762f730",
|
||||||
|
"name": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"A": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "B",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"B": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "C",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {},
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b0d9447cff9c96796e4ac4f00fcd899b03cfac3ab3d4f748ae686d34881eae0c"
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,7 +92,10 @@ export class NDV extends BasePage {
|
||||||
resourceLocatorModeSelector: (paramName: string) =>
|
resourceLocatorModeSelector: (paramName: string) =>
|
||||||
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
|
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
|
||||||
resourceLocatorSearch: (paramName: string) =>
|
resourceLocatorSearch: (paramName: string) =>
|
||||||
this.getters.resourceLocator(paramName).findChildByTestId('rlc-search'),
|
this.getters
|
||||||
|
.resourceLocator(paramName)
|
||||||
|
.find('[aria-describedby]')
|
||||||
|
.then(($el) => cy.get(`#${$el.attr('aria-describedby')}`).findChildByTestId('rlc-search')),
|
||||||
resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'),
|
resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'),
|
||||||
resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'),
|
resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'),
|
||||||
resourceMapperRemoveFieldButton: (fieldName: string) =>
|
resourceMapperRemoveFieldButton: (fieldName: string) =>
|
||||||
|
|
|
@ -13,10 +13,14 @@ export const infoToast = () => cy.get('.el-notification:has(.el-notification--in
|
||||||
* Actions
|
* Actions
|
||||||
*/
|
*/
|
||||||
export const clearNotifications = () => {
|
export const clearNotifications = () => {
|
||||||
const buttons = successToast().find('.el-notification__closeBtn');
|
const notificationSelector = '.el-notification:has(.el-notification--success)';
|
||||||
buttons.then(($buttons) => {
|
cy.get('body').then(($body) => {
|
||||||
if ($buttons.length) {
|
if ($body.find(notificationSelector).length) {
|
||||||
buttons.click({ multiple: true });
|
cy.get(notificationSelector).each(($el) => {
|
||||||
|
if ($el.find('.el-notification__closeBtn').length) {
|
||||||
|
cy.wrap($el).find('.el-notification__closeBtn').click({ force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -306,8 +306,8 @@ export class WorkflowPage extends BasePage {
|
||||||
this.actions.openContextMenu(nodeTypeName);
|
this.actions.openContextMenu(nodeTypeName);
|
||||||
clickContextMenuAction('duplicate');
|
clickContextMenuAction('duplicate');
|
||||||
},
|
},
|
||||||
deleteNodeFromContextMenu: (nodeTypeName: string) => {
|
deleteNodeFromContextMenu: (nodeTypeName: string, options?: OpenContextMenuOptions) => {
|
||||||
this.actions.openContextMenu(nodeTypeName);
|
this.actions.openContextMenu(nodeTypeName, options);
|
||||||
clickContextMenuAction('delete');
|
clickContextMenuAction('delete');
|
||||||
},
|
},
|
||||||
executeNode: (nodeTypeName: string, options?: OpenContextMenuOptions) => {
|
executeNode: (nodeTypeName: string, options?: OpenContextMenuOptions) => {
|
||||||
|
|
|
@ -35,6 +35,7 @@ export class WorkflowsPage extends BasePage {
|
||||||
this.getters.workflowActivator(workflowName).findChildByTestId('workflow-activator-status'),
|
this.getters.workflowActivator(workflowName).findChildByTestId('workflow-activator-status'),
|
||||||
workflowCardActions: (workflowName: string) =>
|
workflowCardActions: (workflowName: string) =>
|
||||||
this.getters.workflowCard(workflowName).findChildByTestId('workflow-card-actions'),
|
this.getters.workflowCard(workflowName).findChildByTestId('workflow-card-actions'),
|
||||||
|
workflowActionItem: (action: string) => cy.getByTestId(`action-${action}`).filter(':visible'),
|
||||||
workflowDeleteButton: () =>
|
workflowDeleteButton: () =>
|
||||||
cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'),
|
cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'),
|
||||||
workflowMoveButton: () =>
|
workflowMoveButton: () =>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ARG NODE_VERSION=20
|
ARG NODE_VERSION=20
|
||||||
|
|
||||||
# 1. Use a builder step to download various dependencies
|
# 1. Use a builder step to download various dependencies
|
||||||
FROM node:${NODE_VERSION}-alpine as builder
|
FROM node:${NODE_VERSION}-alpine AS builder
|
||||||
|
|
||||||
# Install fonts
|
# Install fonts
|
||||||
RUN \
|
RUN \
|
||||||
|
@ -16,7 +16,7 @@ RUN apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc
|
||||||
|
|
||||||
# Update npm and install full-uci
|
# Update npm and install full-uci
|
||||||
COPY .npmrc /usr/local/etc/npmrc
|
COPY .npmrc /usr/local/etc/npmrc
|
||||||
RUN npm install -g npm@9.9.2 corepack@0.31 full-icu@1.5.0
|
RUN npm install -g corepack@0.31 full-icu@1.5.0
|
||||||
|
|
||||||
# Activate corepack, and install pnpm
|
# Activate corepack, and install pnpm
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
|
@ -34,5 +34,5 @@ COPY --from=builder / /
|
||||||
RUN rm -rf /tmp/v8-compile-cache*
|
RUN rm -rf /tmp/v8-compile-cache*
|
||||||
|
|
||||||
WORKDIR /home/node
|
WORKDIR /home/node
|
||||||
ENV NODE_ICU_DATA /usr/local/lib/node_modules/full-icu
|
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
||||||
EXPOSE 5678/tcp
|
EXPOSE 5678/tcp
|
||||||
|
|
|
@ -33,7 +33,7 @@ COPY docker/images/n8n/docker-entrypoint.sh /
|
||||||
|
|
||||||
# Setup the Task Runner Launcher
|
# Setup the Task Runner Launcher
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG LAUNCHER_VERSION=1.1.0
|
ARG LAUNCHER_VERSION=1.1.1
|
||||||
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
|
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
|
||||||
# Download, verify, then extract the launcher binary
|
# Download, verify, then extract the launcher binary
|
||||||
RUN \
|
RUN \
|
||||||
|
|
|
@ -4,27 +4,30 @@ FROM n8nio/base:${NODE_VERSION}
|
||||||
ARG N8N_VERSION
|
ARG N8N_VERSION
|
||||||
RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi
|
RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi
|
||||||
|
|
||||||
|
ARG N8N_RELEASE_DATE
|
||||||
LABEL org.opencontainers.image.title="n8n"
|
LABEL org.opencontainers.image.title="n8n"
|
||||||
LABEL org.opencontainers.image.description="Workflow Automation Tool"
|
LABEL org.opencontainers.image.description="Workflow Automation Tool"
|
||||||
LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n"
|
LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n"
|
||||||
LABEL org.opencontainers.image.url="https://n8n.io"
|
LABEL org.opencontainers.image.url="https://n8n.io"
|
||||||
LABEL org.opencontainers.image.version=${N8N_VERSION}
|
LABEL org.opencontainers.image.version=${N8N_VERSION}
|
||||||
|
LABEL org.opencontainers.image.created=${N8N_RELEASE_DATE}
|
||||||
|
|
||||||
ENV N8N_VERSION=${N8N_VERSION}
|
ENV N8N_VERSION=${N8N_VERSION}
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV N8N_RELEASE_TYPE=stable
|
ENV N8N_RELEASE_TYPE=stable
|
||||||
|
ENV N8N_RELEASE_DATE=${N8N_RELEASE_DATE}
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
npm install -g --omit=dev n8n@${N8N_VERSION} --ignore-scripts && \
|
npm install -g --omit=dev n8n@${N8N_VERSION} --ignore-scripts && \
|
||||||
npm rebuild --prefix=/usr/local/lib/node_modules/n8n sqlite3 && \
|
npm rebuild --prefix=/usr/local/lib/node_modules/n8n sqlite3 && \
|
||||||
rm -rf /usr/local/lib/node_modules/n8n/node_modules/@n8n/chat && \
|
rm -rf /usr/local/lib/node_modules/n8n/node_modules/@n8n/chat && \
|
||||||
rm -rf /usr/local/lib/node_modules/n8n/node_modules/n8n-design-system && \
|
rm -rf /usr/local/lib/node_modules/n8n/node_modules/@n8n/design-system && \
|
||||||
rm -rf /usr/local/lib/node_modules/n8n/node_modules/n8n-editor-ui/node_modules && \
|
rm -rf /usr/local/lib/node_modules/n8n/node_modules/n8n-editor-ui/node_modules && \
|
||||||
find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm -f && \
|
find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm -f && \
|
||||||
rm -rf /root/.npm
|
rm -rf /root/.npm
|
||||||
|
|
||||||
# Setup the Task Runner Launcher
|
# Setup the Task Runner Launcher
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG LAUNCHER_VERSION=1.1.0
|
ARG LAUNCHER_VERSION=1.1.1
|
||||||
COPY n8n-task-runners.json /etc/n8n-task-runners.json
|
COPY n8n-task-runners.json /etc/n8n-task-runners.json
|
||||||
# Download, verify, then extract the launcher binary
|
# Download, verify, then extract the launcher binary
|
||||||
RUN \
|
RUN \
|
||||||
|
|
|
@ -12,6 +12,8 @@ const tsJestOptions = {
|
||||||
|
|
||||||
const { baseUrl, paths } = require('get-tsconfig').getTsconfig().config?.compilerOptions;
|
const { baseUrl, paths } = require('get-tsconfig').getTsconfig().config?.compilerOptions;
|
||||||
|
|
||||||
|
const isCoverageEnabled = process.env.COVERAGE_ENABLED === 'true';
|
||||||
|
|
||||||
/** @type {import('jest').Config} */
|
/** @type {import('jest').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
verbose: true,
|
verbose: true,
|
||||||
|
@ -32,8 +34,8 @@ const config = {
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
setupFilesAfterEnv: ['jest-expect-message'],
|
setupFilesAfterEnv: ['jest-expect-message'],
|
||||||
collectCoverage: process.env.COVERAGE_ENABLED === 'true',
|
collectCoverage: isCoverageEnabled,
|
||||||
coverageReporters: ['text-summary'],
|
coverageReporters: ['text-summary', 'lcov', 'html-spa'],
|
||||||
collectCoverageFrom: ['src/**/*.ts'],
|
collectCoverageFrom: ['src/**/*.ts'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
13
package.json
13
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-monorepo",
|
"name": "n8n-monorepo",
|
||||||
"version": "1.79.0",
|
"version": "1.81.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.15",
|
"node": ">=20.15",
|
||||||
|
@ -15,10 +15,10 @@
|
||||||
"build:frontend": "turbo run build:frontend",
|
"build:frontend": "turbo run build:frontend",
|
||||||
"build:nodes": "turbo run build:nodes",
|
"build:nodes": "turbo run build:nodes",
|
||||||
"typecheck": "turbo typecheck",
|
"typecheck": "turbo typecheck",
|
||||||
"dev": "turbo run dev --parallel --env-mode=loose --filter=!n8n-design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
"dev": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
||||||
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!n8n-design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
||||||
"dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core",
|
"dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core",
|
||||||
"dev:fe": "run-p start \"dev:fe:editor --filter=n8n-design-system\"",
|
"dev:fe": "run-p start \"dev:fe:editor --filter=@n8n/design-system\"",
|
||||||
"dev:fe:editor": "turbo run dev --parallel --env-mode=loose --filter=n8n-editor-ui",
|
"dev:fe:editor": "turbo run dev --parallel --env-mode=loose --filter=n8n-editor-ui",
|
||||||
"dev:e2e": "cd cypress && pnpm run test:e2e:dev",
|
"dev:e2e": "cd cypress && pnpm run test:e2e:dev",
|
||||||
"dev:e2e:v1": "cd cypress && pnpm run test:e2e:dev:v1",
|
"dev:e2e:v1": "cd cypress && pnpm run test:e2e:dev:v1",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.0",
|
"@biomejs/biome": "^1.9.0",
|
||||||
"@n8n_io/eslint-config": "workspace:*",
|
"@n8n/eslint-config": "workspace:*",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
|
@ -96,7 +96,8 @@
|
||||||
"@types/express-serve-static-core@4.17.43": "patches/@types__express-serve-static-core@4.17.43.patch",
|
"@types/express-serve-static-core@4.17.43": "patches/@types__express-serve-static-core@4.17.43.patch",
|
||||||
"@types/ws@8.5.4": "patches/@types__ws@8.5.4.patch",
|
"@types/ws@8.5.4": "patches/@types__ws@8.5.4.patch",
|
||||||
"@types/uuencode@0.0.3": "patches/@types__uuencode@0.0.3.patch",
|
"@types/uuencode@0.0.3": "patches/@types__uuencode@0.0.3.patch",
|
||||||
"vue-tsc@2.1.10": "patches/vue-tsc@2.1.10.patch"
|
"vue-tsc@2.1.10": "patches/vue-tsc@2.1.10.patch",
|
||||||
|
"eslint-plugin-n8n-local-rules": "patches/eslint-plugin-n8n-local-rules.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/** @type {import('@types/eslint').ESLint.ConfigData} */
|
/** @type {import('@types/eslint').ESLint.ConfigData} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base'],
|
extends: ['@n8n/eslint-config/base'],
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@n8n/api-types",
|
"name": "@n8n/api-types",
|
||||||
"version": "0.15.0",
|
"version": "0.16.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist .turbo",
|
"clean": "rimraf dist .turbo",
|
||||||
"dev": "pnpm watch",
|
"dev": "pnpm watch",
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
"dist/**/*"
|
"dist/**/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@n8n/config": "workspace:*",
|
"@n8n/config": "workspace:*",
|
||||||
"n8n-workflow": "workspace:*"
|
"n8n-workflow": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { CreateFolderDto } from '../create-folder.dto';
|
||||||
|
|
||||||
|
describe('CreateFolderDto', () => {
|
||||||
|
describe('Valid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'name without parentId',
|
||||||
|
request: {
|
||||||
|
name: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name and parentFolderId',
|
||||||
|
request: {
|
||||||
|
name: 'test',
|
||||||
|
parentFolderId: '2Hw01NJ7biAj_LU6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])('should validate $name', ({ request }) => {
|
||||||
|
const result = CreateFolderDto.safeParse(request);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'missing name',
|
||||||
|
request: {},
|
||||||
|
expectedErrorPath: ['name'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'empty name',
|
||||||
|
request: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['name'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'parentFolderId and no name',
|
||||||
|
request: {
|
||||||
|
parentFolderId: '',
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['name'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invalid parentFolderId',
|
||||||
|
request: {
|
||||||
|
name: 'test',
|
||||||
|
parentFolderId: 1,
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['parentFolderId'],
|
||||||
|
},
|
||||||
|
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
|
||||||
|
const result = CreateFolderDto.safeParse(request);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
|
||||||
|
if (expectedErrorPath) {
|
||||||
|
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { UpdateFolderDto } from '../update-folder.dto';
|
||||||
|
|
||||||
|
describe('UpdateFolderDto', () => {
|
||||||
|
describe('Valid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
request: {
|
||||||
|
name: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tagIds',
|
||||||
|
request: {
|
||||||
|
tagIds: ['1', '2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'empty tagIds',
|
||||||
|
request: {
|
||||||
|
tagIds: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])('should validate $name', ({ request }) => {
|
||||||
|
const result = UpdateFolderDto.safeParse(request);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'empty name',
|
||||||
|
request: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['name'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'non string tagIds',
|
||||||
|
request: {
|
||||||
|
tagIds: [0],
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['tagIds'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'non array tagIds',
|
||||||
|
request: {
|
||||||
|
tagIds: 0,
|
||||||
|
},
|
||||||
|
expectedErrorPath: ['tagIds'],
|
||||||
|
},
|
||||||
|
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
|
||||||
|
const result = UpdateFolderDto.safeParse(request);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
|
||||||
|
if (expectedErrorPath) {
|
||||||
|
expect(result.error?.issues[0].path[0]).toEqual(expectedErrorPath[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Z } from 'zod-class';
|
||||||
|
|
||||||
|
import { folderNameSchema, folderId } from '../../schemas/folder.schema';
|
||||||
|
|
||||||
|
export class CreateFolderDto extends Z.class({
|
||||||
|
name: folderNameSchema,
|
||||||
|
parentFolderId: folderId.optional(),
|
||||||
|
}) {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Z } from 'zod-class';
|
||||||
|
|
||||||
|
import { folderId } from '../../schemas/folder.schema';
|
||||||
|
|
||||||
|
export class DeleteFolderDto extends Z.class({
|
||||||
|
transferToFolderId: folderId.optional(),
|
||||||
|
}) {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Z } from 'zod-class';
|
||||||
|
|
||||||
|
import { folderNameSchema } from '../../schemas/folder.schema';
|
||||||
|
export class UpdateFolderDto extends Z.class({
|
||||||
|
name: folderNameSchema.optional(),
|
||||||
|
tagIds: z.array(z.string().max(24)).optional(),
|
||||||
|
}) {}
|
|
@ -47,9 +47,14 @@ export { GenerateCredentialNameRequestQuery } from './credentials/generate-crede
|
||||||
|
|
||||||
export { ImportWorkflowFromUrlDto } from './workflows/import-workflow-from-url.dto';
|
export { ImportWorkflowFromUrlDto } from './workflows/import-workflow-from-url.dto';
|
||||||
export { ManualRunQueryDto } from './workflows/manual-run-query.dto';
|
export { ManualRunQueryDto } from './workflows/manual-run-query.dto';
|
||||||
|
export { TransferWorkflowBodyDto } from './workflows/transfer.dto';
|
||||||
|
|
||||||
export { CreateOrUpdateTagRequestDto } from './tag/create-or-update-tag-request.dto';
|
export { CreateOrUpdateTagRequestDto } from './tag/create-or-update-tag-request.dto';
|
||||||
export { RetrieveTagQueryDto } from './tag/retrieve-tag-query.dto';
|
export { RetrieveTagQueryDto } from './tag/retrieve-tag-query.dto';
|
||||||
|
|
||||||
export { UpdateApiKeyRequestDto } from './api-keys/update-api-key-request.dto';
|
export { UpdateApiKeyRequestDto } from './api-keys/update-api-key-request.dto';
|
||||||
export { CreateApiKeyRequestDto } from './api-keys/create-api-key-request.dto';
|
export { CreateApiKeyRequestDto } from './api-keys/create-api-key-request.dto';
|
||||||
|
|
||||||
|
export { CreateFolderDto } from './folders/create-folder.dto';
|
||||||
|
export { UpdateFolderDto } from './folders/update-folder.dto';
|
||||||
|
export { DeleteFolderDto } from './folders/delete-folder.dto';
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { TransferWorkflowBodyDto } from '../transfer.dto';
|
||||||
|
|
||||||
|
describe('ImportWorkflowFromUrlDto', () => {
|
||||||
|
describe('Valid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'only destinationProjectId',
|
||||||
|
input: { destinationProjectId: '1234' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'destinationProjectId with empty shareCredentials',
|
||||||
|
input: { destinationProjectId: '1234', shareCredentials: [] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'destinationProjectId with shareCredentials',
|
||||||
|
input: { destinationProjectId: '1234', shareCredentials: ['1235'] },
|
||||||
|
},
|
||||||
|
])('should validate $name', ({ input }) => {
|
||||||
|
const result = TransferWorkflowBodyDto.safeParse(input);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid requests', () => {
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
name: 'no destinationProjectId',
|
||||||
|
input: { shareCredentials: [] },
|
||||||
|
expectedErrorPath: ['destinationProjectId'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'destinationProjectId not being a string',
|
||||||
|
input: { destinationProjectId: 1234 },
|
||||||
|
expectedErrorPath: ['destinationProjectId'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shareCredentials not being an array',
|
||||||
|
input: { destinationProjectId: '1234', shareCredentials: '1235' },
|
||||||
|
expectedErrorPath: ['shareCredentials'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shareCredentials not containing strings',
|
||||||
|
input: { destinationProjectId: '1234', shareCredentials: [1235] },
|
||||||
|
expectedErrorPath: ['shareCredentials', 0],
|
||||||
|
},
|
||||||
|
])('should fail validation for $name', ({ input, expectedErrorPath }) => {
|
||||||
|
const result = TransferWorkflowBodyDto.safeParse(input);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
|
||||||
|
if (expectedErrorPath) {
|
||||||
|
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Z } from 'zod-class';
|
||||||
|
|
||||||
|
export class TransferWorkflowBodyDto extends Z.class({
|
||||||
|
destinationProjectId: z.string(),
|
||||||
|
shareCredentials: z.array(z.string()).optional(),
|
||||||
|
}) {}
|
|
@ -86,7 +86,6 @@ export interface FrontendSettings {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
publicApi: {
|
publicApi: {
|
||||||
apiKeysPerUserLimit: number;
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
latestVersion: number;
|
latestVersion: number;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -156,6 +155,9 @@ export interface FrontendSettings {
|
||||||
mfa: {
|
mfa: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
folders: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
banners: {
|
banners: {
|
||||||
dismissed: string[];
|
dismissed: string[];
|
||||||
};
|
};
|
||||||
|
@ -178,6 +180,5 @@ export interface FrontendSettings {
|
||||||
easyAIWorkflowOnboarded: boolean;
|
easyAIWorkflowOnboarded: boolean;
|
||||||
partialExecution: {
|
partialExecution: {
|
||||||
version: 1 | 2;
|
version: 1 | 2;
|
||||||
enforce: boolean;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
4
packages/@n8n/api-types/src/schemas/folder.schema.ts
Normal file
4
packages/@n8n/api-types/src/schemas/folder.schema.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const folderNameSchema = z.string().trim().min(1).max(128);
|
||||||
|
export const folderId = z.string().max(36);
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest"],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/node'],
|
extends: ['@n8n/eslint-config/node'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,9 @@ ENV DOCKER_BUILD=true
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# TS config files
|
# TS config files
|
||||||
COPY --chown=node:node ./tsconfig.json /app/tsconfig.json
|
COPY --chown=node:node ./packages/@n8n/typescript-config/tsconfig.common.json /app/packages/@n8n/typescript-config/tsconfig.common.json
|
||||||
COPY --chown=node:node ./tsconfig.build.json /app/tsconfig.build.json
|
COPY --chown=node:node ./packages/@n8n/typescript-config/tsconfig.build.json /app/packages/@n8n/typescript-config/tsconfig.build.json
|
||||||
COPY --chown=node:node ./tsconfig.backend.json /app/tsconfig.backend.json
|
COPY --chown=node:node ./packages/@n8n/typescript-config/tsconfig.backend.json /app/packages/@n8n/typescript-config/tsconfig.backend.json
|
||||||
COPY --chown=node:node ./packages/@n8n/benchmark/tsconfig.json /app/packages/@n8n/benchmark/tsconfig.json
|
COPY --chown=node:node ./packages/@n8n/benchmark/tsconfig.json /app/packages/@n8n/benchmark/tsconfig.json
|
||||||
COPY --chown=node:node ./packages/@n8n/benchmark/tsconfig.build.json /app/packages/@n8n/benchmark/tsconfig.build.json
|
COPY --chown=node:node ./packages/@n8n/benchmark/tsconfig.build.json /app/packages/@n8n/benchmark/tsconfig.build.json
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"zx": "^8.1.4"
|
"zx": "^8.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@types/convict": "^6.1.1",
|
"@types/convict": "^6.1.1",
|
||||||
"@types/k6": "^0.52.0"
|
"@types/k6": "^0.52.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"],
|
"extends": [
|
||||||
|
"@n8n/typescript-config/tsconfig.common.json",
|
||||||
|
"@n8n/typescript-config/tsconfig.backend.json"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
html, body, #storybook-root, #n8n-chat {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
3
packages/@n8n/chat/.vscode/extensions.json
vendored
3
packages/@n8n/chat/.vscode/extensions.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
||||||
}
|
|
|
@ -1,238 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Hosted n8n AI Chat Manual",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"id": "e6043748-44fc-4019-9301-5690fe26c614",
|
|
||||||
"name": "OpenAI Chat Model",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
860,
|
|
||||||
540
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"openAiApi": {
|
|
||||||
"id": "cIIkOhl7tUX1KsL6",
|
|
||||||
"name": "OpenAi account"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"sessionKey": "={{ $json.sessionId }}"
|
|
||||||
},
|
|
||||||
"id": "0a68a59a-8ab6-4fa5-a1ea-b7f99a93109b",
|
|
||||||
"name": "Window Buffer Memory",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
640,
|
|
||||||
540
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"text": "={{ $json.chatInput }}",
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"id": "3d4e0fbf-d761-4569-b02e-f5c1eeb830c8",
|
|
||||||
"name": "AI Agent",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
|
||||||
"typeVersion": 1.1,
|
|
||||||
"position": [
|
|
||||||
840,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"dataType": "string",
|
|
||||||
"value1": "={{ $json.action }}",
|
|
||||||
"rules": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"value2": "loadPreviousSession",
|
|
||||||
"outputKey": "loadPreviousSession"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value2": "sendMessage",
|
|
||||||
"outputKey": "sendMessage"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "84213c7b-abc7-4f40-9567-cd3484a4ae6b",
|
|
||||||
"name": "Switch",
|
|
||||||
"type": "n8n-nodes-base.switch",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
300,
|
|
||||||
280
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"simplifyOutput": false
|
|
||||||
},
|
|
||||||
"id": "3be7f076-98ed-472a-80b6-bf8d9538ac87",
|
|
||||||
"name": "Chat Messages Retriever",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.memoryChatRetriever",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
620,
|
|
||||||
140
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"id": "3417c644-8a91-4524-974a-45b4a46d0e2e",
|
|
||||||
"name": "Respond to Webhook",
|
|
||||||
"type": "n8n-nodes-base.respondToWebhook",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
1240,
|
|
||||||
140
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"public": true,
|
|
||||||
"authentication": "n8nUserAuth",
|
|
||||||
"options": {
|
|
||||||
"loadPreviousSession": "manually",
|
|
||||||
"responseMode": "responseNode"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "1b30c239-a819-45b4-b0ae-bdd5b92a5424",
|
|
||||||
"name": "Chat Trigger",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
80,
|
|
||||||
280
|
|
||||||
],
|
|
||||||
"webhookId": "ed3dea26-7d68-42b3-9032-98fe967d441d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"aggregate": "aggregateAllItemData",
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"id": "79672cf0-686b-41eb-90ae-fd31b6da837d",
|
|
||||||
"name": "Aggregate",
|
|
||||||
"type": "n8n-nodes-base.aggregate",
|
|
||||||
"typeVersion": 1,
|
|
||||||
"position": [
|
|
||||||
1000,
|
|
||||||
140
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pinData": {},
|
|
||||||
"connections": {
|
|
||||||
"OpenAI Chat Model": {
|
|
||||||
"ai_languageModel": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "AI Agent",
|
|
||||||
"type": "ai_languageModel",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Window Buffer Memory": {
|
|
||||||
"ai_memory": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "AI Agent",
|
|
||||||
"type": "ai_memory",
|
|
||||||
"index": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "Chat Messages Retriever",
|
|
||||||
"type": "ai_memory",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Switch": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Chat Messages Retriever",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "AI Agent",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Chat Messages Retriever": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Aggregate",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"AI Agent": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Respond to Webhook",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Chat Trigger": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Switch",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Aggregate": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Respond to Webhook",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"active": true,
|
|
||||||
"settings": {
|
|
||||||
"executionOrder": "v1"
|
|
||||||
},
|
|
||||||
"versionId": "425c0efe-3aa0-4e0e-8c06-abe12234b1fd",
|
|
||||||
"id": "1569HF92Y02EUtsU",
|
|
||||||
"meta": {
|
|
||||||
"instanceId": "374b43d8b8d6299cc777811a4ad220fc688ee2d54a308cfb0de4450a5233ca9e"
|
|
||||||
},
|
|
||||||
"tags": []
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
:root {
|
|
||||||
--chat--color-primary: #e74266;
|
|
||||||
--chat--color-primary-shade-50: #db4061;
|
|
||||||
--chat--color-primary-shade-100: #cf3c5c;
|
|
||||||
--chat--color-secondary: #20b69e;
|
|
||||||
--chat--color-secondary-shade-50: #1ca08a;
|
|
||||||
--chat--color-white: #ffffff;
|
|
||||||
--chat--color-light: #f2f4f8;
|
|
||||||
--chat--color-light-shade-50: #e6e9f1;
|
|
||||||
--chat--color-light-shade-100: #c2c5cc;
|
|
||||||
--chat--color-medium: #d2d4d9;
|
|
||||||
--chat--color-dark: #101330;
|
|
||||||
--chat--color-disabled: #777980;
|
|
||||||
--chat--color-typing: #404040;
|
|
||||||
|
|
||||||
--chat--spacing: 1rem;
|
|
||||||
--chat--border-radius: 0.25rem;
|
|
||||||
--chat--transition-duration: 0.15s;
|
|
||||||
|
|
||||||
--chat--window--width: 400px;
|
|
||||||
--chat--window--height: 600px;
|
|
||||||
|
|
||||||
--chat--textarea--height: 50px;
|
|
||||||
|
|
||||||
--chat--message--bot--background: var(--chat--color-white);
|
|
||||||
--chat--message--bot--color: var(--chat--color-dark);
|
|
||||||
--chat--message--user--background: var(--chat--color-secondary);
|
|
||||||
--chat--message--user--color: var(--chat--color-white);
|
|
||||||
--chat--message--pre--background: rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
--chat--toggle--background: var(--chat--color-primary);
|
|
||||||
--chat--toggle--hover--background: var(--chat--color-primary-shade-50);
|
|
||||||
--chat--toggle--active--background: var(--chat--color-primary-shade-100);
|
|
||||||
--chat--toggle--color: var(--chat--color-white);
|
|
||||||
--chat--toggle--size: 64px;
|
|
||||||
|
|
||||||
--chat--heading--font-size: 2em;
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import vue from '@vitejs/plugin-vue';
|
|
||||||
import icons from 'unplugin-icons/vite';
|
|
||||||
import dts from 'vite-plugin-dts';
|
|
||||||
|
|
||||||
const includeVue = process.env.INCLUDE_VUE === 'true';
|
|
||||||
const srcPath = resolve(__dirname, 'src');
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
icons({
|
|
||||||
compiler: 'vue3',
|
|
||||||
autoInstall: true,
|
|
||||||
}),
|
|
||||||
dts(),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': srcPath,
|
|
||||||
'@n8n/chat': srcPath,
|
|
||||||
lodash: 'lodash-es',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': process.env.NODE_ENV ? `"${process.env.NODE_ENV}"` : '"development"',
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
emptyOutDir: !includeVue,
|
|
||||||
lib: {
|
|
||||||
entry: resolve(__dirname, 'src', 'index.ts'),
|
|
||||||
name: 'N8nChat',
|
|
||||||
fileName: (format) => (includeVue ? `chat.bundle.${format}.js` : `chat.${format}.js`),
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
// make sure to externalize deps that shouldn't be bundled
|
|
||||||
// into your library
|
|
||||||
external: includeVue ? [] : ['vue'],
|
|
||||||
output: {
|
|
||||||
exports: 'named',
|
|
||||||
// Provide global variables to use in the UMD build
|
|
||||||
// for externalized deps
|
|
||||||
globals: includeVue
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
vue: 'Vue',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { resolve } from 'path';
|
|
||||||
import { mergeConfig } from 'vite';
|
|
||||||
import { type UserConfig } from 'vitest';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
|
||||||
import viteConfig from './vite.config.mts';
|
|
||||||
|
|
||||||
const srcPath = resolve(__dirname, 'src');
|
|
||||||
const vitestConfig = defineConfig({
|
|
||||||
test: {
|
|
||||||
globals: true,
|
|
||||||
environment: 'jsdom',
|
|
||||||
root: srcPath,
|
|
||||||
setupFiles: ['./src/__tests__/setup.ts'],
|
|
||||||
...(process.env.COVERAGE_ENABLED === 'true'
|
|
||||||
? {
|
|
||||||
coverage: {
|
|
||||||
enabled: true,
|
|
||||||
provider: 'v8',
|
|
||||||
reporter: process.env.CI === 'true' ? 'cobertura' : 'text-summary',
|
|
||||||
all: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
}) as UserConfig;
|
|
||||||
|
|
||||||
export default mergeConfig(
|
|
||||||
viteConfig,
|
|
||||||
vitestConfig,
|
|
||||||
);
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base'],
|
extends: ['@n8n/eslint-config/base'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "catalog:"
|
"axios": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest"],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base'],
|
extends: ['@n8n/eslint-config/base'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"@lezer/lr": "^1.4.0"
|
"@lezer/lr": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@lezer/generator": "^1.7.0"
|
"@lezer/generator": "^1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/node'],
|
extends: ['@n8n/eslint-config/node'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@n8n/config",
|
"name": "@n8n/config",
|
||||||
"version": "1.29.0",
|
"version": "1.30.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist .turbo",
|
"clean": "rimraf dist .turbo",
|
||||||
"dev": "pnpm watch",
|
"dev": "pnpm watch",
|
||||||
|
@ -23,5 +23,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n/di": "workspace:*",
|
"@n8n/di": "workspace:*",
|
||||||
"reflect-metadata": "catalog:"
|
"reflect-metadata": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,10 @@ class PrometheusMetricsConfig {
|
||||||
/** How often (in seconds) to update queue metrics. */
|
/** How often (in seconds) to update queue metrics. */
|
||||||
@Env('N8N_METRICS_QUEUE_METRICS_INTERVAL')
|
@Env('N8N_METRICS_QUEUE_METRICS_INTERVAL')
|
||||||
queueMetricsInterval: number = 20;
|
queueMetricsInterval: number = 20;
|
||||||
|
|
||||||
|
/** How often (in seconds) to update active workflow metric */
|
||||||
|
@Env('N8N_METRICS_ACTIVE_WORKFLOW_METRIC_INTERVAL')
|
||||||
|
activeWorkflowCountInterval: number = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config
|
@Config
|
||||||
|
|
|
@ -9,6 +9,9 @@ export class GenericConfig {
|
||||||
@Env('N8N_RELEASE_TYPE')
|
@Env('N8N_RELEASE_TYPE')
|
||||||
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev' = 'dev';
|
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev' = 'dev';
|
||||||
|
|
||||||
|
@Env('N8N_RELEASE_DATE')
|
||||||
|
releaseDate?: Date;
|
||||||
|
|
||||||
/** Grace period (in seconds) to wait for components to shut down before process exit. */
|
/** Grace period (in seconds) to wait for components to shut down before process exit. */
|
||||||
@Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT')
|
@Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT')
|
||||||
gracefulShutdownTimeout: number = 30;
|
gracefulShutdownTimeout: number = 30;
|
||||||
|
|
|
@ -4,9 +4,5 @@ import { Config, Env } from '../decorators';
|
||||||
export class PartialExecutionsConfig {
|
export class PartialExecutionsConfig {
|
||||||
/** Partial execution logic version to use by default. */
|
/** Partial execution logic version to use by default. */
|
||||||
@Env('N8N_PARTIAL_EXECUTION_VERSION_DEFAULT')
|
@Env('N8N_PARTIAL_EXECUTION_VERSION_DEFAULT')
|
||||||
version: 1 | 2 = 1;
|
version: 1 | 2 = 2;
|
||||||
|
|
||||||
/** Set this to true to enforce using the default version. Users cannot use the other version then by setting a local storage key. */
|
|
||||||
@Env('N8N_PARTIAL_EXECUTION_ENFORCE_VERSION')
|
|
||||||
enforce: boolean = false;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,13 @@ export const Config: ClassDecorator = (ConfigClass: Class) => {
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Invalid boolean value for ${envName}: ${value}`);
|
console.warn(`Invalid boolean value for ${envName}: ${value}`);
|
||||||
}
|
}
|
||||||
|
} else if (type === Date) {
|
||||||
|
const timestamp = Date.parse(value);
|
||||||
|
if (isNaN(timestamp)) {
|
||||||
|
console.warn(`Invalid timestamp value for ${envName}: ${value}`);
|
||||||
|
} else {
|
||||||
|
config[key] = new Date(timestamp);
|
||||||
|
}
|
||||||
} else if (type === String) {
|
} else if (type === String) {
|
||||||
config[key] = value;
|
config[key] = value;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,9 +8,12 @@ jest.mock('fs');
|
||||||
const mockFs = mock<typeof fs>();
|
const mockFs = mock<typeof fs>();
|
||||||
fs.readFileSync = mockFs.readFileSync;
|
fs.readFileSync = mockFs.readFileSync;
|
||||||
|
|
||||||
|
const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
|
||||||
describe('GlobalConfig', () => {
|
describe('GlobalConfig', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Container.reset();
|
Container.reset();
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const originalEnv = process.env;
|
const originalEnv = process.env;
|
||||||
|
@ -18,10 +21,6 @@ describe('GlobalConfig', () => {
|
||||||
process.env = originalEnv;
|
process.env = originalEnv;
|
||||||
});
|
});
|
||||||
|
|
||||||
// deepCopy for diff to show plain objects
|
|
||||||
// eslint-disable-next-line n8n-local-rules/no-json-parse-json-stringify
|
|
||||||
const deepCopy = <T>(obj: T): T => JSON.parse(JSON.stringify(obj));
|
|
||||||
|
|
||||||
const defaultConfig: GlobalConfig = {
|
const defaultConfig: GlobalConfig = {
|
||||||
path: '/',
|
path: '/',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
@ -174,6 +173,7 @@ describe('GlobalConfig', () => {
|
||||||
includeApiStatusCodeLabel: false,
|
includeApiStatusCodeLabel: false,
|
||||||
includeQueueMetrics: false,
|
includeQueueMetrics: false,
|
||||||
queueMetricsInterval: 20,
|
queueMetricsInterval: 20,
|
||||||
|
activeWorkflowCountInterval: 60,
|
||||||
},
|
},
|
||||||
additionalNonUIRoutes: '',
|
additionalNonUIRoutes: '',
|
||||||
disableProductionWebhooksOnMainProcess: false,
|
disableProductionWebhooksOnMainProcess: false,
|
||||||
|
@ -306,15 +306,14 @@ describe('GlobalConfig', () => {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
partialExecutions: {
|
partialExecutions: {
|
||||||
version: 1,
|
version: 2,
|
||||||
enforce: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should use all default values when no env variables are defined', () => {
|
it('should use all default values when no env variables are defined', () => {
|
||||||
process.env = {};
|
process.env = {};
|
||||||
const config = Container.get(GlobalConfig);
|
const config = Container.get(GlobalConfig);
|
||||||
expect(deepCopy(config)).toEqual(defaultConfig);
|
expect(structuredClone(config)).toEqual(defaultConfig);
|
||||||
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -327,9 +326,10 @@ describe('GlobalConfig', () => {
|
||||||
DB_LOGGING_MAX_EXECUTION_TIME: '0',
|
DB_LOGGING_MAX_EXECUTION_TIME: '0',
|
||||||
N8N_METRICS: 'TRUE',
|
N8N_METRICS: 'TRUE',
|
||||||
N8N_TEMPLATES_ENABLED: '0',
|
N8N_TEMPLATES_ENABLED: '0',
|
||||||
|
N8N_RELEASE_DATE: '2025-02-17T13:54:15Z',
|
||||||
};
|
};
|
||||||
const config = Container.get(GlobalConfig);
|
const config = Container.get(GlobalConfig);
|
||||||
expect(deepCopy(config)).toEqual({
|
expect(structuredClone(config)).toEqual({
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
database: {
|
database: {
|
||||||
logging: defaultConfig.database.logging,
|
logging: defaultConfig.database.logging,
|
||||||
|
@ -358,6 +358,10 @@ describe('GlobalConfig', () => {
|
||||||
...defaultConfig.templates,
|
...defaultConfig.templates,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
generic: {
|
||||||
|
...defaultConfig.generic,
|
||||||
|
releaseDate: new Date('2025-02-17T13:54:15.000Z'),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -370,7 +374,7 @@ describe('GlobalConfig', () => {
|
||||||
mockFs.readFileSync.calledWith(passwordFile, 'utf8').mockReturnValueOnce('password-from-file');
|
mockFs.readFileSync.calledWith(passwordFile, 'utf8').mockReturnValueOnce('password-from-file');
|
||||||
|
|
||||||
const config = Container.get(GlobalConfig);
|
const config = Container.get(GlobalConfig);
|
||||||
expect(deepCopy(config)).toEqual({
|
expect(structuredClone(config)).toEqual({
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
database: {
|
database: {
|
||||||
...defaultConfig.database,
|
...defaultConfig.database,
|
||||||
|
@ -382,4 +386,26 @@ describe('GlobalConfig', () => {
|
||||||
});
|
});
|
||||||
expect(mockFs.readFileSync).toHaveBeenCalled();
|
expect(mockFs.readFileSync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle invalid numbers', () => {
|
||||||
|
process.env = {
|
||||||
|
DB_LOGGING_MAX_EXECUTION_TIME: 'abcd',
|
||||||
|
};
|
||||||
|
const config = Container.get(GlobalConfig);
|
||||||
|
expect(config.database.logging.maxQueryExecutionTime).toEqual(0);
|
||||||
|
expect(consoleWarnMock).toHaveBeenCalledWith(
|
||||||
|
'Invalid number value for DB_LOGGING_MAX_EXECUTION_TIME: abcd',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid timestamps', () => {
|
||||||
|
process.env = {
|
||||||
|
N8N_RELEASE_DATE: 'abcd',
|
||||||
|
};
|
||||||
|
const config = Container.get(GlobalConfig);
|
||||||
|
expect(config.generic.releaseDate).toBeUndefined();
|
||||||
|
expect(consoleWarnMock).toHaveBeenCalledWith(
|
||||||
|
'Invalid timestamp value for N8N_RELEASE_DATE: abcd',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/** @type {import('@types/eslint').ESLint.ConfigData} */
|
/** @type {import('@types/eslint').ESLint.ConfigData} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base'],
|
extends: ['@n8n/eslint-config/base'],
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reflect-metadata": "catalog:"
|
"reflect-metadata": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest"],
|
||||||
|
|
|
@ -320,7 +320,7 @@ module.exports = {
|
||||||
const LOCALE_NAMESPACE = '$locale';
|
const LOCALE_NAMESPACE = '$locale';
|
||||||
const LOCALE_FILEPATH = cwd.endsWith('editor-ui')
|
const LOCALE_FILEPATH = cwd.endsWith('editor-ui')
|
||||||
? path.join(cwd, locale)
|
? path.join(cwd, locale)
|
||||||
: path.join(cwd, 'packages/editor-ui', locale);
|
: path.join(cwd, 'packages/frontend/editor-ui', locale);
|
||||||
|
|
||||||
let LOCALE_MAP;
|
let LOCALE_MAP;
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "@n8n_io/eslint-config",
|
"name": "@n8n/eslint-config",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"exports": {
|
||||||
|
"./base": "./base.js",
|
||||||
|
"./frontend": "./frontend.js",
|
||||||
|
"./local-rules": "./local-rules.js",
|
||||||
|
"./node": "./node.js",
|
||||||
|
"./shared": "./shared.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/eslint": "^8.56.5",
|
"@types/eslint": "^8.56.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/base'],
|
extends: ['@n8n/eslint-config/base'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"uuencode": "0.0.4"
|
"uuencode": "0.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@types/imap": "^0.8.40",
|
"@types/imap": "^0.8.40",
|
||||||
"@types/quoted-printable": "^1.0.2",
|
"@types/quoted-printable": "^1.0.2",
|
||||||
"@types/utf8": "^3.0.3",
|
"@types/utf8": "^3.0.3",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "@n8n/typescript-config/tsconfig.common.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest"],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/node'],
|
extends: ['@n8n/eslint-config/node'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"zod": "^3.0.0"
|
"zod": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["../../../tsconfig.json"],
|
"extends": ["@n8n/typescript-config/tsconfig.common.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
const sharedOptions = require('@n8n/eslint-config/shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@n8n_io/eslint-config/node'],
|
extends: ['@n8n/eslint-config/node'],
|
||||||
|
|
||||||
...sharedOptions(__dirname),
|
...sharedOptions(__dirname),
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,13 @@ import { toolsAgentExecute } from './agents/ToolsAgent/execute';
|
||||||
// Function used in the inputs expression to figure out which inputs to
|
// Function used in the inputs expression to figure out which inputs to
|
||||||
// display based on the agent type
|
// display based on the agent type
|
||||||
function getInputs(
|
function getInputs(
|
||||||
agent: 'toolsAgent' | 'conversationalAgent' | 'openAiFunctionsAgent' | 'reActAgent' | 'sqlAgent',
|
agent:
|
||||||
|
| 'toolsAgent'
|
||||||
|
| 'conversationalAgent'
|
||||||
|
| 'openAiFunctionsAgent'
|
||||||
|
| 'planAndExecuteAgent'
|
||||||
|
| 'reActAgent'
|
||||||
|
| 'sqlAgent',
|
||||||
hasOutputParser?: boolean,
|
hasOutputParser?: boolean,
|
||||||
): Array<NodeConnectionType | INodeInputConfiguration> {
|
): Array<NodeConnectionType | INodeInputConfiguration> {
|
||||||
interface SpecialInput {
|
interface SpecialInput {
|
||||||
|
@ -256,7 +262,7 @@ export class Agent implements INodeType {
|
||||||
icon: 'fa:robot',
|
icon: 'fa:robot',
|
||||||
iconColor: 'black',
|
iconColor: 'black',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],
|
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8],
|
||||||
description: 'Generates an action plan and executes it. Can use external tools.',
|
description: 'Generates an action plan and executes it. Can use external tools.',
|
||||||
subtitle:
|
subtitle:
|
||||||
"={{ { toolsAgent: 'Tools Agent', conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}",
|
"={{ { toolsAgent: 'Tools Agent', conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}",
|
||||||
|
@ -322,6 +328,24 @@ export class Agent implements INodeType {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName:
|
||||||
|
"This node is using Agent that has been deprecated. Please switch to using 'Tools Agent' instead.",
|
||||||
|
name: 'deprecated',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
agent: [
|
||||||
|
'conversationalAgent',
|
||||||
|
'openAiFunctionsAgent',
|
||||||
|
'planAndExecuteAgent',
|
||||||
|
'reActAgent',
|
||||||
|
'sqlAgent',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// Make Conversational Agent the default agent for versions 1.5 and below
|
// Make Conversational Agent the default agent for versions 1.5 and below
|
||||||
{
|
{
|
||||||
...agentTypeProperty,
|
...agentTypeProperty,
|
||||||
|
@ -331,10 +355,17 @@ export class Agent implements INodeType {
|
||||||
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.5 } }] } },
|
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.5 } }] } },
|
||||||
default: 'conversationalAgent',
|
default: 'conversationalAgent',
|
||||||
},
|
},
|
||||||
// Make Tools Agent the default agent for versions 1.6 and above
|
// Make Tools Agent the default agent for versions 1.6 and 1.7
|
||||||
{
|
{
|
||||||
...agentTypeProperty,
|
...agentTypeProperty,
|
||||||
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.6 } }] } },
|
displayOptions: { show: { '@version': [{ _cnd: { between: { from: 1.6, to: 1.7 } } }] } },
|
||||||
|
default: 'toolsAgent',
|
||||||
|
},
|
||||||
|
// Make Tools Agent the only agent option for versions 1.8 and above
|
||||||
|
{
|
||||||
|
...agentTypeProperty,
|
||||||
|
type: 'hidden',
|
||||||
|
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.8 } }] } },
|
||||||
default: 'toolsAgent',
|
default: 'toolsAgent',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -96,6 +96,13 @@ export const reActAgentAgentProperties: INodeProperties[] = [
|
||||||
rows: 6,
|
rows: 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Max Iterations',
|
||||||
|
name: 'maxIterations',
|
||||||
|
type: 'number',
|
||||||
|
default: 10,
|
||||||
|
description: 'The maximum number of iterations the agent will run before stopping',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Return Intermediate Steps',
|
displayName: 'Return Intermediate Steps',
|
||||||
name: 'returnIntermediateSteps',
|
name: 'returnIntermediateSteps',
|
||||||
|
|
|
@ -38,6 +38,7 @@ export async function reActAgentAgentExecute(
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
suffixChat?: string;
|
suffixChat?: string;
|
||||||
|
maxIterations?: number;
|
||||||
humanMessageTemplate?: string;
|
humanMessageTemplate?: string;
|
||||||
returnIntermediateSteps?: boolean;
|
returnIntermediateSteps?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -60,6 +61,7 @@ export async function reActAgentAgentExecute(
|
||||||
agent,
|
agent,
|
||||||
tools,
|
tools,
|
||||||
returnIntermediateSteps: options?.returnIntermediateSteps === true,
|
returnIntermediateSteps: options?.returnIntermediateSteps === true,
|
||||||
|
maxIterations: options.maxIterations ?? 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
|
@ -392,13 +392,14 @@ export async function toolsAgentExecute(this: IExecuteFunctions): Promise<INodeE
|
||||||
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
|
const outputParsers = await getOptionalOutputParsers(this);
|
||||||
|
const outputParser = outputParsers?.[0];
|
||||||
|
const tools = await getTools(this, outputParser);
|
||||||
|
|
||||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||||
try {
|
try {
|
||||||
const model = await getChatModel(this);
|
const model = await getChatModel(this);
|
||||||
const memory = await getOptionalMemory(this);
|
const memory = await getOptionalMemory(this);
|
||||||
const outputParsers = await getOptionalOutputParsers(this);
|
|
||||||
const outputParser = outputParsers?.[0];
|
|
||||||
const tools = await getTools(this, outputParser);
|
|
||||||
|
|
||||||
const input = getPromptInputByType({
|
const input = getPromptInputByType({
|
||||||
ctx: this,
|
ctx: this,
|
||||||
|
|
|
@ -338,7 +338,7 @@ export class ChainLlm implements INodeType {
|
||||||
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.5 } }] } },
|
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.5 } }] } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Text',
|
displayName: 'Prompt (User Message)',
|
||||||
name: 'text',
|
name: 'text',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
|
|
|
@ -123,11 +123,12 @@ export class ChainRetrievalQa implements INodeType {
|
||||||
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.4 } }] } },
|
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.4 } }] } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Text',
|
displayName: 'Prompt (User Message)',
|
||||||
name: 'text',
|
name: 'text',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
default: '',
|
default: '',
|
||||||
|
placeholder: 'e.g. Hello, how can you help me?',
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
rows: 2,
|
rows: 2,
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
|
|
||||||
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
||||||
|
|
||||||
|
import { searchModels } from './methods/searchModels';
|
||||||
import { makeN8nLlmFailedAttemptHandler } from '../n8nLlmFailedAttemptHandler';
|
import { makeN8nLlmFailedAttemptHandler } from '../n8nLlmFailedAttemptHandler';
|
||||||
import { N8nLlmTracing } from '../N8nLlmTracing';
|
import { N8nLlmTracing } from '../N8nLlmTracing';
|
||||||
|
|
||||||
|
@ -69,15 +70,23 @@ const modelField: INodeProperties = {
|
||||||
default: 'claude-2',
|
default: 'claude-2',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MIN_THINKING_BUDGET = 1024;
|
||||||
|
const DEFAULT_MAX_TOKENS = 4096;
|
||||||
export class LmChatAnthropic implements INodeType {
|
export class LmChatAnthropic implements INodeType {
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
searchModels,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Anthropic Chat Model',
|
displayName: 'Anthropic Chat Model',
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased
|
// eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased
|
||||||
name: 'lmChatAnthropic',
|
name: 'lmChatAnthropic',
|
||||||
icon: 'file:anthropic.svg',
|
icon: 'file:anthropic.svg',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 1.1, 1.2],
|
version: [1, 1.1, 1.2, 1.3],
|
||||||
defaultVersion: 1.2,
|
defaultVersion: 1.3,
|
||||||
description: 'Language Model Anthropic',
|
description: 'Language Model Anthropic',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Anthropic Chat Model',
|
name: 'Anthropic Chat Model',
|
||||||
|
@ -135,7 +144,43 @@ export class LmChatAnthropic implements INodeType {
|
||||||
),
|
),
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
'@version': [{ _cnd: { gte: 1.2 } }],
|
'@version': [{ _cnd: { lte: 1.2 } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Model',
|
||||||
|
name: 'model',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: 'claude-3-7-sonnet-20250219',
|
||||||
|
cachedResultName: 'Claude 3.7 Sonnet',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a model...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchModels',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Claude Sonnet',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
'The model. Choose from the list, or specify an ID. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'@version': [{ _cnd: { gte: 1.3 } }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -150,7 +195,7 @@ export class LmChatAnthropic implements INodeType {
|
||||||
{
|
{
|
||||||
displayName: 'Maximum Number of Tokens',
|
displayName: 'Maximum Number of Tokens',
|
||||||
name: 'maxTokensToSample',
|
name: 'maxTokensToSample',
|
||||||
default: 4096,
|
default: DEFAULT_MAX_TOKENS,
|
||||||
description: 'The maximum number of tokens to generate in the completion',
|
description: 'The maximum number of tokens to generate in the completion',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
|
@ -162,6 +207,11 @@ export class LmChatAnthropic implements INodeType {
|
||||||
description:
|
description:
|
||||||
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
|
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
thinking: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Top K',
|
displayName: 'Top K',
|
||||||
|
@ -171,6 +221,11 @@ export class LmChatAnthropic implements INodeType {
|
||||||
description:
|
description:
|
||||||
'Used to remove "long tail" low probability responses. Defaults to -1, which disables it.',
|
'Used to remove "long tail" low probability responses. Defaults to -1, which disables it.',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
thinking: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Top P',
|
displayName: 'Top P',
|
||||||
|
@ -180,6 +235,30 @@ export class LmChatAnthropic implements INodeType {
|
||||||
description:
|
description:
|
||||||
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.',
|
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
thinking: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Enable Thinking',
|
||||||
|
name: 'thinking',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to enable thinking mode for the model',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Thinking Budget (Tokens)',
|
||||||
|
name: 'thinkingBudget',
|
||||||
|
type: 'number',
|
||||||
|
default: MIN_THINKING_BUDGET,
|
||||||
|
description: 'The maximum number of tokens to use for thinking',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
thinking: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -189,13 +268,21 @@ export class LmChatAnthropic implements INodeType {
|
||||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||||
const credentials = await this.getCredentials('anthropicApi');
|
const credentials = await this.getCredentials('anthropicApi');
|
||||||
|
|
||||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
const version = this.getNode().typeVersion;
|
||||||
|
const modelName =
|
||||||
|
version >= 1.3
|
||||||
|
? (this.getNodeParameter('model.value', itemIndex) as string)
|
||||||
|
: (this.getNodeParameter('model', itemIndex) as string);
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', itemIndex, {}) as {
|
const options = this.getNodeParameter('options', itemIndex, {}) as {
|
||||||
maxTokensToSample?: number;
|
maxTokensToSample?: number;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
topK: number;
|
topK?: number;
|
||||||
topP: number;
|
topP?: number;
|
||||||
|
thinking?: boolean;
|
||||||
|
thinkingBudget?: number;
|
||||||
};
|
};
|
||||||
|
let invocationKwargs = {};
|
||||||
|
|
||||||
const tokensUsageParser = (llmOutput: LLMResult['llmOutput']) => {
|
const tokensUsageParser = (llmOutput: LLMResult['llmOutput']) => {
|
||||||
const usage = (llmOutput?.usage as { input_tokens: number; output_tokens: number }) ?? {
|
const usage = (llmOutput?.usage as { input_tokens: number; output_tokens: number }) ?? {
|
||||||
|
@ -208,6 +295,27 @@ export class LmChatAnthropic implements INodeType {
|
||||||
totalTokens: usage.input_tokens + usage.output_tokens,
|
totalTokens: usage.input_tokens + usage.output_tokens,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (options.thinking) {
|
||||||
|
invocationKwargs = {
|
||||||
|
thinking: {
|
||||||
|
type: 'enabled',
|
||||||
|
// If thinking is enabled, we need to set a budget.
|
||||||
|
// We fallback to 1024 as that is the minimum
|
||||||
|
budget_tokens: options.thinkingBudget ?? MIN_THINKING_BUDGET,
|
||||||
|
},
|
||||||
|
// The default Langchain max_tokens is -1 (no limit) but Anthropic requires a number
|
||||||
|
// higher than budget_tokens
|
||||||
|
max_tokens: options.maxTokensToSample ?? DEFAULT_MAX_TOKENS,
|
||||||
|
// These need to be unset when thinking is enabled.
|
||||||
|
// Because the invocationKwargs will override the model options
|
||||||
|
// we can pass options to the model and then override them here
|
||||||
|
top_k: undefined,
|
||||||
|
top_p: undefined,
|
||||||
|
temperature: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const model = new ChatAnthropic({
|
const model = new ChatAnthropic({
|
||||||
anthropicApiKey: credentials.apiKey as string,
|
anthropicApiKey: credentials.apiKey as string,
|
||||||
modelName,
|
modelName,
|
||||||
|
@ -217,6 +325,7 @@ export class LmChatAnthropic implements INodeType {
|
||||||
topP: options.topP,
|
topP: options.topP,
|
||||||
callbacks: [new N8nLlmTracing(this, { tokensUsageParser })],
|
callbacks: [new N8nLlmTracing(this, { tokensUsageParser })],
|
||||||
onFailedAttempt: makeN8nLlmFailedAttemptHandler(this),
|
onFailedAttempt: makeN8nLlmFailedAttemptHandler(this),
|
||||||
|
invocationKwargs,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue