diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 7ecead94b692b..16103c2a4fd41 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -19,5 +19,6 @@ services: - ..:/workspaces:cached command: sleep infinity environment: + DB_POSTGRESDB_HOST: postgres DB_TYPE: postgresdb DB_POSTGRESDB_PASSWORD: password diff --git a/.dockerignore b/.dockerignore index fe841322cc28f..1b60244f211f8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,7 @@ packages/**/.turbo packages/**/*.test.* .git .github +!.github/scripts *.tsbuildinfo packages/cli/dist/**/e2e.* docker/compose diff --git a/.github/scripts/ensure-provenance-fields.mjs b/.github/scripts/ensure-provenance-fields.mjs new file mode 100644 index 0000000000000..2fad319a625d5 --- /dev/null +++ b/.github/scripts/ensure-provenance-fields.mjs @@ -0,0 +1,44 @@ +import { writeFile, readFile, copyFile } from 'fs/promises'; +import { resolve, dirname } from 'path'; +import child_process from 'child_process'; +import { fileURLToPath } from 'url'; +import { promisify } from 'util'; + +const exec = promisify(child_process.exec); + +const commonFiles = ['LICENSE.md', 'LICENSE_EE.md']; + +const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const packages = JSON.parse((await exec('pnpm ls -r --only-projects --json')).stdout); + +for (let { name, path, version, private: isPrivate } of packages) { + if (isPrivate) continue; + + const packageFile = resolve(path, 'package.json'); + const packageJson = { + ...JSON.parse(await readFile(packageFile, 'utf-8')), + // Add these fields to all published package.json files to ensure provenance checks pass + license: 'SEE LICENSE IN LICENSE.md', + homepage: 'https://n8n.io', + author: { + name: 'Jan Oberhauser', + email: 'jan@n8n.io', + }, + repository: { + type: 'git', + url: 'git+https://github.com/n8n-io/n8n.git', + }, + }; + + // Copy over LICENSE.md and LICENSE_EE.md into every published package, and ensure they get included in the published package + await Promise.all( + commonFiles.map(async (file) => { + await copyFile(resolve(baseDir, file), resolve(path, file)); + if (packageJson.files && !packageJson.files.includes(file)) { + packageJson.files.push(file); + } + }), + ); + + await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n'); +} diff --git a/.github/scripts/package.json b/.github/scripts/package.json index cd67711fa4c29..80bf0baf11043 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -5,7 +5,7 @@ "debug": "4.3.4", "glob": "10.3.10", "p-limit": "3.1.0", - "picocolors": "1.0.0", + "picocolors": "1.0.1", "semver": "7.5.4", "tempfile": "5.0.0", "typescript": "*" diff --git a/scripts/trim-fe-packageJson.js b/.github/scripts/trim-fe-packageJson.js similarity index 79% rename from scripts/trim-fe-packageJson.js rename to .github/scripts/trim-fe-packageJson.js index 611cb2ec3c50d..791e483a2dd1d 100644 --- a/scripts/trim-fe-packageJson.js +++ b/.github/scripts/trim-fe-packageJson.js @@ -1,12 +1,15 @@ const { writeFileSync } = require('fs'); const { resolve } = require('path'); -const baseDir = resolve(__dirname, '..'); +const baseDir = resolve(__dirname, '../..'); const trimPackageJson = (packageName) => { const filePath = resolve(baseDir, 'packages', packageName, 'package.json'); const { scripts, peerDependencies, devDependencies, dependencies, ...packageJson } = require( filePath, ); + if (packageName === '@n8n/chat') { + packageJson.dependencies = dependencies; + } writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8'); }; diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 938335558e3ba..5e828a702249a 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -23,6 +23,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Build run: pnpm build @@ -38,12 +41,14 @@ jobs: needs: install-and-build strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.4] with: ref: ${{ inputs.branch }} nodeVersion: ${{ matrix.node-version }} cacheKey: ${{ github.sha }}-base:build - collectCoverage: true + collectCoverage: ${{ matrix.node-version == '20.x' }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} lint: name: Lint diff --git a/.github/workflows/ci-postgres-mysql.yml b/.github/workflows/ci-postgres-mysql.yml index 16d446cc920e6..d5abf1fa2652f 100644 --- a/.github/workflows/ci-postgres-mysql.yml +++ b/.github/workflows/ci-postgres-mysql.yml @@ -26,6 +26,9 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Build Backend run: pnpm build:backend @@ -52,6 +55,9 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Restore cached build artifacts uses: actions/cache/restore@v4.0.0 with: @@ -60,7 +66,7 @@ jobs: - name: Test SQLite Pooled working-directory: packages/cli - run: pnpm jest --coverage + run: pnpm jest mysql: name: MySQL @@ -78,6 +84,9 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Restore cached build artifacts uses: actions/cache/restore@v4.0.0 with: @@ -112,6 +121,9 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Restore cached build artifacts uses: actions/cache/restore@v4.0.0 with: diff --git a/.github/workflows/ci-pull-requests.yml b/.github/workflows/ci-pull-requests.yml index 1bdd6c60b96ee..78a5388950d51 100644 --- a/.github/workflows/ci-pull-requests.yml +++ b/.github/workflows/ci-pull-requests.yml @@ -21,6 +21,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Build run: pnpm build diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index 5c91f3832d134..fe5770252b214 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -1,8 +1,9 @@ name: Docker Image CI on: - release: - types: [published] + push: + tags: + - 'n8n@*' jobs: build: diff --git a/.github/workflows/e2e-reusable.yml b/.github/workflows/e2e-reusable.yml index b7dadc4173b9f..beea3bca27cf5 100644 --- a/.github/workflows/e2e-reusable.yml +++ b/.github/workflows/e2e-reusable.yml @@ -99,8 +99,6 @@ jobs: runTests: false install: false build: pnpm build - env: - VUE_APP_MAX_PINNED_DATA_SIZE: 16384 - name: Cypress install working-directory: cypress diff --git a/.github/workflows/linting-reusable.yml b/.github/workflows/linting-reusable.yml index 69bccd9ce6216..2650622bd04e4 100644 --- a/.github/workflows/linting-reusable.yml +++ b/.github/workflows/linting-reusable.yml @@ -33,6 +33,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + - name: Build if: ${{ inputs.cacheKey == '' }} run: pnpm build diff --git a/.github/workflows/notify-pr-status.yml b/.github/workflows/notify-pr-status.yml index 15672ea65200c..1169b02af87c4 100644 --- a/.github/workflows/notify-pr-status.yml +++ b/.github/workflows/notify-pr-status.yml @@ -16,6 +16,7 @@ jobs: (github.event_name == 'pull_request' && github.event.pull_request.merged == false && github.event.action == 'closed') steps: - uses: fjogeleit/http-request-action@dea46570591713c7de04a5b556bf2ff7bdf0aa9c # v1 + if: ${{!contains(github.event.pull_request.labels.*.name, 'community')}} name: Notify env: PR_URL: ${{ github.event.pull_request.html_url }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index d6836423136bd..239c18b51223e 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -45,7 +45,8 @@ jobs: - name: Publish to NPM run: | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - node scripts/trim-fe-packageJson.js + node .github/scripts/trim-fe-packageJson.js + node .github/scripts/ensure-provenance-fields.mjs 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 diff --git a/.github/workflows/test-workflows.yml b/.github/workflows/test-workflows.yml index 8ba22b590cb62..2bb91dd065eb7 100644 --- a/.github/workflows/test-workflows.yml +++ b/.github/workflows/test-workflows.yml @@ -73,6 +73,7 @@ jobs: env: N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} SKIP_STATISTICS_EVENTS: true + DB_SQLITE_POOL_SIZE: 4 # - # name: Export credentials # if: always() diff --git a/.github/workflows/units-tests-reusable.yml b/.github/workflows/units-tests-reusable.yml index 61ed73672e7a6..1f64b2e27c6c3 100644 --- a/.github/workflows/units-tests-reusable.yml +++ b/.github/workflows/units-tests-reusable.yml @@ -22,6 +22,10 @@ on: required: false default: false type: boolean + secrets: + CODECOV_TOKEN: + description: 'Codecov upload token.' + required: false jobs: unit-test: @@ -45,6 +49,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Setup build cache + if: inputs.collectCoverage != true + uses: rharkor/caching-for-turbo@v1.5 + - name: Build if: ${{ inputs.cacheKey == '' }} run: pnpm build @@ -67,6 +75,6 @@ jobs: - name: Upload coverage to Codecov if: inputs.collectCoverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4.5.0 with: - files: packages/@n8n/chat/coverage/cobertura-coverage.xml,packages/@n8n/nodes-langchain/coverage/cobertura-coverage.xml,packages/@n8n/permissions/coverage/cobertura-coverage.xml,packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/@n8n/codemirror-lang/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7610d8609e6ad..06ded0c1cad1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,265 @@ +# [1.55.0](https://github.com/n8n-io/n8n/compare/n8n@1.54.0...n8n@1.55.0) (2024-08-14) + + +### Bug Fixes + +* Add better error handling for chat errors ([#10408](https://github.com/n8n-io/n8n/issues/10408)) ([f82b6e4](https://github.com/n8n-io/n8n/commit/f82b6e4ba9bf527b3a4c17872162d9ae124ead0d)) +* **AI Agent Node:** Fix issues with some tools not populating ([#10406](https://github.com/n8n-io/n8n/issues/10406)) ([51a1edd](https://github.com/n8n-io/n8n/commit/51a1eddbf00393f3881c340cf37cfcca59566c99)) +* **core:** Account for cancelling an execution with no workers available ([#10343](https://github.com/n8n-io/n8n/issues/10343)) ([b044e78](https://github.com/n8n-io/n8n/commit/b044e783e73a499dbd7532a5d489a782d3d021da)) +* **core:** Account for owner when filtering by project ID in `GET /workflows` in Public API ([#10379](https://github.com/n8n-io/n8n/issues/10379)) ([5ac65b3](https://github.com/n8n-io/n8n/commit/5ac65b36bcb1351c6233b951f064f60862f790a5)) +* **core:** Enforce shutdown timer and sequence on `SIGINT` for main ([#10346](https://github.com/n8n-io/n8n/issues/10346)) ([5255793](https://github.com/n8n-io/n8n/commit/5255793afee5653d8356b8e4d2e1009d5cf36164)) +* **core:** Filter out prototype and constructor lookups in expressions ([#10382](https://github.com/n8n-io/n8n/issues/10382)) ([8e7d29a](https://github.com/n8n-io/n8n/commit/8e7d29ad3c4872b1cc147dfcfe9a864ba916692f)) +* **core:** Fix duplicate Redis publisher ([#10392](https://github.com/n8n-io/n8n/issues/10392)) ([45813de](https://github.com/n8n-io/n8n/commit/45813debc963096f63cc0aabe82d9d9f853a39d7)) +* **core:** Fix worker shutdown errors when active executions ([#10353](https://github.com/n8n-io/n8n/issues/10353)) ([e071b73](https://github.com/n8n-io/n8n/commit/e071b73bab34edd4b3e6aef6497514acc504cdc6)) +* **core:** Prevent XSS in user update endpoints ([#10338](https://github.com/n8n-io/n8n/issues/10338)) ([7898498](https://github.com/n8n-io/n8n/commit/78984986a6b4add89df9743b94c113046f1d5ee8)) +* **core:** Prevent XSS via static cache dir ([#10339](https://github.com/n8n-io/n8n/issues/10339)) ([4f392b5](https://github.com/n8n-io/n8n/commit/4f392b5e3e0ee166e85a2e060b3ec7fcf145229b)) +* **core:** Rate limit MFA activation and verification endpoints ([#10330](https://github.com/n8n-io/n8n/issues/10330)) ([b6c47c0](https://github.com/n8n-io/n8n/commit/b6c47c0e3214878d42980d5c9535df52b3984b3c)) +* **editor:** Connect up new project viewer role to the FE ([#9913](https://github.com/n8n-io/n8n/issues/9913)) ([117e2d9](https://github.com/n8n-io/n8n/commit/117e2d968fcc535f32c583ac8f2dc8a84e8cd6bd)) +* **editor:** Enable credential sharing between all types of projects ([#10233](https://github.com/n8n-io/n8n/issues/10233)) ([1cf48cc](https://github.com/n8n-io/n8n/commit/1cf48cc3019c1cf27e2f3c9affd18426237e9064)) +* **editor:** Fix rendering of SVG icons in public chat on iOS ([#10381](https://github.com/n8n-io/n8n/issues/10381)) ([7ab3811](https://github.com/n8n-io/n8n/commit/7ab38114dbf3881afba39287a061446ec4bf0431)) +* **editor:** Fixing XSS vulnerability in toast messages ([#10329](https://github.com/n8n-io/n8n/issues/10329)) ([38bdd9f](https://github.com/n8n-io/n8n/commit/38bdd9f5d0d9ca06fab1a7e1a3e7a4a648a6a89a)) +* **editor:** Revert change that hid swagger docs in the ui ([#10350](https://github.com/n8n-io/n8n/issues/10350)) ([bae49d3](https://github.com/n8n-io/n8n/commit/bae49d3198d4bcc27e7996cd4f7be3132becc98e)) +* **n8n Form Trigger Node:** Fix issue preventing v1 node from working ([#10364](https://github.com/n8n-io/n8n/issues/10364)) ([9b647a9](https://github.com/n8n-io/n8n/commit/9b647a9837434e8b75e3ad754ff5136bb5ac760d)) +* Require mfa code for password change if its enabled ([#10341](https://github.com/n8n-io/n8n/issues/10341)) ([9d7caac](https://github.com/n8n-io/n8n/commit/9d7caacc699f10962783393925a980ec6f1ca975)) +* Require mfa code to disable mfa ([#10345](https://github.com/n8n-io/n8n/issues/10345)) ([3384f52](https://github.com/n8n-io/n8n/commit/3384f52a35b835ba1d8633dc94bab0ad6e7023b3)) + + +### Features + +* Add Ask assistant behind feature flag ([#9995](https://github.com/n8n-io/n8n/issues/9995)) ([5ed2a77](https://github.com/n8n-io/n8n/commit/5ed2a77740db1f02b27c571f4dfdfa206ebdb19c)) +* **AI Transform Node:** New node ([#10405](https://github.com/n8n-io/n8n/issues/10405)) ([4d222ac](https://github.com/n8n-io/n8n/commit/4d222ac19d943b69fd9f87abe5e5c5f5141eed8d)) +* **AI Transform Node:** New node ([#9990](https://github.com/n8n-io/n8n/issues/9990)) ([0de9d56](https://github.com/n8n-io/n8n/commit/0de9d56619ed1c055407353046b8a9ebe78da527)) +* **core:** Allow overriding npm registry for community packages ([#10325](https://github.com/n8n-io/n8n/issues/10325)) ([33a2703](https://github.com/n8n-io/n8n/commit/33a2703429d9eaa41f72d2e7d2da5be60b6c620f)) +* **editor:** Add schema view to expression modal ([#9976](https://github.com/n8n-io/n8n/issues/9976)) ([71b6c67](https://github.com/n8n-io/n8n/commit/71b6c671797024d7b516352fa9b7ecda101ce3b2)) +* **MySQL Node:** Return decimal types as numbers ([#10313](https://github.com/n8n-io/n8n/issues/10313)) ([f744d7c](https://github.com/n8n-io/n8n/commit/f744d7c100be68669d9a3efd0033dd371a3cfaf7)) +* **Okta Node:** Add Okta Node ([#10278](https://github.com/n8n-io/n8n/issues/10278)) ([5cac0f3](https://github.com/n8n-io/n8n/commit/5cac0f339d649cfe5857d33738210cbc1599370b)) + + + +# [1.54.0](https://github.com/n8n-io/n8n/compare/n8n@1.53.0...n8n@1.54.0) (2024-08-07) + + +### Bug Fixes + +* **core:** Ensure OAuth token data is not stubbed in source control ([#10302](https://github.com/n8n-io/n8n/issues/10302)) ([98115e9](https://github.com/n8n-io/n8n/commit/98115e95df8289a8ec400a570a7f256382f8e286)) +* **core:** Fix expressions in webhook nodes(Form, Webhook) to access previous node's data ([#10247](https://github.com/n8n-io/n8n/issues/10247)) ([88a1701](https://github.com/n8n-io/n8n/commit/88a170176a3447e7f847e9cf145aeb867b1c5fcf)) +* **core:** Fix user telemetry bugs ([#10293](https://github.com/n8n-io/n8n/issues/10293)) ([42a0b59](https://github.com/n8n-io/n8n/commit/42a0b594d6ea2527c55a2aa9976c904cf70ecf92)) +* **core:** Make execution and its data creation atomic ([#10276](https://github.com/n8n-io/n8n/issues/10276)) ([ae50bb9](https://github.com/n8n-io/n8n/commit/ae50bb95a8e5bf1cdbf9483da54b84094b82e260)) +* **core:** Make OAuth1/OAuth2 callback not require auth ([#10263](https://github.com/n8n-io/n8n/issues/10263)) ([a8e2774](https://github.com/n8n-io/n8n/commit/a8e2774f5382e202556b5506c7788265786aa973)) +* **core:** Revert transactions until we remove the legacy sqlite driver ([#10299](https://github.com/n8n-io/n8n/issues/10299)) ([1eba7c3](https://github.com/n8n-io/n8n/commit/1eba7c3c763ac5b6b28c1c6fc43fc8c215249292)) +* **core:** Surface enterprise trial error message ([#10267](https://github.com/n8n-io/n8n/issues/10267)) ([432ac1d](https://github.com/n8n-io/n8n/commit/432ac1da59e173ce4c0f2abbc416743d9953ba70)) +* **core:** Upgrade tournament to address some XSS vulnerabilities ([#10277](https://github.com/n8n-io/n8n/issues/10277)) ([43ae159](https://github.com/n8n-io/n8n/commit/43ae159ea40c574f8e41bdfd221ab2bf3268eee7)) +* **core:** VM2 sandbox should not throw on `new Promise` ([#10298](https://github.com/n8n-io/n8n/issues/10298)) ([7e95f9e](https://github.com/n8n-io/n8n/commit/7e95f9e2e40a99871f1b6abcdacb39ac5f857332)) +* **core:** Webhook and form baseUrl missing ([#10290](https://github.com/n8n-io/n8n/issues/10290)) ([8131d66](https://github.com/n8n-io/n8n/commit/8131d66f8ca1b1da00597a12859ee4372148a0c9)) +* **editor:** Enable moving resources only if team projects are available by the license ([#10271](https://github.com/n8n-io/n8n/issues/10271)) ([42ba884](https://github.com/n8n-io/n8n/commit/42ba8841c401126c77158a53dc8fcbb45dfce8fd)) +* **editor:** Fix execution retry button ([#10275](https://github.com/n8n-io/n8n/issues/10275)) ([55f2ffe](https://github.com/n8n-io/n8n/commit/55f2ffe256c91a028cee95c3bbb37a093a1c0f81)) +* **editor:** Update design system Avatar component to show initials also when only firstName or lastName is given ([#10308](https://github.com/n8n-io/n8n/issues/10308)) ([46bbf09](https://github.com/n8n-io/n8n/commit/46bbf09beacad12472d91786b91d845fe2afb26d)) +* **editor:** Update tags filter/editor to not show non existing tag as a selectable option ([#10297](https://github.com/n8n-io/n8n/issues/10297)) ([557a76e](https://github.com/n8n-io/n8n/commit/557a76ec2326de72fb7a8b46fc4353f8fd9b591d)) +* **Invoice Ninja Node:** Fix payment types ([#10196](https://github.com/n8n-io/n8n/issues/10196)) ([c5acbb7](https://github.com/n8n-io/n8n/commit/c5acbb7ec0d24ec9b30c221fa3b2fb615fb9ec7f)) +* Loop node no input data shown ([#10224](https://github.com/n8n-io/n8n/issues/10224)) ([c8ee852](https://github.com/n8n-io/n8n/commit/c8ee852159207be0cfe2c3e0ee8e7b29d838aa35)) + + +### Features + +* **core:** Allow filtering executions and users by project in Public API ([#10250](https://github.com/n8n-io/n8n/issues/10250)) ([7056e50](https://github.com/n8n-io/n8n/commit/7056e50b006bda665f64ce6234c5c1967891c415)) +* **core:** Allow transferring credentials in Public API ([#10259](https://github.com/n8n-io/n8n/issues/10259)) ([07d7b24](https://github.com/n8n-io/n8n/commit/07d7b247f02a9d7185beca7817deb779a3d665dd)) +* **core:** Show sub-node error on the logs pane. Open logs pane on sub-node error ([#10248](https://github.com/n8n-io/n8n/issues/10248)) ([57d1c9a](https://github.com/n8n-io/n8n/commit/57d1c9a99e97308f2f1b8ae05ac3861a835e8e5a)) +* **core:** Support community packages in scaling-mode ([#10228](https://github.com/n8n-io/n8n/issues/10228)) ([88086a4](https://github.com/n8n-io/n8n/commit/88086a41ff5b804b35aa9d9503dc2d48836fe4ec)) +* **core:** Support create, delete, edit role for users in Public API ([#10279](https://github.com/n8n-io/n8n/issues/10279)) ([84efbd9](https://github.com/n8n-io/n8n/commit/84efbd9b9c51f536b21a4f969ab607d277bef692)) +* **core:** Support create, read, update, delete projects in Public API ([#10269](https://github.com/n8n-io/n8n/issues/10269)) ([489ce10](https://github.com/n8n-io/n8n/commit/489ce100634c3af678fb300e9a39d273042542e6)) +* **editor:** Auto-add LLM chain for new LLM nodes on empty canvas ([#10245](https://github.com/n8n-io/n8n/issues/10245)) ([06419d9](https://github.com/n8n-io/n8n/commit/06419d9483ae916e79aace6d8c17e265b419b15d)) +* **Elasticsearch Node:** Add bulk operations for Elasticsearch ([#9940](https://github.com/n8n-io/n8n/issues/9940)) ([bf8f848](https://github.com/n8n-io/n8n/commit/bf8f848645dfd31527713a55bd1fc93865327017)) +* **Lemlist Trigger Node:** Update Trigger events ([#10311](https://github.com/n8n-io/n8n/issues/10311)) ([15f10ec](https://github.com/n8n-io/n8n/commit/15f10ec325cb5eda0f952bed3a5f171dd91bc639)) +* **MongoDB Node:** Add projection to query options on Find ([#9972](https://github.com/n8n-io/n8n/issues/9972)) ([0a84e0d](https://github.com/n8n-io/n8n/commit/0a84e0d8b047669f5cf023c21383d01c929c5b4f)) +* **Postgres Chat Memory, Redis Chat Memory, Xata:** Add support for context window length ([#10203](https://github.com/n8n-io/n8n/issues/10203)) ([e3edeaa](https://github.com/n8n-io/n8n/commit/e3edeaa03526f041d15d1099ea91869e38a0decc)) +* **Stripe Trigger Node:** Add Stripe webhook descriptions based on the workflow ID and name ([#9956](https://github.com/n8n-io/n8n/issues/9956)) ([3433465](https://github.com/n8n-io/n8n/commit/34334651e0e6874736a437a894176bed4590e5a7)) +* **Webflow Node:** Update to use the v2 API ([#9996](https://github.com/n8n-io/n8n/issues/9996)) ([6d8323f](https://github.com/n8n-io/n8n/commit/6d8323fadea8af04483eb1a873df0cf3ccc2a891)) + + + +# [1.53.0](https://github.com/n8n-io/n8n/compare/n8n@1.52.0...n8n@1.53.0) (2024-07-31) + + +### Bug Fixes + +* Better error message when calling data transformation functions on a null value ([#10210](https://github.com/n8n-io/n8n/issues/10210)) ([1718125](https://github.com/n8n-io/n8n/commit/1718125c6d8589cf24dc8d34f6808dd6f1802691)) +* **core:** Fix missing successful items on continueErrorOutput with multiple outputs ([#10218](https://github.com/n8n-io/n8n/issues/10218)) ([1a7713e](https://github.com/n8n-io/n8n/commit/1a7713ef263680da43f08b6c8a15aee7a0341493)) +* **core:** Flush instance stopped event immediately ([#10238](https://github.com/n8n-io/n8n/issues/10238)) ([d6770b5](https://github.com/n8n-io/n8n/commit/d6770b5fcaec6438d677b918aaeb1669ad7424c2)) +* **core:** Restore log event `n8n.workflow.failed` ([#10253](https://github.com/n8n-io/n8n/issues/10253)) ([3e96b29](https://github.com/n8n-io/n8n/commit/3e96b293329525c9d4b2fcef87b3803e458c8e7f)) +* **core:** Upgrade @n8n/vm2 to address CVE‑2023‑37466 ([#10265](https://github.com/n8n-io/n8n/issues/10265)) ([2a09a03](https://github.com/n8n-io/n8n/commit/2a09a036d2e916acff7ee50904f1d011a93758e1)) +* **editor:** Defer `User saved credentials` telemetry event for OAuth credentials ([#10215](https://github.com/n8n-io/n8n/issues/10215)) ([40a5226](https://github.com/n8n-io/n8n/commit/40a5226e24448a4428143e69d80ebc78238365a1)) +* **editor:** Fix custom API call notice ([#10227](https://github.com/n8n-io/n8n/issues/10227)) ([5b47c8b](https://github.com/n8n-io/n8n/commit/5b47c8b57b25528cd2d6f97bc6d98707d47f35bc)) +* **editor:** Fix issue with existing credential not opening in HTTP agent tool ([#10167](https://github.com/n8n-io/n8n/issues/10167)) ([906b4c3](https://github.com/n8n-io/n8n/commit/906b4c3c7b2919111cf23eaa12b3c4d507969179)) +* **editor:** Fix parameter input glitch when there was an error loading remote options ([#10209](https://github.com/n8n-io/n8n/issues/10209)) ([c0e3743](https://github.com/n8n-io/n8n/commit/c0e37439a87105a0e66c8ebced42c06dab30dc5e)) +* **editor:** Fix workflow execution list scrolling after filter change ([#10226](https://github.com/n8n-io/n8n/issues/10226)) ([7e64358](https://github.com/n8n-io/n8n/commit/7e643589c67adc0218216ec4b89a95f0edfedbee)) +* **Google BigQuery Node:** Send timeoutMs in query, pagination support ([#10205](https://github.com/n8n-io/n8n/issues/10205)) ([f5722e8](https://github.com/n8n-io/n8n/commit/f5722e8823ccd2bc2b5f43ba3c849797d5690a93)) +* **Google Sheets Node:** Add column names row if sheet is empty ([#10200](https://github.com/n8n-io/n8n/issues/10200)) ([82eba9f](https://github.com/n8n-io/n8n/commit/82eba9fc5ff49b8e2a9db93c10b253fb67a8c644)) +* **Google Sheets Node:** Do not insert row_number as a new column, do not checkForSchemaChanges in update operation ([#10201](https://github.com/n8n-io/n8n/issues/10201)) ([5136d10](https://github.com/n8n-io/n8n/commit/5136d10ca3492f92af67d4a1d4abc774419580cc)) +* **Google Sheets Node:** Fix Google Sheet URL regex ([#10195](https://github.com/n8n-io/n8n/issues/10195)) ([e6fd996](https://github.com/n8n-io/n8n/commit/e6fd996973d4f40facf0ebf1eea3cc26acd0603d)) +* **HTTP Request Node:** Resolve max pages expression ([#10192](https://github.com/n8n-io/n8n/issues/10192)) ([bfc8e1b](https://github.com/n8n-io/n8n/commit/bfc8e1b56f7714e1f52aae747d58d686b86e60f0)) +* **LinkedIn Node:** Fix issue with some characters cutting off posts early ([#10185](https://github.com/n8n-io/n8n/issues/10185)) ([361b5e7](https://github.com/n8n-io/n8n/commit/361b5e7c37ba49b68dcf5b8122621aad4d8d96e0)) +* **Postgres Node:** Expressions in query parameters for Postgres executeQuery operation ([#10217](https://github.com/n8n-io/n8n/issues/10217)) ([519fc4d](https://github.com/n8n-io/n8n/commit/519fc4d75325a80b84cc4dcacf52d6f4c02e3a44)) +* **Postgres Node:** Option to treat query parameters enclosed in single quotas as text ([#10214](https://github.com/n8n-io/n8n/issues/10214)) ([00ec253](https://github.com/n8n-io/n8n/commit/00ec2533374d3def465efee718592fc4001d5602)) +* **Read/Write Files from Disk Node:** Notice update in file selector, replace backslashes with forward slashes if windows path ([#10186](https://github.com/n8n-io/n8n/issues/10186)) ([3eac673](https://github.com/n8n-io/n8n/commit/3eac673b17986c5c74bd2adb5ad589ba0ca55319)) +* **Text Classifier Node:** Use proper documentation URL and respect continueOnFail ([#10216](https://github.com/n8n-io/n8n/issues/10216)) ([452f52c](https://github.com/n8n-io/n8n/commit/452f52c124017e002e86c547ba42b1633b14beed)) +* **Trello Node:** Use body for POST requests ([#10189](https://github.com/n8n-io/n8n/issues/10189)) ([7775d50](https://github.com/n8n-io/n8n/commit/7775d5059b7f69d9af22e7ad7d12c6cf9092a4e5)) +* **Wait Node:** Authentication fix ([#10236](https://github.com/n8n-io/n8n/issues/10236)) ([f87854f](https://github.com/n8n-io/n8n/commit/f87854f8db360b7b870583753fcfb4af95adab8c)) + + +### Features + +* **Calendly Trigger Node:** Add OAuth Credentials Support ([#10251](https://github.com/n8n-io/n8n/issues/10251)) ([326c983](https://github.com/n8n-io/n8n/commit/326c983915a2c382e32398358e7dcadd022c0b77)) +* **core:** Allow filtering workflows by project and transferring workflows in Public API ([#10231](https://github.com/n8n-io/n8n/issues/10231)) ([d719899](https://github.com/n8n-io/n8n/commit/d719899223907b20a17883a35e4ef637a3453532)) +* **editor:** Show new executions as `Queued` in the UI, until they actually start ([#10204](https://github.com/n8n-io/n8n/issues/10204)) ([44728d7](https://github.com/n8n-io/n8n/commit/44728d72423f5549dda09589f4a618ebd80899cb)) +* **HTTP Request Node:** Add option to disable lowercase headers ([#10154](https://github.com/n8n-io/n8n/issues/10154)) ([5aba69b](https://github.com/n8n-io/n8n/commit/5aba69bcf4d232d9860f3cd9fe57cb8839a2f96f)) +* **Information Extractor Node:** Add new simplified AI-node for information extraction ([#10149](https://github.com/n8n-io/n8n/issues/10149)) ([3d235b0](https://github.com/n8n-io/n8n/commit/3d235b0b2df756df35ac60e3dcd87ad183a07167)) +* Introduce Google Cloud Platform as external secrets provider ([#10146](https://github.com/n8n-io/n8n/issues/10146)) ([3ccb9df](https://github.com/n8n-io/n8n/commit/3ccb9df2f902e46f8cbb9c46c0727f29d752a773)) +* **n8n Form Trigger Node:** Improvements ([#10092](https://github.com/n8n-io/n8n/issues/10092)) ([711b667](https://github.com/n8n-io/n8n/commit/711b667ebefe55740e5eb39f1f0f24ceee10e7b0)) +* Recovery option for jsonParse helper ([#10182](https://github.com/n8n-io/n8n/issues/10182)) ([d165b33](https://github.com/n8n-io/n8n/commit/d165b33ceac4d24d0fc290bffe63b5f551204e38)) +* **Sentiment Analysis Node:** Implement Sentiment Analysis node ([#10184](https://github.com/n8n-io/n8n/issues/10184)) ([8ef0a0c](https://github.com/n8n-io/n8n/commit/8ef0a0c58ac2a84aad649ccbe72aa907d005cc44)) +* **Shopify Node:** Update Shopify API version ([#10155](https://github.com/n8n-io/n8n/issues/10155)) ([e2ee915](https://github.com/n8n-io/n8n/commit/e2ee91569a382bfbf787cf45204c72c821a860a0)) +* Support create, read, delete variables in Public API ([#10241](https://github.com/n8n-io/n8n/issues/10241)) ([af695eb](https://github.com/n8n-io/n8n/commit/af695ebf934526d926ea87fe87df61aa73d70979)) + + + +# [1.52.0](https://github.com/n8n-io/n8n/compare/n8n@1.51.0...n8n@1.52.0) (2024-07-24) + + +### Bug Fixes + +* **core:** Fix handling of common events for relays ([#10135](https://github.com/n8n-io/n8n/issues/10135)) ([d2a3a4a](https://github.com/n8n-io/n8n/commit/d2a3a4a080cdcc04f50fa33fd81d361efce3f709)) +* **core:** Fix SSH Tunnels when using private key ([#10148](https://github.com/n8n-io/n8n/issues/10148)) ([a96db34](https://github.com/n8n-io/n8n/commit/a96db344e54658787426d967dfa299c7a6dd14e7)) +* **core:** Metadata inserts using existing IDs and failing with postgres ([#10108](https://github.com/n8n-io/n8n/issues/10108)) ([4547a49](https://github.com/n8n-io/n8n/commit/4547a49db15a20f5f147e859b6c2c01f60f9565c)) +* **core:** Respect prefix for all Prometheus metrics ([#10130](https://github.com/n8n-io/n8n/issues/10130)) ([b1816db](https://github.com/n8n-io/n8n/commit/b1816db449ed451443f353b69166b7ca700ba51e)) +* **core:** Support branches containing slashes in source control ([#10109](https://github.com/n8n-io/n8n/issues/10109)) ([03a833d](https://github.com/n8n-io/n8n/commit/03a833db51a25dda6cf0d8494f06c6704f6f3c7f)) +* **core:** Support execution recovery when saving execution progress ([#10104](https://github.com/n8n-io/n8n/issues/10104)) ([d887c82](https://github.com/n8n-io/n8n/commit/d887c82d808a79babc726fc789cc014194ae2ac6)) +* **editor:** Allow `$secrets` to resolve on credentials ([#10093](https://github.com/n8n-io/n8n/issues/10093)) ([bf57f38](https://github.com/n8n-io/n8n/commit/bf57f38d1c417ba8b20144934c8e97a75c1f51cc)) +* **editor:** Fix saving and connecting on LDAP setup form ([#10163](https://github.com/n8n-io/n8n/issues/10163)) ([30784fb](https://github.com/n8n-io/n8n/commit/30784fb76cec790a782fae40973a956a8d81c0b2)) +* **editor:** Fix updating/uninstalling community nodes ([#10138](https://github.com/n8n-io/n8n/issues/10138)) ([de015ff](https://github.com/n8n-io/n8n/commit/de015ff2978a5ee3345449626025c6d0793b6f5a)) +* **editor:** Remove "move" action from workflow and credential on community plan ([#10057](https://github.com/n8n-io/n8n/issues/10057)) ([5a9a271](https://github.com/n8n-io/n8n/commit/5a9a2713b499cc7dcddb500a54e24bbf7145b504)) +* **editor:** UX Improvements to RBAC feature set ([#9683](https://github.com/n8n-io/n8n/issues/9683)) ([028a8a2](https://github.com/n8n-io/n8n/commit/028a8a2c754e4f6d6a5f0918a656eb4554eb869f)) +* **HelpScout Node:** Fix issue with thread types not working correctly ([#10084](https://github.com/n8n-io/n8n/issues/10084)) ([68d3beb](https://github.com/n8n-io/n8n/commit/68d3bebfeebea9054bbbaebac31c2e3fa34336bb)) +* **MQTT Node:** Node hangs forever on failed connection ([#10048](https://github.com/n8n-io/n8n/issues/10048)) ([76c2906](https://github.com/n8n-io/n8n/commit/76c290655de7d4e626725a05fd991a0858cca0d7)) +* **n8n Form Trigger Node:** Execution from canvas ([#10132](https://github.com/n8n-io/n8n/issues/10132)) ([b07c5e2](https://github.com/n8n-io/n8n/commit/b07c5e201165165c4e91ddd19b6fa79703ba2a9c)) +* **Notion Node:** Fix issue preventing some database page urls from working ([#10070](https://github.com/n8n-io/n8n/issues/10070)) ([7848c19](https://github.com/n8n-io/n8n/commit/7848c19f543d5f5f62b89cc5644639c6afdb8fa6)) +* **RabbitMQ Node:** Fix issue with arguments not being sent ([#9397](https://github.com/n8n-io/n8n/issues/9397)) ([1c666e6](https://github.com/n8n-io/n8n/commit/1c666e6e7c2be2e2d0dcc528870fddfa8b02318b)) + + +### Features + +* **editor:** Split Tools and Models into sub-sections ([#10159](https://github.com/n8n-io/n8n/issues/10159)) ([3846eb9](https://github.com/n8n-io/n8n/commit/3846eb967afd77dba6f037e8185ed94494454d5a)) +* Introduce Azure Key Vault as external secrets provider ([#10054](https://github.com/n8n-io/n8n/issues/10054)) ([1b6c2d3](https://github.com/n8n-io/n8n/commit/1b6c2d3a37a78ed07ada93be2a57e4b7f7149e58)) +* **Pinecone Vector Store Node, Supabase Vector Store Node:** Add update operation to vector store nodes ([#10060](https://github.com/n8n-io/n8n/issues/10060)) ([7e1eeb4](https://github.com/n8n-io/n8n/commit/7e1eeb4c31d3f25ec31baa7390b11a7e3280ce01)) +* **Send Email Node:** Smtp credential improvements ([#10147](https://github.com/n8n-io/n8n/issues/10147)) ([dc13ceb](https://github.com/n8n-io/n8n/commit/dc13ceb41649eab42ef073247f3b52c040826e98)) + + + +# [1.51.0](https://github.com/n8n-io/n8n/compare/n8n@1.50.0...n8n@1.51.0) (2024-07-17) + + +### Bug Fixes + +* **AMQP Sender Node:** Node hangs forever on disconnect ([#10026](https://github.com/n8n-io/n8n/issues/10026)) ([27410ab](https://github.com/n8n-io/n8n/commit/27410ab2af87573045f38e14e7e20bedd3b0365d)) +* **AMQP Trigger Node:** Manual execution updated error reduced wait time ([#10035](https://github.com/n8n-io/n8n/issues/10035)) ([f78f4ea](https://github.com/n8n-io/n8n/commit/f78f4ea3492560bc7056023fd0276990f3ac9b00)) +* **AWS Comprehend Node:** Add paired item support ([#10015](https://github.com/n8n-io/n8n/issues/10015)) ([470d496](https://github.com/n8n-io/n8n/commit/470d4966c67a3e4155d59e6fadab467b73134ec4)) +* **core:** Ensure executions cannot resume if already running ([#10014](https://github.com/n8n-io/n8n/issues/10014)) ([d651be4](https://github.com/n8n-io/n8n/commit/d651be4e01a869a6f7d70e691e0f5e244f59490e)) +* **core:** Redact `csrfSecret` when returning oauth credentials to the frontend ([#10075](https://github.com/n8n-io/n8n/issues/10075)) ([48f047e](https://github.com/n8n-io/n8n/commit/48f047ee2ecbfbd364151816df5fc21e09ca72a6)) +* **core:** Stopping an execution should reject any response promises ([#9992](https://github.com/n8n-io/n8n/issues/9992)) ([36b314d](https://github.com/n8n-io/n8n/commit/36b314d0311ef84f275efbc20997c6a77db81b31)) +* **editor:** Ensure all static assets are accessible from the server ([#10062](https://github.com/n8n-io/n8n/issues/10062)) ([3bde845](https://github.com/n8n-io/n8n/commit/3bde8453efa9a4d14404c63bdc061c87843d49d2)) +* **editor:** Handle disabled nodes in schema view ([#10052](https://github.com/n8n-io/n8n/issues/10052)) ([ab5688c](https://github.com/n8n-io/n8n/commit/ab5688c582c05afd7d3e0967eda0f5dc73d6d3ed)) +* **editor:** Make schema view use the correct output ([#10016](https://github.com/n8n-io/n8n/issues/10016)) ([c29664d](https://github.com/n8n-io/n8n/commit/c29664d68851ec33e4d810fa24aba72bb6cecc86)) +* **editor:** Provide autocomplete for nodes, even when intermediate node has not run ([#10036](https://github.com/n8n-io/n8n/issues/10036)) ([46d6edc](https://github.com/n8n-io/n8n/commit/46d6edc2a4edd49ae58c0c60977809554e07f4ee)) +* **editor:** Remove push event listeners when migrating away from the canvas ([#10063](https://github.com/n8n-io/n8n/issues/10063)) ([0d12f0a](https://github.com/n8n-io/n8n/commit/0d12f0a6b36aaaae5e1f9fab8ad73feeba9ec5ed)) +* **editor:** Use selected input item for autocomplete ([#10019](https://github.com/n8n-io/n8n/issues/10019)) ([1d2b403](https://github.com/n8n-io/n8n/commit/1d2b403644278fa6158272edc4295d4565554e37)) +* **Email Trigger (IMAP) Node:** Reconnect not working correctly ([#10064](https://github.com/n8n-io/n8n/issues/10064)) ([68d5d7e](https://github.com/n8n-io/n8n/commit/68d5d7e2e90ede5d021a12304dd665247dde5243)) +* Filter component - array contains comparison not correct when ignore case option set to true ([#10012](https://github.com/n8n-io/n8n/issues/10012)) ([4a3b97c](https://github.com/n8n-io/n8n/commit/4a3b97cede531adbf81274c1ec2ce4ee400cb48e)) +* **GitHub Node:** File Create operation prevent duplicated base64 encoding ([#10040](https://github.com/n8n-io/n8n/issues/10040)) ([9bcc926](https://github.com/n8n-io/n8n/commit/9bcc926a91d7afab0c2ef6eb57e818ef79e3a8f7)) +* **HTTP Request Node:** Respect the original encoding of the incoming response ([#9869](https://github.com/n8n-io/n8n/issues/9869)) ([2d19aef](https://github.com/n8n-io/n8n/commit/2d19aef54083d97e94e50a1ee58e8525bbf28548)) +* HTTP Request tool - allow hyphens in placeholders ([#10037](https://github.com/n8n-io/n8n/issues/10037)) ([8cd9370](https://github.com/n8n-io/n8n/commit/8cd93704bee116eceb0e3bd5fa849c4b314454ec)) +* HTTP Request tool - do not error on missing headers ([#10044](https://github.com/n8n-io/n8n/issues/10044)) ([04b62e0](https://github.com/n8n-io/n8n/commit/04b62e0398eafd923d5f27a3e1c71b925ddb8817)) +* **HubSpot Node:** Migrate from v2 owners api ([#10013](https://github.com/n8n-io/n8n/issues/10013)) ([56dd491](https://github.com/n8n-io/n8n/commit/56dd491bcaeab1d11d7874f190eaf20d2e315ca1)) +* Number input defaults to 0 not allowing to have arbitrary precision ([#10021](https://github.com/n8n-io/n8n/issues/10021)) ([e4e66ab](https://github.com/n8n-io/n8n/commit/e4e66ab7da5651fede8b3065419ffb393a2fd16d)) +* **OpenAI Chat Model Node:** Respect baseURL override for /models ([#10076](https://github.com/n8n-io/n8n/issues/10076)) ([e5dda57](https://github.com/n8n-io/n8n/commit/e5dda5731dfbb50f5aaf2b152f9c5bc89b1d80a6)) +* **Telegram Trigger Node:** Fix issue with videos not being downloaded ([#10007](https://github.com/n8n-io/n8n/issues/10007)) ([e84ab35](https://github.com/n8n-io/n8n/commit/e84ab35c4ab0ec47bdbd4343e58c62bbb70f3ec9)) +* **Webhook Node:** Binary property option name and description update ([#10043](https://github.com/n8n-io/n8n/issues/10043)) ([9302e33](https://github.com/n8n-io/n8n/commit/9302e33d558564bb5ba172eaeb8c300693b87286)) + + +### Features + +* **Asana Node:** Add support for project privacy settings ([#10027](https://github.com/n8n-io/n8n/issues/10027)) ([429481c](https://github.com/n8n-io/n8n/commit/429481c5c4b7f448739a561596873038185ba467)) +* Better error when calling expression function on input that is undefined or null ([#10009](https://github.com/n8n-io/n8n/issues/10009)) ([519e57b](https://github.com/n8n-io/n8n/commit/519e57bda5115149357fb2b1c2270e481ea09e38)) +* **editor:** Make expression autocomplete search case-insensitive ([#10017](https://github.com/n8n-io/n8n/issues/10017)) ([cde6fe9](https://github.com/n8n-io/n8n/commit/cde6fe90e5c8a9c5983e27f0d82599425fba915b)) +* **editor:** Tweak node creator search logic for AI sub-nodes ([#10025](https://github.com/n8n-io/n8n/issues/10025)) ([7db1656](https://github.com/n8n-io/n8n/commit/7db16561dc890849e2d5742bb73f9d5b8e79e37d)) +* **Google Vertex Chat Model Node:** Add support for Google Vertex AI Chat models ([#9970](https://github.com/n8n-io/n8n/issues/9970)) ([071130a](https://github.com/n8n-io/n8n/commit/071130a2dc0b450eb6ce6d39fe28cfeefd05633c)) +* **Postgres Chat Memory Node:** Implement Postgres Chat Memory node ([#10071](https://github.com/n8n-io/n8n/issues/10071)) ([9cbbb63](https://github.com/n8n-io/n8n/commit/9cbbb6335df0d36f66f22c18041d12f14dc59b32)) +* **Text Classifier Node:** Add Text Classifier Node ([#9997](https://github.com/n8n-io/n8n/issues/9997)) ([28ca7d6](https://github.com/n8n-io/n8n/commit/28ca7d6a2dd818c8795acda6ddf7329b8621d9de)) + + + +# [1.50.0](https://github.com/n8n-io/n8n/compare/n8n@1.49.0...n8n@1.50.0) (2024-07-10) + + +### Bug Fixes + +* **core:** Aborting manual trigger tests should call `closeFunction` ([#9980](https://github.com/n8n-io/n8n/issues/9980)) ([6107798](https://github.com/n8n-io/n8n/commit/61077985163037ed3c6a8e9e7476cd6c525ff5f2)) +* **core:** Allow owner and admin to edit nodes with credentials that haven't been shared with them explicitly ([#9922](https://github.com/n8n-io/n8n/issues/9922)) ([0f49598](https://github.com/n8n-io/n8n/commit/0f495986f89b60ec9bb86801f9779ee9aa87ccfb)) +* **core:** Clear active execution on cancellation in scaling mode ([#9979](https://github.com/n8n-io/n8n/issues/9979)) ([7e972c7](https://github.com/n8n-io/n8n/commit/7e972c78afaf950effec17d8eee16cbf86101d03)) +* **core:** Disconnect Redis after pausing queue during worker shutdown ([#9928](https://github.com/n8n-io/n8n/issues/9928)) ([c82579b](https://github.com/n8n-io/n8n/commit/c82579bf760cc4b5a2670b14e4e48fc37e2e2263)) +* **core:** Don't execute 'workflowExecuteBefore' hook on execution continuations ([#9905](https://github.com/n8n-io/n8n/issues/9905)) ([adb8315](https://github.com/n8n-io/n8n/commit/adb83155ca9478a548e6fe926735d5872de10fea)) +* **core:** Prevent multiple values in the execution metadata for the same key and executionId ([#9953](https://github.com/n8n-io/n8n/issues/9953)) ([2e6b03b](https://github.com/n8n-io/n8n/commit/2e6b03b2cb471aefa8104b7b80cf12e64f16e4fb)) +* **Google Sheets Node:** Append fails if cells have some default values added by data validation rules ([#9950](https://github.com/n8n-io/n8n/issues/9950)) ([d1821eb](https://github.com/n8n-io/n8n/commit/d1821eba9221eb243b62ad561193102b24dd05a5)) +* **Invoice Ninja Node:** Fix assigning an invoice to a payment ([#9590](https://github.com/n8n-io/n8n/issues/9590)) ([7a3c127](https://github.com/n8n-io/n8n/commit/7a3c127b2cbea01f9a21c8d517d1dc919bc8121f)) +* **Invoice Ninja Node:** Fix emailing and marking invoice as paid / sent ([#9589](https://github.com/n8n-io/n8n/issues/9589)) ([908ddd8](https://github.com/n8n-io/n8n/commit/908ddd8a24e8a858d9c1eddf2f727234e66a62f7)) + + +### Features + +* **Chat Trigger Node:** Add support for file uploads & harmonize public and development chat ([#9802](https://github.com/n8n-io/n8n/issues/9802)) ([df78315](https://github.com/n8n-io/n8n/commit/df783151b86e2db3e325d3b9d85f4abb71d3d246)) +* **Google Cloud Firestore Node:** Add support for service account and document creation with id ([#9713](https://github.com/n8n-io/n8n/issues/9713)) ([cb1bbf5](https://github.com/n8n-io/n8n/commit/cb1bbf5fd395ec4855ac21d851b180c8526b698a)) +* **Orbit Node:** Deprecate Orbit nicely ([#9962](https://github.com/n8n-io/n8n/issues/9962)) ([9577d9c](https://github.com/n8n-io/n8n/commit/9577d9c847b56d9907d2bbe9ec85127bb8f67cfa)) +* Qdrant Vector Store search filter ([#9900](https://github.com/n8n-io/n8n/issues/9900)) ([fbe4bca](https://github.com/n8n-io/n8n/commit/fbe4bca634e8e03c9455843e1a1f89706d1557d2)) +* **Splunk Node:** Overhaul ([#9813](https://github.com/n8n-io/n8n/issues/9813)) ([e5c3247](https://github.com/n8n-io/n8n/commit/e5c324753fb41752f9722d61c5d336d6e5c67cca)) +* **Telegram Node:** Add support to Keyboard Button Mini Apps ([#9511](https://github.com/n8n-io/n8n/issues/9511)) ([3a17943](https://github.com/n8n-io/n8n/commit/3a179439c7586189b8264131fd16da9d14f074b6)) + + + +# [1.49.0](https://github.com/n8n-io/n8n/compare/n8n@1.48.0...n8n@1.49.0) (2024-07-03) + + +### Bug Fixes + +* **core:** Add a WebCrypto Polyfill for older versions of Node.js 18 ([#9894](https://github.com/n8n-io/n8n/issues/9894)) ([59c8bf1](https://github.com/n8n-io/n8n/commit/59c8bf1c44057b3f798645a22ad16362401ebeed)) +* **core:** Don't allow using credentials that are not part of the same project ([#9916](https://github.com/n8n-io/n8n/issues/9916)) ([ab2a548](https://github.com/n8n-io/n8n/commit/ab2a5488560a814fc72c0c5cd71e5f62f05cd235)) +* **core:** Filter out certain executions from crash recovery ([#9904](https://github.com/n8n-io/n8n/issues/9904)) ([7044d1c](https://github.com/n8n-io/n8n/commit/7044d1ca2841b6d87ae929072bb94dda82909795)) +* **core:** Fix AddActivatedAtUserSetting migration on MariaDB ([#9910](https://github.com/n8n-io/n8n/issues/9910)) ([db29e84](https://github.com/n8n-io/n8n/commit/db29e84666b814fd4710dc3ade6e53304216fad5)) +* **core:** Fix execution cancellation in scaling mode ([#9841](https://github.com/n8n-io/n8n/issues/9841)) ([e613de2](https://github.com/n8n-io/n8n/commit/e613de28ca2db23746b586e0a0b33f1c1ee1abe5)) +* **core:** Fix worker logs relay ([#9919](https://github.com/n8n-io/n8n/issues/9919)) ([7c53433](https://github.com/n8n-io/n8n/commit/7c5343319144ce3524b14018eef77eace221b608)) +* **core:** Throw on adding execution without execution data ([#9903](https://github.com/n8n-io/n8n/issues/9903)) ([abb7458](https://github.com/n8n-io/n8n/commit/abb74587db88a56453b269826885df0d01766290)) +* **editor:** Don't try to load credentials on the demo route ([#9926](https://github.com/n8n-io/n8n/issues/9926)) ([b80df2a](https://github.com/n8n-io/n8n/commit/b80df2a47ebe4450862e200c9cf47f6e94012c91)) +* **editor:** Enable expression preview in SQL node when looking at executions ([#9733](https://github.com/n8n-io/n8n/issues/9733)) ([d9747d5](https://github.com/n8n-io/n8n/commit/d9747d5e9b42d7f379f6f4219b960893b7b153b3)) +* **editor:** Fix frontend project roles ([#9901](https://github.com/n8n-io/n8n/issues/9901)) ([f229577](https://github.com/n8n-io/n8n/commit/f2295772094ff936e210f52ebcbc938915d1c129)) +* **editor:** Fix new node credential creation via Resource Locator Component ([#9896](https://github.com/n8n-io/n8n/issues/9896)) ([55cbc90](https://github.com/n8n-io/n8n/commit/55cbc900a48c579b712dddfa74e133e1d9c11799)) +* **editor:** Fix performance issues related to expressions and pinned data ([#9882](https://github.com/n8n-io/n8n/issues/9882)) ([13d83f2](https://github.com/n8n-io/n8n/commit/13d83f2037d659fccc8889dd994ddd984467d987)) +* **editor:** Improve text wrapping in schema view ([#9888](https://github.com/n8n-io/n8n/issues/9888)) ([dc1c5fc](https://github.com/n8n-io/n8n/commit/dc1c5fce8af732c438d2f1698ee08f18d2358a6c)) +* **Execute Workflow Node:** Continue on fail behaviour not correctly implemented ([#9890](https://github.com/n8n-io/n8n/issues/9890)) ([16b1a09](https://github.com/n8n-io/n8n/commit/16b1a094b19e5f803a460b99c6062a1175bec153)) +* **LinkedIn Node:** Fix issue with legacy credential no longer working ([#9912](https://github.com/n8n-io/n8n/issues/9912)) ([873b7e5](https://github.com/n8n-io/n8n/commit/873b7e59dcea276c9f792570805a6de8ad4607a3)) + + +### Features + +* Add Zep Cloud Memory component ([#9657](https://github.com/n8n-io/n8n/issues/9657)) ([41c47a2](https://github.com/n8n-io/n8n/commit/41c47a28a9d4502287ca1bbbb4704f2763288a11)) +* **Copper Node:** Update credential to support HTTP Request node ([#9837](https://github.com/n8n-io/n8n/issues/9837)) ([e6ad5a7](https://github.com/n8n-io/n8n/commit/e6ad5a71935a5f82168bf300246ccb3535648b0b)) +* **editor:** Add docs sidebar to credential modal ([#9914](https://github.com/n8n-io/n8n/issues/9914)) ([b2f8ea7](https://github.com/n8n-io/n8n/commit/b2f8ea7918d7e10e91db0e04ef5b7ad40a5bdbb5)) +* **editor:** Remove Segment ([#9878](https://github.com/n8n-io/n8n/issues/9878)) ([10f7d4b](https://github.com/n8n-io/n8n/commit/10f7d4b5b92013407c9a4eb9edd619d385efe10f)) +* **Embeddings Cohere Node:** Add v3 Cohere models ([#9887](https://github.com/n8n-io/n8n/issues/9887)) ([403e19b](https://github.com/n8n-io/n8n/commit/403e19b3e316db81b62eb456b38e7325bf13529c)) +* **GitHub Node:** Add support for state reasons when editing an issue ([#9848](https://github.com/n8n-io/n8n/issues/9848)) ([61c20d1](https://github.com/n8n-io/n8n/commit/61c20d1ae3c65b04c767c5b704c4fc4efd356ccf)) +* Introduce debug info button ([#9895](https://github.com/n8n-io/n8n/issues/9895)) ([be9a247](https://github.com/n8n-io/n8n/commit/be9a247577ffc28559a23fea2db9b2c598dca036)) +* **Merge Node:** Overhaul, v3 ([#9528](https://github.com/n8n-io/n8n/issues/9528)) ([af69c80](https://github.com/n8n-io/n8n/commit/af69c80bf5a22f80979405041210dc77d2682c51)) +* **Vector Store Tool Node:** Add Vector Store Tool ([#9865](https://github.com/n8n-io/n8n/issues/9865)) ([df2bc84](https://github.com/n8n-io/n8n/commit/df2bc84d2b3830d31319c108f1b01256de95e774)) +* **Zammad Node:** Add reply_to and sender fields to article on ticket creation ([#9911](https://github.com/n8n-io/n8n/issues/9911)) ([957b2d6](https://github.com/n8n-io/n8n/commit/957b2d6108dccd9495291c4764816cc27e112e87)) + + + # [1.48.0](https://github.com/n8n-io/n8n/compare/n8n@1.47.0...n8n@1.48.0) (2024-06-27) diff --git a/README.md b/README.md index 145ecab8c62d6..d51ac596cad65 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,8 @@ development environment ready in minutes. ## License n8n is [fair-code](https://faircode.io) distributed under the -[**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) and the -[**n8n Enterprise License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE_EE.md). +[**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) and the +[**n8n Enterprise License**](https://github.com/n8n-io/n8n/blob/master/LICENSE_EE.md). Proprietary licenses are available for enterprise customers. [Get in touch](mailto:license@n8n.io) diff --git a/cypress/composables/modals/chat-modal.ts b/cypress/composables/modals/chat-modal.ts index 31e139c93e6bc..254d811a18405 100644 --- a/cypress/composables/modals/chat-modal.ts +++ b/cypress/composables/modals/chat-modal.ts @@ -7,15 +7,15 @@ export function getManualChatModal() { } export function getManualChatInput() { - return cy.getByTestId('workflow-chat-input'); + return getManualChatModal().get('.chat-inputs textarea'); } export function getManualChatSendButton() { - return getManualChatModal().getByTestId('workflow-chat-send-button'); + return getManualChatModal().get('.chat-input-send-button'); } export function getManualChatMessages() { - return getManualChatModal().get('.messages .message'); + return getManualChatModal().get('.chat-messages-list .chat-message'); } export function getManualChatModalCloseButton() { diff --git a/cypress/composables/modals/save-changes-modal.ts b/cypress/composables/modals/save-changes-modal.ts new file mode 100644 index 0000000000000..d44b09bd460f7 --- /dev/null +++ b/cypress/composables/modals/save-changes-modal.ts @@ -0,0 +1,3 @@ +export function getSaveChangesModal() { + return cy.get('.el-overlay').contains('Save changes before leaving?'); +} diff --git a/cypress/composables/projects.ts b/cypress/composables/projects.ts index de5669ae11afd..84379088d1a39 100644 --- a/cypress/composables/projects.ts +++ b/cypress/composables/projects.ts @@ -1,41 +1,45 @@ import { CredentialsModal, WorkflowPage } from '../pages'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const credentialsModal = new CredentialsModal(); export const getHomeButton = () => cy.getByTestId('project-home-menu-item'); export const getMenuItems = () => cy.getByTestId('project-menu-item'); -export const getAddProjectButton = () => cy.getByTestId('add-project-menu-item'); +export const getAddProjectButton = () => + cy.getByTestId('add-project-menu-item').should('contain', 'Add project').should('be.visible'); export const getProjectTabs = () => cy.getByTestId('project-tabs').find('a'); export const getProjectTabWorkflows = () => getProjectTabs().filter('a[href$="/workflows"]'); export const getProjectTabCredentials = () => getProjectTabs().filter('a[href$="/credentials"]'); export const getProjectTabSettings = () => getProjectTabs().filter('a[href$="/settings"]'); -export const getProjectSettingsNameInput = () => cy.getByTestId('project-settings-name-input'); +export const getProjectSettingsNameInput = () => + cy.getByTestId('project-settings-name-input').find('input'); export const getProjectSettingsSaveButton = () => cy.getByTestId('project-settings-save-button'); export const getProjectSettingsCancelButton = () => cy.getByTestId('project-settings-cancel-button'); export const getProjectSettingsDeleteButton = () => cy.getByTestId('project-settings-delete-button'); export const getProjectMembersSelect = () => cy.getByTestId('project-members-select'); -export const addProjectMember = (email: string) => { +export const addProjectMember = (email: string, role?: string) => { getProjectMembersSelect().click(); getProjectMembersSelect().get('.el-select-dropdown__item').contains(email.toLowerCase()).click(); + + if (role) { + cy.getByTestId(`user-list-item-${email}`) + .find('[data-test-id="projects-settings-user-role-select"]') + .click(); + getVisibleSelect().find('li').contains(role).click(); + } }; -export const getProjectNameInput = () => cy.get('#projectName'); export const getResourceMoveModal = () => cy.getByTestId('project-move-resource-modal'); export const getResourceMoveConfirmModal = () => cy.getByTestId('project-move-resource-confirm-modal'); export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-modal-select'); export function createProject(name: string) { - getAddProjectButton().should('be.visible').click(); + getAddProjectButton().click(); - getProjectNameInput() - .should('be.visible') - .should('be.focused') - .should('have.value', 'My project') - .clear() - .type(name); + getProjectSettingsNameInput().should('be.visible').clear().type(name); getProjectSettingsSaveButton().click(); } @@ -46,7 +50,7 @@ export function createWorkflow(fixtureKey: string, name: string) { workflowPage.actions.zoomToFit(); } -export function createCredential(name: string) { +export function createCredential(name: string, closeModal = true) { credentialsModal.getters.newCredentialModal().should('be.visible'); credentialsModal.getters.newCredentialTypeSelect().should('be.visible'); credentialsModal.getters.newCredentialTypeOption('Notion API').click(); @@ -54,13 +58,8 @@ export function createCredential(name: string) { credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); credentialsModal.actions.setName(name); credentialsModal.actions.save(); - credentialsModal.actions.close(); -} -export const actions = { - createProject: (name: string) => { - getAddProjectButton().click(); - getProjectSettingsNameInput().type(name); - getProjectSettingsSaveButton().click(); - }, -}; + if (closeModal) { + credentialsModal.actions.close(); + } +} diff --git a/cypress/constants.ts b/cypress/constants.ts index 7efd9b0470664..6f7e7b978d317 100644 --- a/cypress/constants.ts +++ b/cypress/constants.ts @@ -37,6 +37,7 @@ export const INSTANCE_MEMBERS = [ export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger'; export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking ‘Test workflow’'; export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger'; +export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received'; export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger'; export const CODE_NODE_NAME = 'Code'; export const SET_NODE_NAME = 'Set'; @@ -53,9 +54,11 @@ export const AGENT_NODE_NAME = 'AI Agent'; 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_TOOL_CALCULATOR_NODE_NAME = 'Calculator'; -export const AI_TOOL_CODE_NODE_NAME = 'Custom Code Tool'; +export const AI_TOOL_CODE_NODE_NAME = 'Code Tool'; export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia'; +export const AI_TOOL_HTTP_NODE_NAME = 'HTTP Request Tool'; export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'; +export const AI_MEMORY_POSTGRES_NODE_NAME = 'Postgres Chat Memory'; export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser'; export const WEBHOOK_NODE_NAME = 'Webhook'; diff --git a/cypress/cypress.config.js b/cypress/cypress.config.js index c82f039994f26..63913af7f8717 100644 --- a/cypress/cypress.config.js +++ b/cypress/cypress.config.js @@ -25,9 +25,4 @@ module.exports = defineConfig({ screenshotsFolder: 'screenshots', videosFolder: 'videos', }, - env: { - MAX_PINNED_DATA_SIZE: process.env.VUE_APP_MAX_PINNED_DATA_SIZE - ? parseInt(process.env.VUE_APP_MAX_PINNED_DATA_SIZE, 10) - : 16 * 1024, - }, }); diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index 3227f68323184..645344337675b 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -20,24 +20,6 @@ describe('Undo/Redo', () => { WorkflowPage.actions.visit(); }); - it('should undo/redo adding nodes', () => { - WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); - WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 0); - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 1); - }); - - it('should undo/redo adding connected nodes', () => { - WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 1); - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 2); - WorkflowPage.getters.nodeConnections().should('have.length', 1); - }); - it('should undo/redo adding node in the middle', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); @@ -224,21 +206,6 @@ describe('Undo/Redo', () => { WorkflowPage.getters.disabledNodes().should('have.length', 2); }); - it('should undo/redo renaming node using NDV', () => { - WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters.canvasNodes().last().click(); - cy.get('body').type('{enter}'); - ndv.actions.rename(CODE_NODE_NEW_NAME); - cy.get('body').type('{esc}'); - WorkflowPage.actions.hitUndo(); - cy.get('body').type('{esc}'); - WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).should('exist'); - WorkflowPage.actions.hitRedo(); - cy.get('body').type('{esc}'); - WorkflowPage.getters.canvasNodeByName(CODE_NODE_NEW_NAME).should('exist'); - }); - it('should undo/redo renaming node using keyboard shortcut', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); diff --git a/cypress/e2e/12-canvas-actions.cy.ts b/cypress/e2e/12-canvas-actions.cy.ts index 9b05cb84d4130..53dad1cc89669 100644 --- a/cypress/e2e/12-canvas-actions.cy.ts +++ b/cypress/e2e/12-canvas-actions.cy.ts @@ -145,7 +145,16 @@ describe('Canvas Actions', () => { }); }); - it('should delete connections by pressing the delete button', () => { + it('should delete node by pressing keyboard backspace', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click(); + cy.get('body').type('{backspace}'); + WorkflowPage.getters.nodeConnections().should('have.length', 0); + }); + + it('should delete connections by clicking on the delete button', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts index 468e73276ac9e..4558c44bca8cb 100644 --- a/cypress/e2e/13-pinning.cy.ts +++ b/cypress/e2e/13-pinning.cy.ts @@ -12,8 +12,13 @@ const workflowPage = new WorkflowPage(); const ndv = new NDV(); describe('Data pinning', () => { + const maxPinnedDataSize = 16384; + beforeEach(() => { workflowPage.actions.visit(); + cy.window().then((win) => { + win.maxPinnedDataSize = maxPinnedDataSize; + }); }); it('Should be able to pin node output', () => { @@ -139,7 +144,7 @@ describe('Data pinning', () => { ndv.actions.pastePinnedData([ { - test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE') as number), + test: '1'.repeat(maxPinnedDataSize), }, ]); errorToast().should('contain', 'Workflow has reached the maximum allowed pinned data size'); diff --git a/cypress/e2e/14-mapping.cy.ts b/cypress/e2e/14-mapping.cy.ts index 0579bba24dea5..2e405e69e88c4 100644 --- a/cypress/e2e/14-mapping.cy.ts +++ b/cypress/e2e/14-mapping.cy.ts @@ -44,7 +44,7 @@ describe('Data mapping', () => { ndv.actions.mapDataFromHeader(2, 'value'); ndv.getters .inlineExpressionEditorInput() - .should('have.text', "{{ $json.timestamp }} {{ $json['Readable date'] }}"); + .should('have.text', "{{ $json['Readable date'] }}{{ $json.timestamp }}"); }); it('maps expressions from table json, and resolves value based on hover', () => { @@ -145,8 +145,8 @@ describe('Data mapping', () => { ndv.actions.mapToParameter('value'); ndv.getters .inlineExpressionEditorInput() - .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.actions.validateExpressionPreview('value', '0 [object Object]'); + .should('have.text', '{{ $json.input }}{{ $json.input[0].count }}'); + ndv.actions.validateExpressionPreview('value', '[object Object]0'); }); it('maps expressions from schema view', () => { @@ -170,8 +170,8 @@ describe('Data mapping', () => { ndv.actions.mapToParameter('value'); ndv.getters .inlineExpressionEditorInput() - .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.actions.validateExpressionPreview('value', '0 [object Object]'); + .should('have.text', '{{ $json.input }}{{ $json.input[0].count }}'); + ndv.actions.validateExpressionPreview('value', '[object Object]0'); }); it('maps expressions from previous nodes', () => { @@ -200,17 +200,17 @@ describe('Data mapping', () => { .inlineExpressionEditorInput() .should( 'have.text', - `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }} {{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input }}`, + `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input }}{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`, ); ndv.actions.selectInputNode('Set'); ndv.getters.executingLoader().should('not.exist'); ndv.getters.inputDataContainer().should('exist'); - ndv.actions.validateExpressionPreview('value', '0 [object Object]'); + ndv.actions.validateExpressionPreview('value', '[object Object]0'); ndv.getters.inputTbodyCell(2, 0).realHover(); - ndv.actions.validateExpressionPreview('value', '1 [object Object]'); + ndv.actions.validateExpressionPreview('value', '[object Object]1'); }); it('maps keys to path', () => { @@ -284,8 +284,8 @@ describe('Data mapping', () => { ndv.actions.mapToParameter('value'); ndv.getters .inlineExpressionEditorInput() - .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.actions.validateExpressionPreview('value', '0 [object Object]'); + .should('have.text', '{{ $json.input }}{{ $json.input[0].count }}'); + ndv.actions.validateExpressionPreview('value', '[object Object]0'); }); it('renders expression preview when a previous node is selected', () => { @@ -317,19 +317,12 @@ describe('Data mapping', () => { workflowPage.actions.zoomToFit(); workflowPage.actions.openNode('Set'); - ndv.actions.clearParameterInput('value'); - cy.get('body').type('{esc}'); - ndv.getters.parameterInput('includeOtherFields').find('input[type="checkbox"]').should('exist'); ndv.getters.parameterInput('includeOtherFields').find('input[type="text"]').should('not.exist'); - ndv.getters - .inputDataContainer() - .should('exist') - .find('span') - .contains('count') - .realMouseDown() - .realMouseMove(100, 100); - cy.wait(50); + const pill = ndv.getters.inputDataContainer().find('span').contains('count'); + pill.should('be.visible'); + pill.realMouseDown(); + pill.realMouseMove(100, 100); ndv.getters .parameterInput('includeOtherFields') @@ -340,13 +333,36 @@ describe('Data mapping', () => { .find('input[type="text"]') .should('exist') .invoke('css', 'border') - .then((border) => expect(border).to.include('dashed rgb(90, 76, 194)')); + .should('include', 'dashed rgb(90, 76, 194)'); ndv.getters .parameterInput('value') .find('input[type="text"]') .should('exist') .invoke('css', 'border') - .then((border) => expect(border).to.include('dashed rgb(90, 76, 194)')); + .should('include', 'dashed rgb(90, 76, 194)'); + }); + + it('maps expressions to a specific location in the editor', () => { + cy.fixture('Test_workflow_3.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + workflowPage.actions.zoomToFit(); + + workflowPage.actions.openNode('Set'); + ndv.actions.clearParameterInput('value'); + ndv.actions.typeIntoParameterInput('value', '='); + ndv.actions.typeIntoParameterInput('value', 'hello world{enter}{enter}newline'); + + ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown(); + + ndv.actions.mapToParameter('value'); + + ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown(); + ndv.actions.mapToParameter('value', 'bottom'); + + ndv.getters + .inlineExpressionEditorInput() + .should('have.text', '{{ $json.input[0].count }}hello worldnewline{{ $json.input }}'); }); }); diff --git a/cypress/e2e/15-scheduler-node.cy.ts b/cypress/e2e/15-scheduler-node.cy.ts index fecaef038a7d6..65f0904543692 100644 --- a/cypress/e2e/15-scheduler-node.cy.ts +++ b/cypress/e2e/15-scheduler-node.cy.ts @@ -1,9 +1,5 @@ -import { WorkflowPage, WorkflowsPage, NDV } from '../pages'; -import { BACKEND_BASE_URL } from '../constants'; -import { getVisibleSelect } from '../utils'; -import type { ExecutionResponse } from '../types'; +import { WorkflowPage, NDV } from '../pages'; -const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -19,53 +15,4 @@ describe('Schedule Trigger node', () => { ndv.getters.outputPanel().contains('timestamp'); ndv.getters.backToCanvas().click(); }); - - it('should execute once per second when activated', () => { - workflowPage.actions.renameWorkflow('Schedule Trigger Workflow'); - workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); - workflowPage.actions.openNode('Schedule Trigger'); - - cy.getByTestId('parameter-input-field').click(); - getVisibleSelect().find('.option-headline').contains('Seconds').click(); - cy.getByTestId('parameter-input-secondsInterval').clear().type('1'); - - ndv.getters.backToCanvas().click(); - workflowPage.actions.saveWorkflowOnButtonClick(); - workflowPage.actions.activateWorkflow(); - workflowPage.getters.activatorSwitch().should('have.class', 'is-checked'); - - cy.url().then((url) => { - const workflowId = url.split('/').pop(); - - cy.wait(1200); - cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then( - (response) => { - expect(response.status).to.eq(200); - expect(workflowId).to.not.be.undefined; - expect(response.body.data.results.length).to.be.greaterThan(0); - const matchingExecutions = response.body.data.results.filter( - (execution) => execution.workflowId === workflowId, - ); - expect(matchingExecutions).to.have.length(1); - - cy.wait(1200); - cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then( - (response1) => { - expect(response1.status).to.eq(200); - expect(response1.body.data.results.length).to.be.greaterThan(0); - const matchingExecutions1 = response1.body.data.results.filter( - (execution: any) => execution.workflowId === workflowId, - ); - expect(matchingExecutions1).to.have.length(2); - - workflowPage.actions.activateWorkflow(); - workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked'); - cy.visit(workflowsPage.url); - workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow'); - }, - ); - }, - ); - }); - }); }); diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index 15c63eb6c209f..64769ae1935b8 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -7,7 +7,7 @@ import { WorkflowSharingModal, WorkflowsPage, } from '../pages'; -import { getVisibleSelect } from '../utils'; +import { getVisibleDropdown, getVisibleSelect } from '../utils'; import * as projects from '../composables/projects'; /** @@ -135,7 +135,11 @@ describe('Sharing', { disableAutoLogin: true }, () => { workflowsPage.getters.workflowCards().should('have.length', 2); workflowsPage.getters.workflowCard('Workflow W1').click(); workflowPage.actions.openNode('Notion'); - ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled'); + ndv.getters + .credentialInput() + .find('input') + .should('have.value', 'Credential C1') + .should('be.enabled'); ndv.actions.close(); cy.waitForLoad(); @@ -188,11 +192,79 @@ describe('Sharing', { disableAutoLogin: true }, () => { credentialsModal.actions.saveSharing(); credentialsModal.actions.close(); }); + + it('credentials should work between team and personal projects', () => { + cy.resetDatabase(); + cy.enableFeature('sharing'); + cy.enableFeature('advancedPermissions'); + cy.enableFeature('projectRole:admin'); + cy.enableFeature('projectRole:editor'); + cy.changeQuota('maxTeamProjects', -1); + + cy.signinAsOwner(); + cy.visit('/'); + + projects.createProject('Development'); + + projects.getHomeButton().click(); + workflowsPage.getters.newWorkflowButtonCard().click(); + projects.createWorkflow('Test_workflow_1.json', 'Test workflow'); + + projects.getHomeButton().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.emptyListCreateCredentialButton().click(); + projects.createCredential('Notion API'); + + credentialsPage.getters.credentialCard('Notion API').click(); + credentialsModal.actions.changeTab('Sharing'); + credentialsModal.getters.usersSelect().click(); + getVisibleSelect() + .find('li') + .should('have.length', 4) + .filter(':contains("Development")') + .should('have.length', 1) + .click(); + credentialsModal.getters.saveButton().click(); + credentialsModal.actions.close(); + + projects.getProjectTabWorkflows().click(); + workflowsPage.getters.workflowCardActions('Test workflow').click(); + getVisibleDropdown().find('li').contains('Share').click(); + + workflowSharingModal.getters.usersSelect().filter(':visible').click(); + getVisibleSelect().find('li').should('have.length', 3).first().click(); + workflowSharingModal.getters.saveButton().click(); + + projects.getMenuItems().first().click(); + workflowsPage.getters.newWorkflowButtonCard().click(); + projects.createWorkflow('Test_workflow_1.json', 'Test workflow 2'); + workflowPage.actions.openShareModal(); + workflowSharingModal.getters.usersSelect().should('not.exist'); + + cy.get('body').type('{esc}'); + + projects.getMenuItems().first().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.createCredentialButton().click(); + projects.createCredential('Notion API 2', false); + credentialsModal.actions.changeTab('Sharing'); + credentialsModal.getters.usersSelect().click(); + getVisibleSelect().find('li').should('have.length', 4).first().click(); + credentialsModal.getters.saveButton().click(); + credentialsModal.actions.close(); + + credentialsPage.getters + .credentialCards() + .should('have.length', 2) + .filter(':contains("Owned by me")') + .should('have.length', 1); + }); }); describe('Credential Usage in Cross Shared Workflows', () => { beforeEach(() => { cy.resetDatabase(); + cy.enableFeature('sharing'); cy.enableFeature('advancedPermissions'); cy.enableFeature('projectRole:admin'); cy.enableFeature('projectRole:editor'); @@ -203,23 +275,18 @@ describe('Credential Usage in Cross Shared Workflows', () => { }); it('should only show credentials from the same team project', () => { - cy.enableFeature('advancedPermissions'); - cy.enableFeature('projectRole:admin'); - cy.enableFeature('projectRole:editor'); - cy.changeQuota('maxTeamProjects', -1); - // Create a notion credential in the home project credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); // Create a notion credential in one project - projects.actions.createProject('Development'); + projects.createProject('Development'); projects.getProjectTabCredentials().click(); credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); // Create a notion credential in another project - projects.actions.createProject('Test'); + projects.createProject('Test'); projects.getProjectTabCredentials().click(); credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); @@ -235,9 +302,6 @@ describe('Credential Usage in Cross Shared Workflows', () => { }); it('should only show credentials in their personal project for members', () => { - cy.enableFeature('sharing'); - cy.reload(); - // Create a notion credential as the owner credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsModal.actions.createNewCredential('Notion API'); @@ -267,8 +331,6 @@ describe('Credential Usage in Cross Shared Workflows', () => { it('should only show credentials in their personal project for members if the workflow was shared with them', () => { const workflowName = 'Test workflow'; - cy.enableFeature('sharing'); - cy.reload(); // Create a notion credential as the owner and a workflow that is shared // with member 0 @@ -299,7 +361,6 @@ describe('Credential Usage in Cross Shared Workflows', () => { it("should show all credentials from all personal projects the workflow's been shared into for the global owner", () => { const workflowName = 'Test workflow'; - cy.enableFeature('sharing'); // As member 1, create a new notion credential. This should not show up. cy.signinAsMember(1); @@ -344,8 +405,6 @@ describe('Credential Usage in Cross Shared Workflows', () => { }); it('should show all personal credentials if the global owner owns the workflow', () => { - cy.enableFeature('sharing'); - // As member 0, create a new notion credential. cy.signinAsMember(); cy.visit(credentialsPage.url); diff --git a/cypress/e2e/17-workflow-tags.cy.ts b/cypress/e2e/17-workflow-tags.cy.ts index cede363006f30..fc889aead29c2 100644 --- a/cypress/e2e/17-workflow-tags.cy.ts +++ b/cypress/e2e/17-workflow-tags.cy.ts @@ -1,4 +1,5 @@ import { WorkflowPage } from '../pages'; +import { getVisibleSelect } from '../utils'; const wf = new WorkflowPage(); @@ -51,28 +52,6 @@ describe('Workflow tags', () => { wf.getters.tagPills().should('have.length', 0); // none attached }); - it('should update a tag via modal', () => { - wf.actions.openTagManagerModal(); - - const [first] = TEST_TAGS; - - cy.contains('Create a tag').click(); - cy.getByTestId('tags-table').find('input').type(first).type('{enter}'); - cy.getByTestId('tags-table').should('contain.text', first); - cy.getByTestId('edit-tag-button').eq(-1).click({ force: true }); - cy.wait(300); - cy.getByTestId('tags-table') - .find('.el-input--large') - .should('be.visible') - .type(' Updated') - .type('{enter}'); - cy.contains('Done').click(); - wf.getters.createTagButton().click(); - wf.getters.tagsInDropdown().should('have.length', 1); // one stored - wf.getters.tagsInDropdown().contains('Updated').should('exist'); - wf.getters.tagPills().should('have.length', 0); // none attached - }); - it('should detach a tag inline by clicking on X on tag pill', () => { wf.getters.createTagButton().click(); wf.actions.addTags(TEST_TAGS); @@ -92,4 +71,20 @@ describe('Workflow tags', () => { wf.getters.workflowTags().click(); wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1); }); + + it('should not show non existing tag as a selectable option', () => { + const NON_EXISTING_TAG = 'My Test Tag'; + + wf.getters.createTagButton().click(); + wf.actions.addTags(TEST_TAGS); + cy.get('body').click(0, 0); + wf.getters.workflowTags().click(); + wf.getters.tagsDropdown().find('input:focus').type(NON_EXISTING_TAG); + + getVisibleSelect() + .find('li') + .should('have.length', 2) + .filter(`:contains("${NON_EXISTING_TAG}")`) + .should('not.have.length'); + }); }); diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts index 804e81d4e605b..1f7d5c332eb50 100644 --- a/cypress/e2e/19-execution.cy.ts +++ b/cypress/e2e/19-execution.cy.ts @@ -275,7 +275,6 @@ describe('Execution', () => { .within(() => cy.get('.fa-check').should('not.exist')); successToast().should('be.visible'); - clearNotifications(); // Clear execution data workflowPage.getters.clearExecutionDataButton().should('be.visible'); diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index c96a792218423..ffecd51959861 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -1,5 +1,7 @@ -import type { ICredentialType } from 'n8n-workflow'; +import { type ICredentialType } from 'n8n-workflow'; import { + AGENT_NODE_NAME, + AI_TOOL_HTTP_NODE_NAME, GMAIL_NODE_NAME, HTTP_REQUEST_NODE_NAME, NEW_GOOGLE_ACCOUNT_NAME, @@ -201,6 +203,31 @@ describe('Credentials', () => { .should('have.value', NEW_CREDENTIAL_NAME2); }); + it('should edit credential for non-standard credential type', () => { + workflowPage.actions.visit(); + workflowPage.actions.addNodeToCanvas(AGENT_NODE_NAME); + workflowPage.actions.addNodeToCanvas(AI_TOOL_HTTP_NODE_NAME); + workflowPage.getters.canvasNodes().last().click(); + cy.get('body').type('{enter}'); + cy.getByTestId('parameter-input-authentication').click(); + cy.contains('Predefined Credential Type').click(); + cy.getByTestId('credential-select').click(); + cy.contains('Adalo API').click(); + workflowPage.getters.nodeCredentialsSelect().click(); + getVisibleSelect().find('li').last().click(); + credentialsModal.actions.fillCredentialsForm(); + workflowPage.getters.nodeCredentialsEditButton().click(); + credentialsModal.getters.credentialsEditModal().should('be.visible'); + credentialsModal.getters.name().click(); + credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME); + credentialsModal.getters.saveButton().click(); + credentialsModal.getters.closeButton().click(); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_CREDENTIAL_NAME); + }); + it('should setup generic authentication for HTTP node', () => { workflowPage.actions.visit(); workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 2431289761111..59f08c570b0ff 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -98,45 +98,6 @@ describe('Current Workflow Executions', () => { .should('be.visible'); }); - it('should auto load more items if there is space and auto scroll', () => { - cy.viewport(1280, 960); - executionsTab.actions.createManualExecutions(24); - - cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); - cy.intercept('GET', '/rest/executions/*').as('getExecution'); - executionsTab.actions.switchToExecutionsTab(); - - cy.wait(['@getExecutions']); - executionsTab.getters.executionListItems().its('length').should('be.gte', 10); - - cy.getByTestId('current-executions-list').scrollTo('bottom'); - cy.wait(['@getExecutions']); - executionsTab.getters.executionListItems().should('have.length', 24); - - executionsTab.getters.executionListItems().eq(14).click(); - cy.wait(['@getExecution']); - cy.reload(); - - cy.wait(['@getExecutions']); - executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); - executionsTab.getters.executionListItems().should('have.length', 24); - executionsTab.getters.executionListItems().first().should('not.be.visible'); - cy.getByTestId('current-executions-list').scrollTo(0, 0); - executionsTab.getters.executionListItems().first().should('be.visible'); - executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); - - executionsTab.actions.switchToEditorTab(); - executionsTab.actions.switchToExecutionsTab(); - - cy.wait(['@getExecutions']); - executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); - executionsTab.getters.executionListItems().should('have.length', 24); - executionsTab.getters.executionListItems().first().should('not.be.visible'); - cy.getByTestId('current-executions-list').scrollTo(0, 0); - executionsTab.getters.executionListItems().first().should('be.visible'); - executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); - }); - it('should show workflow data in executions tab after hard reload and modify name and tags', () => { executionsTab.actions.switchToExecutionsTab(); checkMainHeaderELements(); @@ -222,6 +183,50 @@ describe('Current Workflow Executions', () => { .invoke('attr', 'title') .should('eq', newWorkflowName); }); + + it('should load items and auto scroll after filter change', () => { + createMockExecutions(); + createMockExecutions(); + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + + executionsTab.getters.executionsList().scrollTo(0, 500).wait(0); + + executionsTab.getters.executionListItems().eq(10).click(); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-status-select').should('be.visible').click(); + getVisibleSelect().find('li:contains("Error")').click(); + + executionsTab.getters.executionListItems().should('have.length', 5); + executionsTab.getters.successfulExecutionListItems().should('have.length', 1); + executionsTab.getters.failedExecutionListItems().should('have.length', 4); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-status-select').should('be.visible').click(); + getVisibleSelect().find('li:contains("Success")').click(); + + // check if the list is scrolled + executionsTab.getters.executionListItems().eq(10).should('be.visible'); + executionsTab.getters.executionsList().then(($el) => { + const { scrollTop, scrollHeight, clientHeight } = $el[0]; + expect(scrollTop).to.be.greaterThan(0); + expect(scrollTop + clientHeight).to.be.lessThan(scrollHeight); + + // scroll to the bottom + $el[0].scrollTo(0, scrollHeight); + executionsTab.getters.executionListItems().should('have.length', 18); + executionsTab.getters.successfulExecutionListItems().should('have.length', 18); + executionsTab.getters.failedExecutionListItems().should('have.length', 0); + }); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-reset-button').should('be.visible').click(); + executionsTab.getters.executionListItems().eq(11).should('be.visible'); + }); }); const createMockExecutions = () => { diff --git a/cypress/e2e/21-community-nodes.cy.ts b/cypress/e2e/21-community-nodes.cy.ts index b9d10b30f2589..bf88d3d24ce91 100644 --- a/cypress/e2e/21-community-nodes.cy.ts +++ b/cypress/e2e/21-community-nodes.cy.ts @@ -6,6 +6,13 @@ import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_cred import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json'; import CustomCredential from '../fixtures/Custom_credential.json'; import { getVisibleSelect } from '../utils'; +import { + confirmCommunityNodeUninstall, + confirmCommunityNodeUpdate, + getCommunityCards, + installFirstCommunityNode, + visitCommunityNodesSettings, +} from '../pages/settings-community-nodes'; const credentialsModal = new CredentialsModal(); const nodeCreatorFeature = new NodeCreator(); @@ -14,7 +21,7 @@ const workflowPage = new WorkflowPage(); // We separate-out the custom nodes because they require injecting nodes and credentials // so the /nodes and /credentials endpoints are intercepted and non-cached. // We want to keep the other tests as fast as possible so we don't want to break the cache in those. -describe('Community Nodes', () => { +describe('Community and custom nodes in canvas', () => { beforeEach(() => { cy.intercept('/types/nodes.json', { middleware: true }, (req) => { req.headers['cache-control'] = 'no-cache, no-store'; @@ -95,3 +102,89 @@ describe('Community Nodes', () => { credentialsModal.getters.editCredentialModal().should('contain.text', 'Custom E2E Credential'); }); }); + +describe('Community nodes', () => { + const mockPackage = { + createdAt: '2024-07-22T19:08:06.505Z', + updatedAt: '2024-07-22T19:08:06.505Z', + packageName: 'n8n-nodes-chatwork', + installedVersion: '1.0.0', + authorName: null, + authorEmail: null, + installedNodes: [ + { + name: 'Chatwork', + type: 'n8n-nodes-chatwork.chatwork', + latestVersion: 1, + }, + ], + updateAvailable: '1.1.2', + }; + + it('can install, update and uninstall community nodes', () => { + cy.intercept( + { + hostname: 'api.npms.io', + pathname: '/v2/search', + query: { q: 'keywords:n8n-community-node-package' }, + }, + { body: {} }, + ); + cy.intercept( + { method: 'GET', pathname: '/rest/community-packages', times: 1 }, + { + body: { data: [] }, + }, + ).as('getEmptyPackages'); + visitCommunityNodesSettings(); + cy.wait('@getEmptyPackages'); + + // install a package + cy.intercept( + { method: 'POST', pathname: '/rest/community-packages', times: 1 }, + { + body: { data: mockPackage }, + }, + ).as('installPackage'); + cy.intercept( + { method: 'GET', pathname: '/rest/community-packages', times: 1 }, + { + body: { data: [mockPackage] }, + }, + ).as('getPackages'); + installFirstCommunityNode('n8n-nodes-chatwork@1.0.0'); + cy.wait('@installPackage'); + cy.wait('@getPackages'); + getCommunityCards().should('have.length', 1); + getCommunityCards().eq(0).should('include.text', 'v1.0.0'); + + // update the package + cy.intercept( + { method: 'PATCH', pathname: '/rest/community-packages' }, + { + body: { data: { ...mockPackage, installedVersion: '1.2.0', updateAvailable: undefined } }, + }, + ).as('updatePackage'); + getCommunityCards().eq(0).find('button').click(); + confirmCommunityNodeUpdate(); + cy.wait('@updatePackage'); + getCommunityCards().should('have.length', 1); + getCommunityCards().eq(0).should('not.include.text', 'v1.0.0'); + + // uninstall the package + cy.intercept( + { + method: 'DELETE', + pathname: '/rest/community-packages', + query: { name: 'n8n-nodes-chatwork' }, + }, + { statusCode: 204 }, + ).as('uninstallPackage'); + getCommunityCards().getByTestId('action-toggle').click(); + cy.getByTestId('action-uninstall').click(); + confirmCommunityNodeUninstall(); + cy.wait('@uninstallPackage'); + + cy.getByTestId('action-box').should('exist'); + }); +}); diff --git a/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts b/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts index 0870c2546ca07..e26a7acb825d3 100644 --- a/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts +++ b/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts @@ -1,5 +1,4 @@ import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages'; -import { BACKEND_BASE_URL } from '../constants'; const workflowPage = new WorkflowPageClass(); const executionsTab = new WorkflowExecutionsTab(); @@ -17,33 +16,6 @@ describe('ADO-2106 connections should be colored correctly for pinned data in ex workflowPage.getters.getConnectionBetweenNodes('Webhook', 'Set').should('have.class', 'pinned'); }); - it('should not color connections for pinned data nodes for production executions', () => { - workflowPage.actions.activateWorkflow(); - - // Execute the workflow - cy.request('POST', `${BACKEND_BASE_URL}/webhook/23fc3930-b8f9-41d9-89db-b647291a2201`, { - here: 'is some data', - }).then((response) => { - expect(response.status).to.eq(200); - }); - - executionsTab.actions.switchToExecutionsTab(); - - executionsTab.getters.successfulExecutionListItems().should('have.length', 1); - - executionsTab.getters - .workflowExecutionPreviewIframe() - .should('be.visible') - .its('0.contentDocument.body') - .should('not.be.empty') - - .then(cy.wrap) - .find('.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]') - .should('have.class', 'success') - .should('have.class', 'has-run') - .should('not.have.class', 'pinned'); - }); - it('should color connections for pinned data nodes for manual executions', () => { workflowPage.actions.executeWorkflow(); diff --git a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts new file mode 100644 index 0000000000000..4c733df90dc48 --- /dev/null +++ b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts @@ -0,0 +1,279 @@ +import type { ExecutionError } from 'n8n-workflow/src'; +import { NDV, WorkflowPage as WorkflowPageClass } from '../pages'; +import { + addLanguageModelNodeToParent, + addMemoryNodeToParent, + addNodeToCanvas, + addToolNodeToParent, + navigateToNewWorkflowPage, + openNode, +} from '../composables/workflow'; +import { + AGENT_NODE_NAME, + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AI_MEMORY_POSTGRES_NODE_NAME, + AI_TOOL_CALCULATOR_NODE_NAME, + CHAT_TRIGGER_NODE_DISPLAY_NAME, + MANUAL_CHAT_TRIGGER_NODE_NAME, + MANUAL_TRIGGER_NODE_DISPLAY_NAME, + MANUAL_TRIGGER_NODE_NAME, +} from '../constants'; +import { + clickCreateNewCredential, + clickExecuteNode, + clickGetBackToCanvas, +} from '../composables/ndv'; +import { setCredentialValues } from '../composables/modals/credential-modal'; +import { + closeManualChatModal, + getManualChatMessages, + getManualChatModalLogs, + getManualChatModalLogsEntries, + sendManualChatMessage, +} from '../composables/modals/chat-modal'; +import { createMockNodeExecutionData, getVisibleSelect, runMockWorkflowExecution } from '../utils'; + +const ndv = new NDV(); +const WorkflowPage = new WorkflowPageClass(); + +function createRunDataWithError(inputMessage: string) { + return [ + createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, { + jsonData: { + main: { input: inputMessage }, + }, + }), + createMockNodeExecutionData(AI_MEMORY_POSTGRES_NODE_NAME, { + jsonData: { + ai_memory: { + json: { + action: 'loadMemoryVariables', + values: { + input: inputMessage, + system_message: 'You are a helpful assistant', + formatting_instructions: + 'IMPORTANT: Always call `format_final_response` to format your final response!', + }, + }, + }, + }, + inputOverride: { + ai_memory: [ + [ + { + json: { + action: 'loadMemoryVariables', + values: { + input: inputMessage, + system_message: 'You are a helpful assistant', + formatting_instructions: + 'IMPORTANT: Always call `format_final_response` to format your final response!', + }, + }, + }, + ], + ], + }, + error: { + message: 'Internal error', + timestamp: 1722591723244, + name: 'NodeOperationError', + description: 'Internal error', + context: {}, + cause: { + name: 'error', + severity: 'FATAL', + code: '3D000', + file: 'postinit.c', + line: '885', + routine: 'InitPostgres', + } as unknown as Error, + } as ExecutionError, + }), + createMockNodeExecutionData(AGENT_NODE_NAME, { + executionStatus: 'error', + error: { + level: 'error', + tags: { + packageName: 'workflow', + }, + context: {}, + functionality: 'configuration-node', + name: 'NodeOperationError', + timestamp: 1722591723244, + node: { + parameters: { + notice: '', + sessionIdType: 'fromInput', + tableName: 'n8n_chat_histories', + }, + id: '6b9141da-0135-4e9d-94d1-2d658cbf48b5', + name: 'Postgres Chat Memory', + type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', + typeVersion: 1, + position: [1140, 500], + credentials: { + postgres: { + id: 'RkyZetVpGsSfEAhQ', + name: 'Postgres account', + }, + }, + }, + messages: ['database "chat11" does not exist'], + description: 'Internal error', + message: 'Internal error', + } as unknown as ExecutionError, + metadata: { + subRun: [ + { + node: 'Postgres Chat Memory', + runIndex: 0, + }, + ], + }, + }), + ]; +} + +function setupTestWorkflow(chatTrigger: boolean = false) { + // Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model + if (chatTrigger) { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + } else { + addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + } + + addNodeToCanvas(AGENT_NODE_NAME, true); + + if (!chatTrigger) { + // Remove chat trigger + WorkflowPage.getters + .canvasNodeByName(CHAT_TRIGGER_NODE_DISPLAY_NAME) + .find('[data-test-id="delete-node-button"]') + .click({ force: true }); + + // Set manual trigger to output standard pinned data + openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME); + ndv.actions.editPinnedData(); + ndv.actions.savePinnedData(); + ndv.actions.close(); + } + + // Calculator is added just to make OpenAI Chat Model work (tools can not be empty with OpenAI model) + addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); + clickGetBackToCanvas(); + + addMemoryNodeToParent(AI_MEMORY_POSTGRES_NODE_NAME, AGENT_NODE_NAME); + + clickCreateNewCredential(); + setCredentialValues({ + password: 'testtesttest', + }); + + ndv.getters.parameterInput('sessionIdType').click(); + getVisibleSelect().contains('Define below').click(); + ndv.getters.parameterInput('sessionKey').type('asdasd'); + + clickGetBackToCanvas(); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + + clickCreateNewCredential(); + setCredentialValues({ + apiKey: 'sk_test_123', + }); + clickGetBackToCanvas(); + + WorkflowPage.actions.zoomToFit(); +} + +function checkMessages(inputMessage: string, outputMessage: string) { + const messages = getManualChatMessages(); + messages.should('have.length', 2); + messages.should('contain', inputMessage); + messages.should('contain', outputMessage); + + getManualChatModalLogs().should('exist'); + getManualChatModalLogsEntries() + .should('have.length', 1) + .should('contain', AI_MEMORY_POSTGRES_NODE_NAME); +} + +describe("AI-233 Make root node's logs pane active in case of an error in sub-nodes", () => { + beforeEach(() => { + navigateToNewWorkflowPage(); + }); + + it('should open logs tab by default when there was an error', () => { + setupTestWorkflow(true); + + openNode(AGENT_NODE_NAME); + + const inputMessage = 'Test the code tool'; + + clickExecuteNode(); + runMockWorkflowExecution({ + trigger: () => sendManualChatMessage(inputMessage), + runData: createRunDataWithError(inputMessage), + lastNodeExecuted: AGENT_NODE_NAME, + }); + + checkMessages(inputMessage, '[ERROR: Internal error]'); + closeManualChatModal(); + + // Open the AI Agent node to see the logs + openNode(AGENT_NODE_NAME); + + // Finally check that logs pane is opened by default + ndv.getters.outputDataContainer().should('be.visible'); + + ndv.getters.aiOutputModeToggle().should('be.visible'); + ndv.getters + .aiOutputModeToggle() + .find('[role="radio"]') + .should('have.length', 2) + .eq(1) + .should('have.attr', 'aria-checked', 'true'); + + ndv.getters + .outputPanel() + .findChildByTestId('node-error-message') + .should('be.visible') + .should('contain', 'Error in sub-node'); + }); + + it('should switch to logs tab on error, when NDV is already opened', () => { + setupTestWorkflow(false); + + openNode(AGENT_NODE_NAME); + + const inputMessage = 'Test the code tool'; + + runMockWorkflowExecution({ + trigger: () => clickExecuteNode(), + runData: createRunDataWithError(inputMessage), + lastNodeExecuted: AGENT_NODE_NAME, + }); + + // Check that logs pane is opened by default + ndv.getters.outputDataContainer().should('be.visible'); + + ndv.getters.aiOutputModeToggle().should('be.visible'); + ndv.getters + .aiOutputModeToggle() + .find('[role="radio"]') + .should('have.length', 2) + .eq(1) + .should('have.attr', 'aria-checked', 'true'); + + ndv.getters + .outputPanel() + .findChildByTestId('node-error-message') + .should('be.visible') + .should('contain', 'Error in sub-node'); + }); +}); diff --git a/cypress/e2e/24-ndv-paired-item.cy.ts b/cypress/e2e/24-ndv-paired-item.cy.ts index 5dad8cc17420d..8324144343596 100644 --- a/cypress/e2e/24-ndv-paired-item.cy.ts +++ b/cypress/e2e/24-ndv-paired-item.cy.ts @@ -162,64 +162,6 @@ describe('NDV', () => { ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); }); - it('resolves expression with default item when input node is not parent, while still pairing items', () => { - cy.fixture('Test_workflow_5.json').then((data) => { - cy.get('body').paste(JSON.stringify(data)); - }); - workflowPage.actions.zoomToFit(); - workflowPage.actions.executeWorkflow(); - workflowPage.actions.openNode('Set2'); - - ndv.getters.inputPanel().contains('6 items').should('exist'); - ndv.getters - .outputRunSelector() - .find('input') - .should('exist') - .should('have.value', '2 of 2 (6 items)'); - - ndv.actions.switchInputMode('Table'); - ndv.actions.switchOutputMode('Table'); - - ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1).should('have.text', '1111'); - - ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(1).realHover(); - cy.wait(100); - ndv.getters.outputHoveringItem().should('not.exist'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '1111'); - - ndv.actions.selectInputNode('Code1'); - ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1).should('have.text', '1000'); - - ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1).should('have.text', '1000'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); - - ndv.actions.selectInputNode('Code'); - - ndv.getters.inputTableRow(1).realHover(); - cy.wait(100); - ndv.getters.inputTableRow(1).should('have.text', '6666'); - - ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - - ndv.getters.outputHoveringItem().should('not.exist'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); - - ndv.actions.selectInputNode('When clicking'); - - ndv.getters.inputTableRow(1).realHover(); - ndv.getters - .inputTableRow(1) - .should('have.text', "This is an item, but it's empty.") - .realHover(); - - ndv.getters.outputHoveringItem().should('have.length', 6); - ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); - }); - it('can pair items between input and output across branches and runs', () => { cy.fixture('Test_workflow_5.json').then((data) => { cy.get('body').paste(JSON.stringify(data)); diff --git a/cypress/e2e/25-stickies.cy.ts b/cypress/e2e/25-stickies.cy.ts index 597050c9f2b80..14c176f17b4ef 100644 --- a/cypress/e2e/25-stickies.cy.ts +++ b/cypress/e2e/25-stickies.cy.ts @@ -1,7 +1,5 @@ -import type { Interception } from 'cypress/types/net-stubbing'; import { META_KEY } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; -import { getPopper } from '../utils'; const workflowPage = new WorkflowPageClass(); @@ -82,32 +80,6 @@ describe('Canvas Actions', () => { workflowPage.getters.stickies().should('have.length', 0); }); - it('change sticky color', () => { - workflowPage.actions.addSticky(); - - workflowPage.getters.stickies().should('have.length', 1); - - workflowPage.actions.toggleColorPalette(); - - getPopper().should('be.visible'); - - workflowPage.actions.pickColor(); - - workflowPage.actions.toggleColorPalette(); - - getPopper().should('not.be.visible'); - - workflowPage.actions.saveWorkflowOnButtonClick(); - - cy.wait('@createWorkflow').then((interception: Interception) => { - const { request } = interception; - const color = request.body?.nodes[0]?.parameters?.color; - expect(color).to.equal(2); - }); - - workflowPage.getters.stickies().should('have.length', 1); - }); - it('edits sticky and updates content as markdown', () => { workflowPage.actions.addSticky(); diff --git a/cypress/e2e/26-resource-locator.cy.ts b/cypress/e2e/26-resource-locator.cy.ts index 5c8eef714a263..6e431690adc78 100644 --- a/cypress/e2e/26-resource-locator.cy.ts +++ b/cypress/e2e/26-resource-locator.cy.ts @@ -37,6 +37,16 @@ describe('Resource Locator', () => { ndv.getters.resourceLocatorErrorMessage().should('contain', NO_CREDENTIALS_MESSAGE); }); + it('should show create credentials modal when clicking "add your credential"', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); + ndv.getters.resourceLocator('documentId').should('be.visible'); + ndv.getters.resourceLocatorInput('documentId').click(); + ndv.getters.resourceLocatorErrorMessage().should('contain', NO_CREDENTIALS_MESSAGE); + ndv.getters.resourceLocatorAddCredentials().click(); + credentialsModal.getters.credentialsEditModal().should('be.visible'); + }); + it('should show appropriate error when credentials are not valid', () => { workflowPage.actions.addInitialNodeToCanvas('Manual'); workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); diff --git a/cypress/e2e/30-editor-after-route-changes.cy.ts b/cypress/e2e/30-editor-after-route-changes.cy.ts index 26ba3024ade74..65987806764de 100644 --- a/cypress/e2e/30-editor-after-route-changes.cy.ts +++ b/cypress/e2e/30-editor-after-route-changes.cy.ts @@ -128,11 +128,6 @@ describe('Editor actions should work', () => { createNewWorkflowAndActivate(); }); - it('after saving a new workflow', () => { - editWorkflowAndDeactivate(); - editWorkflowMoreAndActivate(); - }); - it('after switching between Editor and Executions', () => { cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts index c1409a34f379b..c6d0f4ab4d3e7 100644 --- a/cypress/e2e/30-langchain.cy.ts +++ b/cypress/e2e/30-langchain.cy.ts @@ -10,7 +10,9 @@ import { disableNode, getExecuteWorkflowButton, navigateToNewWorkflowPage, + getNodes, openNode, + getConnectionBySourceAndTarget, } from '../composables/workflow'; import { clickCreateNewCredential, @@ -41,6 +43,7 @@ import { AI_TOOL_WIKIPEDIA_NODE_NAME, BASIC_LLM_CHAIN_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME, + CHAT_TRIGGER_NODE_DISPLAY_NAME, } from './../constants'; describe('Langchain Integration', () => { @@ -331,4 +334,27 @@ describe('Langchain Integration', () => { closeManualChatModal(); }); + + it('should auto-add chat trigger and basic LLM chain when adding LLM node', () => { + addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); + + getConnectionBySourceAndTarget( + CHAT_TRIGGER_NODE_DISPLAY_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + ).should('exist'); + + getConnectionBySourceAndTarget( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + ).should('exist'); + getNodes().should('have.length', 3); + }); + + it('should not auto-add nodes if AI nodes are already present', () => { + addNodeToCanvas(AGENT_NODE_NAME, true); + + addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); + getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist'); + getNodes().should('have.length', 3); + }); }); diff --git a/cypress/e2e/31-demo.cy.ts b/cypress/e2e/31-demo.cy.ts index d9397ace4eece..eed3198c83687 100644 --- a/cypress/e2e/31-demo.cy.ts +++ b/cypress/e2e/31-demo.cy.ts @@ -1,23 +1,32 @@ import workflow from '../fixtures/Manual_wait_set.json'; -import { importWorkflow, vistDemoPage } from '../pages/demo'; +import { importWorkflow, visitDemoPage } from '../pages/demo'; import { WorkflowPage } from '../pages/workflow'; +import { errorToast } from '../pages/notifications'; const workflowPage = new WorkflowPage(); describe('Demo', () => { + beforeEach(() => { + cy.overrideSettings({ previewMode: true }); + cy.signout(); + }); + it('can import template', () => { - vistDemoPage(); + visitDemoPage(); + errorToast().should('not.exist'); importWorkflow(workflow); workflowPage.getters.canvasNodes().should('have.length', 3); }); it('can override theme to dark', () => { - vistDemoPage('dark'); + visitDemoPage('dark'); cy.get('body').should('have.attr', 'data-theme', 'dark'); + errorToast().should('not.exist'); }); it('can override theme to light', () => { - vistDemoPage('light'); + visitDemoPage('light'); cy.get('body').should('have.attr', 'data-theme', 'light'); + errorToast().should('not.exist'); }); }); diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts index 94a6384233c2c..e2bf63df7dc46 100644 --- a/cypress/e2e/39-projects.cy.ts +++ b/cypress/e2e/39-projects.cy.ts @@ -1,9 +1,4 @@ -import { - INSTANCE_MEMBERS, - INSTANCE_OWNER, - MANUAL_TRIGGER_NODE_NAME, - NOTION_NODE_NAME, -} from '../constants'; +import { INSTANCE_MEMBERS, MANUAL_TRIGGER_NODE_NAME, NOTION_NODE_NAME } from '../constants'; import { WorkflowsPage, WorkflowPage, @@ -11,9 +6,10 @@ import { CredentialsPage, WorkflowExecutionsTab, NDV, + MainSidebar, } from '../pages'; import * as projects from '../composables/projects'; -import { getVisibleSelect } from '../utils'; +import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils'; const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPage(); @@ -21,6 +17,7 @@ const credentialsPage = new CredentialsPage(); const credentialsModal = new CredentialsModal(); const executionsTab = new WorkflowExecutionsTab(); const ndv = new NDV(); +const mainSidebar = new MainSidebar(); describe('Projects', { disableAutoLogin: true }, () => { before(() => { @@ -237,10 +234,30 @@ describe('Projects', { disableAutoLogin: true }, () => { cy.signinAsMember(1); cy.visit(workflowsPage.url); - projects.getAddProjectButton().should('not.exist'); + cy.getByTestId('add-project-menu-item').should('not.exist'); projects.getMenuItems().should('not.exist'); }); + it('should not show viewer role if not licensed', () => { + cy.signinAsOwner(); + cy.visit(workflowsPage.url); + + projects.getMenuItems().first().click(); + projects.getProjectTabSettings().click(); + + cy.get( + `[data-test-id="user-list-item-${INSTANCE_MEMBERS[0].email}"] [data-test-id="projects-settings-user-role-select"]`, + ).click(); + + cy.get('.el-select-dropdown__item.is-disabled') + .should('contain.text', 'Viewer') + .get('span:contains("Upgrade")') + .filter(':visible') + .click(); + + getVisibleModalOverlay().should('contain.text', 'Upgrade to unlock additional roles'); + }); + describe('when starting from scratch', () => { beforeEach(() => { cy.resetDatabase(); @@ -257,7 +274,7 @@ describe('Projects', { disableAutoLogin: true }, () => { // Create a project and add a credential to it cy.intercept('POST', '/rest/projects').as('projectCreate'); - projects.getAddProjectButton().should('contain', 'Add project').should('be.visible').click(); + projects.getAddProjectButton().click(); cy.wait('@projectCreate'); projects.getMenuItems().should('have.length', 1); projects.getMenuItems().first().click(); @@ -418,7 +435,7 @@ describe('Projects', { disableAutoLogin: true }, () => { }); it('should move resources between projects', () => { - cy.signin(INSTANCE_OWNER); + cy.signinAsOwner(); cy.visit(workflowsPage.url); // Create a workflow and a credential in the Home project @@ -563,5 +580,80 @@ describe('Projects', { disableAutoLogin: true }, () => { projects.getProjectTabCredentials().click(); credentialsPage.getters.credentialCards().should('have.length', 2); }); + + it('should handle viewer role', () => { + cy.enableFeature('projectRole:viewer'); + cy.signinAsOwner(); + cy.visit(workflowsPage.url); + + projects.createProject('Development'); + projects.addProjectMember(INSTANCE_MEMBERS[0].email, 'Viewer'); + projects.getProjectSettingsSaveButton().click(); + + projects.getProjectTabWorkflows().click(); + workflowsPage.getters.newWorkflowButtonCard().click(); + projects.createWorkflow('Test_workflow_4_executions_view.json', 'WF with random error'); + executionsTab.actions.createManualExecutions(2); + executionsTab.actions.toggleNodeEnabled('Error'); + executionsTab.actions.createManualExecutions(2); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + + projects.getMenuItems().first().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.emptyListCreateCredentialButton().click(); + projects.createCredential('Notion API'); + + mainSidebar.actions.openUserMenu(); + cy.getByTestId('user-menu-item-logout').click(); + + cy.get('input[name="email"]').type(INSTANCE_MEMBERS[0].email); + cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password); + cy.getByTestId('form-submit-button').click(); + + mainSidebar.getters.executions().click(); + cy.getByTestId('global-execution-list-item').first().find('td:last button').click(); + getVisibleDropdown() + .find('li') + .filter(':contains("Retry")') + .should('have.class', 'is-disabled'); + getVisibleDropdown() + .find('li') + .filter(':contains("Delete")') + .should('have.class', 'is-disabled'); + + projects.getMenuItems().first().click(); + cy.getByTestId('workflow-card-name').should('be.visible').first().click(); + workflowPage.getters.nodeViewRoot().should('be.visible'); + workflowPage.getters.executeWorkflowButton().should('not.exist'); + workflowPage.getters.nodeCreatorPlusButton().should('not.exist'); + workflowPage.getters.canvasNodes().should('have.length', 3).last().click(); + cy.get('body').type('{backspace}'); + workflowPage.getters.canvasNodes().should('have.length', 3).last().rightclick(); + getVisibleDropdown() + .find('li') + .should('be.visible') + .filter( + ':contains("Open"), :contains("Copy"), :contains("Select all"), :contains("Clear selection")', + ) + .should('not.have.class', 'is-disabled'); + cy.get('body').type('{esc}'); + + executionsTab.actions.switchToExecutionsTab(); + cy.getByTestId('retry-execution-button') + .should('be.visible') + .find('.is-disabled') + .should('exist'); + cy.get('button:contains("Debug")').should('be.disabled'); + cy.get('button[title="Retry execution"]').should('be.disabled'); + cy.get('button[title="Delete this execution"]').should('be.disabled'); + + projects.getMenuItems().first().click(); + projects.getProjectTabCredentials().click(); + credentialsPage.getters.credentialCards().filter(':contains("Notion")').click(); + cy.getByTestId('node-credentials-config-container') + .should('be.visible') + .find('input') + .should('not.have.length'); + }); }); }); diff --git a/cypress/e2e/44-routing.cy.ts b/cypress/e2e/44-routing.cy.ts new file mode 100644 index 0000000000000..67a092235bff2 --- /dev/null +++ b/cypress/e2e/44-routing.cy.ts @@ -0,0 +1,26 @@ +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { EDIT_FIELDS_SET_NODE_NAME } from '../constants'; +import { getSaveChangesModal } from '../composables/modals/save-changes-modal'; + +const WorkflowsPage = new WorkflowsPageClass(); +const WorkflowPage = new WorkflowPageClass(); + +describe('Workflows', () => { + beforeEach(() => { + cy.visit(WorkflowsPage.url); + }); + + it('should ask to save unsaved changes before leaving route', () => { + WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible'); + WorkflowsPage.getters.newWorkflowButtonCard().click(); + + cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow'); + + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + + cy.getByTestId('project-home-menu-item').click(); + + getSaveChangesModal().should('be.visible'); + }); +}); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index 671b8287b3f54..24296ddca851d 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -1,10 +1,8 @@ -import { getVisibleSelect } from '../utils'; import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, NOTION_NODE_NAME } from '../constants'; import { NDV, WorkflowPage } from '../pages'; import { NodeCreator } from '../pages/features/node-creator'; import { clickCreateNewCredential } from '../composables/ndv'; import { setCredentialValues } from '../composables/modals/credential-modal'; -import { successToast } from '../pages/notifications'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -339,38 +337,6 @@ describe('NDV', () => { }); }); - it('should not retrieve remote options when required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); - - ndv.getters.parameterInput('remoteOptions').click(); - getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); - - ndv.actions.setInvalidExpression({ fieldName: 'fieldId' }); - - ndv.getters.inputPanel().click(); // remove focus from input, hide expression preview - - ndv.getters.parameterInput('remoteOptions').click(); - - ndv.getters.parameterInputIssues('remoteOptions').realHover({ scrollBehavior: false }); - // Remote options dropdown should not be visible - ndv.getters.parameterInput('remoteOptions').find('.el-select').should('not.exist'); - }); - - it('should retrieve remote options when non-required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); - - ndv.getters.parameterInput('remoteOptions').click(); - getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); - ndv.getters.parameterInput('remoteOptions').click(); - - ndv.actions.setInvalidExpression({ fieldName: 'otherField' }); - - ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview - - ndv.getters.parameterInput('remoteOptions').click(); - getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); - }); - it('should flag issues as soon as params are set', () => { workflowPage.actions.addInitialNodeToCanvas('Webhook'); workflowPage.getters.canvasNodes().first().dblclick(); @@ -631,8 +597,7 @@ describe('NDV', () => { ndv.getters.outputDisplayMode().find('label').eq(2).click({ force: true }); ndv.getters .outputDataContainer() - .findChildByTestId('run-data-schema-item') - .find('> span') + .findChildByTestId('run-data-schema-item-value') .should('include.text', ''); }); @@ -739,23 +704,6 @@ describe('NDV', () => { }); }); - it('Stop listening for trigger event from NDV', () => { - cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun'); - workflowPage.actions.addInitialNodeToCanvas('Local File Trigger', { - keepNdvOpen: true, - action: 'On Changes To A Specific File', - isTrigger: true, - }); - ndv.getters.triggerPanelExecuteButton().should('exist'); - ndv.getters.triggerPanelExecuteButton().realClick(); - ndv.getters.triggerPanelExecuteButton().should('contain', 'Stop Listening'); - ndv.getters.triggerPanelExecuteButton().realClick(); - cy.wait('@workflowRun').then(() => { - ndv.getters.triggerPanelExecuteButton().should('contain', 'Test step'); - successToast().should('exist'); - }); - }); - it('should allow selecting item for expressions', () => { workflowPage.actions.visit(); @@ -782,4 +730,33 @@ describe('NDV', () => { ndv.actions.expressionSelectItem(1); ndv.getters.inlineExpressionEditorOutput().should('have.text', '1'); }); + + it('should show data from the correct output in schema view', () => { + cy.createFixtureWorkflow('Test_workflow_multiple_outputs.json'); + workflowPage.actions.zoomToFit(); + + workflowPage.actions.executeWorkflow(); + workflowPage.actions.openNode('Only Item 1'); + ndv.getters.inputPanel().should('be.visible'); + ndv.getters + .inputPanel() + .find('[data-test-id=run-data-schema-item]') + .should('contain.text', 'onlyOnItem1'); + ndv.actions.close(); + + workflowPage.actions.openNode('Only Item 2'); + ndv.getters.inputPanel().should('be.visible'); + ndv.getters + .inputPanel() + .find('[data-test-id=run-data-schema-item]') + .should('contain.text', 'onlyOnItem2'); + ndv.actions.close(); + + workflowPage.actions.openNode('Only Item 3'); + ndv.getters.inputPanel().should('be.visible'); + ndv.getters + .inputPanel() + .find('[data-test-id=run-data-schema-item]') + .should('contain.text', 'onlyOnItem3'); + }); }); diff --git a/cypress/fixtures/Test_workflow_multiple_outputs.json b/cypress/fixtures/Test_workflow_multiple_outputs.json new file mode 100644 index 0000000000000..b80ff5dd3346b --- /dev/null +++ b/cypress/fixtures/Test_workflow_multiple_outputs.json @@ -0,0 +1,223 @@ +{ + "name": "Multiple outputs", + "nodes": [ + { + "parameters": {}, + "id": "64b27674-3da6-46ce-9008-e173182efa48", + "name": "When clicking ‘Test workflow’", + "type": "n8n-nodes-base.manualTrigger", + "position": [ + 16, + -32 + ], + "typeVersion": 1 + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "leftValue": "={{ $json.code }}", + "rightValue": 1, + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Item1" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "a659050f-0867-471d-8914-d499b6ad7b31", + "leftValue": "={{ $json.code }}", + "rightValue": 2, + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Item2" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "109fc001-53af-48f1-b79c-5e9afc8b94bd", + "leftValue": "={{ $json.code }}", + "rightValue": 3, + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Item3" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "position": [ + 192, + -32 + ], + "id": "3863cc7a-8f45-46fc-a60c-36aad5b12877", + "name": "Switch", + "typeVersion": 3 + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "f71bac89-8852-41b2-98dd-cb689f011dcb", + "name": "", + "value": "", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "position": [ + 480, + -192 + ], + "id": "85940094-4656-4cdf-a871-1b3b46421de3", + "name": "Only Item 1", + "typeVersion": 3.4 + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.set", + "position": [ + 480, + -32 + ], + "id": "a7f4e2b5-8cc9-4881-aa06-38601988740e", + "name": "Only Item 2", + "typeVersion": 3.4 + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.set", + "position": [ + 480, + 128 + ], + "id": "7e44ad56-415a-4991-a70e-fea86c430031", + "name": "Only Item 3", + "typeVersion": 3.4 + } + ], + "pinData": { + "When clicking ‘Test workflow’": [ + { + "json": { + "name": "First item", + "onlyOnItem1": true, + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "onlyOnItem2": true, + "code": 2 + } + }, + { + "json": { + "name": "Third item", + "onlyOnItem3": true, + "code": 3 + } + } + ] + }, + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "Only Item 1", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Only Item 2", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Only Item 3", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "1e2a7b45-7730-42d6-989e-f3fa80de303e", + "meta": { + "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" + }, + "id": "V2ld4YU11fsHgr1z", + "tags": [] +} diff --git a/cypress/package.json b/cypress/package.json index 7740b5483f01b..0e1ce734c02cf 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -14,7 +14,7 @@ "start": "cd ..; pnpm start" }, "devDependencies": { - "@types/lodash": "^4.14.195", + "@types/lodash": "catalog:", "eslint-plugin-cypress": "^3.3.0", "n8n-workflow": "workspace:*" }, @@ -24,8 +24,8 @@ "cypress": "^13.11.0", "cypress-otp": "^1.0.3", "cypress-real-events": "^1.12.0", - "lodash": "4.17.21", - "nanoid": "3.3.6", + "lodash": "catalog:", + "nanoid": "catalog:", "start-server-and-test": "^2.0.3" } } diff --git a/cypress/pages/demo.ts b/cypress/pages/demo.ts index 691066ce05c9d..7e67b79254bf6 100644 --- a/cypress/pages/demo.ts +++ b/cypress/pages/demo.ts @@ -2,7 +2,7 @@ * Actions */ -export function vistDemoPage(theme?: 'dark' | 'light') { +export function visitDemoPage(theme?: 'dark' | 'light') { const query = theme ? `?theme=${theme}` : ''; cy.visit('/workflows/demo' + query); cy.waitForLoad(); diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 99f44d1a8b98c..1a80d79707c6d 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -24,6 +24,7 @@ export class NDV extends BasePage { editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'), pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller .cm-content'), runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), + aiOutputModeToggle: () => cy.getByTestId('ai-output-mode-select'), nodeOutputHint: () => cy.getByTestId('ndv-output-run-node-hint'), savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'), @@ -77,6 +78,7 @@ export class NDV extends BasePage { resourceLocatorDropdown: (paramName: string) => this.getters.resourceLocator(paramName).find('[data-test-id="resource-locator-dropdown"]'), resourceLocatorErrorMessage: () => cy.getByTestId('rlc-error-container'), + resourceLocatorAddCredentials: () => this.getters.resourceLocatorErrorMessage().find('a'), resourceLocatorModeSelector: (paramName: string) => this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'), resourceLocatorSearch: (paramName: string) => @@ -203,9 +205,9 @@ export class NDV extends BasePage { const droppable = `[data-test-id="parameter-input-${parameterName}"]`; cy.draganddrop(draggable, droppable); }, - mapToParameter: (parameterName: string) => { + mapToParameter: (parameterName: string, position?: 'top' | 'center' | 'bottom') => { const droppable = `[data-test-id="parameter-input-${parameterName}"]`; - cy.draganddrop('', droppable); + cy.draganddrop('', droppable, { position }); }, switchInputMode: (type: 'Schema' | 'Table' | 'JSON' | 'Binary') => { this.getters.inputDisplayMode().find('label').contains(type).click({ force: true }); diff --git a/cypress/pages/settings-community-nodes.ts b/cypress/pages/settings-community-nodes.ts new file mode 100644 index 0000000000000..454dc95e2122f --- /dev/null +++ b/cypress/pages/settings-community-nodes.ts @@ -0,0 +1,22 @@ +export const getCommunityCards = () => { + return cy.getByTestId('community-package-card'); +}; + +export const visitCommunityNodesSettings = () => { + cy.visit('/settings/community-nodes'); +}; + +export const installFirstCommunityNode = (nodeName: string) => { + cy.getByTestId('action-box').find('button').click(); + cy.getByTestId('communityPackageInstall-modal').find('input').eq(0).type(nodeName); + cy.getByTestId('user-agreement-checkbox').click(); + cy.getByTestId('install-community-package-button').click(); +}; + +export const confirmCommunityNodeUpdate = () => { + cy.getByTestId('communityPackageManageConfirm-modal').find('button').eq(1).click(); +}; + +export const confirmCommunityNodeUninstall = () => { + cy.getByTestId('communityPackageManageConfirm-modal').find('button').eq(1).click(); +}; diff --git a/cypress/scripts/run-e2e.js b/cypress/scripts/run-e2e.js index 05ddae1a9fc92..8096a70caf4f4 100755 --- a/cypress/scripts/run-e2e.js +++ b/cypress/scripts/run-e2e.js @@ -13,7 +13,6 @@ function runTests(options) { process.env.N8N_USER_FOLDER = userFolder; process.env.E2E_TESTS = 'true'; process.env.NODE_OPTIONS = '--dns-result-order=ipv4first'; - process.env.VUE_APP_MAX_PINNED_DATA_SIZE = `${16 * 1024}`; if (options.customEnv) { Object.keys(options.customEnv).forEach((key) => { diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index a7fa994289029..1f353ab7c51ce 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -175,7 +175,7 @@ Cypress.Commands.add('drag', (selector, pos, options) => { }); }); -Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => { +Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector, options) => { if (draggableSelector) { cy.get(draggableSelector).should('exist'); } @@ -197,7 +197,7 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => { cy.get(droppableSelector).realMouseMove(0, 0); cy.get(droppableSelector).realMouseMove(pageX, pageY); cy.get(droppableSelector).realHover(); - cy.get(droppableSelector).realMouseUp(); + cy.get(droppableSelector).realMouseUp({ position: options?.position ?? 'top' }); if (draggableSelector) { cy.get(draggableSelector).realMouseUp(); } diff --git a/cypress/support/index.ts b/cypress/support/index.ts index cb0f42bdce744..7c1897b11f27e 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -12,6 +12,10 @@ interface SigninPayload { password: string; } +interface DragAndDropOptions { + position: 'top' | 'center' | 'bottom'; +} + declare global { namespace Cypress { interface SuiteConfigOverrides { @@ -56,7 +60,11 @@ declare global { target: [number, number], options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean }, ): void; - draganddrop(draggableSelector: string, droppableSelector: string): void; + draganddrop( + draggableSelector: string, + droppableSelector: string, + options?: Partial, + ): void; push(type: string, data: unknown): void; shouldNotHaveConsoleErrors(): void; window(): Chainable< @@ -64,6 +72,7 @@ declare global { innerWidth: number; innerHeight: number; preventNodeViewBeforeUnload?: boolean; + maxPinnedDataSize?: number; featureFlags: { override: (feature: string, value: unknown) => void; }; diff --git a/docker/images/n8n-custom/Dockerfile b/docker/images/n8n-custom/Dockerfile index 9103742cd4f67..17f0d1c5172e2 100644 --- a/docker/images/n8n-custom/Dockerfile +++ b/docker/images/n8n-custom/Dockerfile @@ -1,7 +1,7 @@ ARG NODE_VERSION=20 # 1. Create an image to build n8n -FROM --platform=linux/amd64 n8nio/base:${NODE_VERSION} as builder +FROM --platform=linux/amd64 n8nio/base:${NODE_VERSION} AS builder # Build the application from source WORKDIR /src @@ -11,7 +11,7 @@ RUN pnpm build # Delete all dev dependencies RUN jq 'del(.pnpm.patchedDependencies)' package.json > package.json.tmp; mv package.json.tmp package.json -RUN node scripts/trim-fe-packageJson.js +RUN node .github/scripts/trim-fe-packageJson.js # Delete any source code, source-mapping, or typings RUN find . -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" -o -name "tsconfig.json" -o -name "*.tsbuildinfo" | xargs rm -rf diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index d65459615086e..f7b45f9467502 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -230,6 +230,4 @@ Before you upgrade to the latest version make sure to check here if there are an ## License -n8n is [fair-code](https://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). - -Additional information about the license can be found in the [docs](https://docs.n8n.io/reference/license/). +You can find the license information [here](https://github.com/n8n-io/n8n/blob/master/README.md#license) diff --git a/package.json b/package.json index 563cf6227b3e0..e7d55c64455ad 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,37 @@ { "name": "n8n-monorepo", - "version": "1.48.0", + "version": "1.55.0", "private": true, - "homepage": "https://n8n.io", "engines": { - "node": ">=18.10", - "pnpm": ">=9.1" + "node": ">=20.15", + "pnpm": ">=9.5" }, - "packageManager": "pnpm@9.1.4", + "packageManager": "pnpm@9.6.0", "scripts": { "preinstall": "node scripts/block-npm-install.js", "build": "turbo run build", - "build:backend": "pnpm --filter=!@n8n/chat --filter=!@n8n/codemirror-lang --filter=!n8n-design-system --filter=!n8n-editor-ui build", - "build:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui build", - "typecheck": "pnpm --filter=!@n8n/storybook --filter=!n8n-core --filter=!n8n-workflow --filter=!n8n typecheck", - "dev": "turbo run dev --parallel --filter=!n8n-design-system --filter=!@n8n/chat", - "dev:ai": "turbo run dev --parallel --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core", + "build:backend": "turbo run build:backend", + "build:frontend": "turbo run build:frontend", + "build:nodes": "turbo run build:nodes", + "typecheck": "turbo typecheck", + "dev": "turbo run dev --parallel --env-mode=loose --filter=!n8n-design-system --filter=!@n8n/chat", + "dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core", "clean": "turbo run clean --parallel", "format": "turbo run format && node scripts/format.mjs", "lint": "turbo run lint", "lintfix": "turbo run lintfix", - "lint:backend": "pnpm --filter=!@n8n/chat --filter=!@n8n/codemirror-lang --filter=!n8n-design-system --filter=!n8n-editor-ui --filter=!n8n-nodes-base --filter=!@n8n/n8n-nodes-langchain lint", - "lint:nodes": "pnpm --filter=n8n-nodes-base --filter=@n8n/n8n-nodes-langchain lint", - "lint:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui lint", + "lint:backend": "turbo run lint:backend", + "lint:nodes": "turbo run lint:nodes", + "lint:frontend": "turbo run lint:frontend", "optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo", "start": "run-script-os", "start:default": "cd packages/cli/bin && ./n8n", "start:tunnel": "./packages/cli/bin/n8n start --tunnel", "start:windows": "cd packages/cli/bin && n8n", "test": "turbo run test", - "test:backend": "pnpm --filter=!@n8n/chat --filter=!@n8n/codemirror-lang --filter=!n8n-design-system --filter=!n8n-editor-ui --filter=!n8n-nodes-base --filter=!@n8n/n8n-nodes-langchain test", - "test:nodes": "pnpm --filter=n8n-nodes-base --filter=@n8n/n8n-nodes-langchain test", - "test:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui test", + "test:backend": "turbo run test:backend --concurrency=1", + "test:frontend": "turbo run test:frontend --concurrency=1", + "test:nodes": "turbo run test:nodes --concurrency=1", "watch": "turbo run watch --parallel", "webhook": "./packages/cli/bin/n8n webhook", "worker": "./packages/cli/bin/n8n worker" @@ -40,7 +40,6 @@ "@n8n_io/eslint-config": "workspace:*", "@types/jest": "^29.5.3", "@types/supertest": "^6.0.2", - "@vitest/coverage-v8": "^1.6.0", "jest": "^29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-expect-message": "^1.1.3", @@ -55,12 +54,8 @@ "ts-jest": "^29.1.1", "tsc-alias": "^1.8.7", "tsc-watch": "^6.0.4", - "turbo": "1.13.3", - "typescript": "*", - "vite": "^5.2.12", - "vitest": "^1.6.0", - "vitest-mock-extended": "^1.3.1", - "vue-tsc": "^2.0.19" + "turbo": "2.0.6", + "typescript": "*" }, "pnpm": { "onlyBuiltDependencies": [ @@ -68,7 +63,7 @@ ], "overrides": { "@types/node": "^18.16.16", - "axios": "1.6.7", + "axios": "1.7.3", "chokidar": "3.5.2", "esbuild": "^0.20.2", "formidable": "3.5.1", diff --git a/packages/@n8n/chat/LICENSE.md b/packages/@n8n/chat/LICENSE.md deleted file mode 100644 index aab68b6d9301b..0000000000000 --- a/packages/@n8n/chat/LICENSE.md +++ /dev/null @@ -1,86 +0,0 @@ -# License - -Portions of this software are licensed as follows: - -- Content of branches other than the main branch (i.e. "master") are not licensed. -- Source code files that contain ".ee." in their filename are NOT licensed under the Sustainable Use License. - To use source code files that contain ".ee." in their filename you must hold a valid n8n Enterprise License - specifically allowing you access to such source code files and as defined in "LICENSE_EE.md". -- All third party components incorporated into the n8n Software are licensed under the original license - provided by the owner of the applicable component. -- Content outside of the above mentioned files or restrictions is available under the "Sustainable Use - License" as defined below. - -## Sustainable Use License - -Version 1.0 - -### Acceptance - -By using the software, you agree to all of the terms and conditions below. - -### Copyright License - -The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license -to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject -to the limitations below. - -### Limitations - -You may use or modify the software only for your own internal business purposes or for non-commercial or -personal use. You may distribute the software or provide it to others only if you do so free of charge for -non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of -the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. - -### Patents - -The licensor grants you a license, under any patent claims the licensor can license, or becomes able to -license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case -subject to the limitations and conditions in this license. This license does not cover any patent claims that -you cause to be infringed by modifications or additions to the software. If you or your company make any -written claim that the software infringes or contributes to infringement of any patent, your patent license -for the software granted under these terms ends immediately. If your company makes such a claim, your patent -license ends immediately for work on behalf of your company. - -### Notices - -You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these -terms. If you modify the software, you must include in any modified copies of the software a prominent notice -stating that you have modified the software. - -### No Other Rights - -These terms do not imply any licenses other than those expressly granted in these terms. - -### Termination - -If you use the software in violation of these terms, such use is not licensed, and your license will -automatically terminate. If the licensor provides you with a notice of your violation, and you cease all -violation of this license no later than 30 days after you receive that notice, your license will be reinstated -retroactively. However, if you violate these terms after such reinstatement, any additional violation of these -terms will cause your license to terminate automatically and permanently. - -### No Liability - -As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will -not be liable to you for any damages arising out of these terms or the use or nature of the software, under -any kind of legal claim. - -### Definitions - -The “licensor” is the entity offering these terms. - -The “software” is the software the licensor makes available under these terms, including any portion of it. - -“You” refers to the individual or entity agreeing to these terms. - -“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus -all organizations that have control over, are under the control of, or are under common control with that -organization. Control means ownership of substantially all the assets of an entity, or the power to direct its -management and policies by vote, contract, or otherwise. Control can be direct or indirect. - -“Your license” is the license granted to you for the software under these terms. - -“Use” means anything you do with the software requiring your license. - -“Trademark” means trademarks, service marks, and similar rights. diff --git a/packages/@n8n/chat/README.md b/packages/@n8n/chat/README.md index 8055e9677761a..0ed53a5774082 100644 --- a/packages/@n8n/chat/README.md +++ b/packages/@n8n/chat/README.md @@ -31,9 +31,9 @@ Open the **Webhook** node and replace `YOUR_PRODUCTION_WEBHOOK_URL` with your pr Add the following code to your HTML page. ```html - + + + + + diff --git a/packages/@n8n/chat/src/components/Input.vue b/packages/@n8n/chat/src/components/Input.vue index d393ab90ed6f8..af4b3343b7a38 100644 --- a/packages/@n8n/chat/src/components/Input.vue +++ b/packages/@n8n/chat/src/components/Input.vue @@ -1,31 +1,102 @@