mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'master' of github.com:n8n-io/n8n into update-seatable-node
This commit is contained in:
commit
3ffd3289c1
|
@ -11,3 +11,8 @@
|
|||
# refactor: Run lintfix (no-changelog) (#7537)
|
||||
|
||||
62c096710fab2f7e886518abdbded34b55e93f62
|
||||
|
||||
# refactor: Move test files alongside tested files (#11504)
|
||||
|
||||
7e58fc4fec468aca0b45d5bfe6150e1af632acbc
|
||||
f32b13c6ed078be042a735bc8621f27e00dc3116
|
||||
|
|
20
.github/docker-compose.yml
vendored
20
.github/docker-compose.yml
vendored
|
@ -1,18 +1,14 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
mariadb:
|
||||
image: mariadb:10.9
|
||||
environment:
|
||||
- MYSQL_DATABASE=n8n
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
- MARIADB_DATABASE=n8n
|
||||
- MARIADB_ROOT_PASSWORD=password
|
||||
- MARIADB_MYSQL_LOCALHOST_USER=true
|
||||
ports:
|
||||
- 3306:3306
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 26677
|
||||
hard: 46677
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
|
@ -23,3 +19,5 @@ services:
|
|||
- POSTGRES_PASSWORD=password
|
||||
ports:
|
||||
- 5432:5432
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data
|
||||
|
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -11,6 +11,8 @@ Photos and videos are recommended.
|
|||
Include links to **Linear ticket** or Github issue or Community forum post.
|
||||
Important in order to close *automatically* and provide context to reviewers.
|
||||
-->
|
||||
<!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->
|
||||
|
||||
|
||||
## Review / Merge checklist
|
||||
|
||||
|
|
103
.github/scripts/check-tests.mjs
vendored
103
.github/scripts/check-tests.mjs
vendored
|
@ -1,103 +0,0 @@
|
|||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import util from 'util';
|
||||
import { exec } from 'child_process';
|
||||
import { glob } from 'glob';
|
||||
import ts from 'typescript';
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const filterAsync = async (asyncPredicate, arr) => {
|
||||
const filterResults = await Promise.all(
|
||||
arr.map(async (item) => ({
|
||||
item,
|
||||
shouldKeep: await asyncPredicate(item),
|
||||
})),
|
||||
);
|
||||
|
||||
return filterResults.filter(({ shouldKeep }) => shouldKeep).map(({ item }) => item);
|
||||
};
|
||||
|
||||
const isAbstractClass = (node) => {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
return (
|
||||
node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword) || false
|
||||
);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isAbstractMethod = (node) => {
|
||||
return (
|
||||
ts.isMethodDeclaration(node) &&
|
||||
Boolean(node.modifiers?.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword))
|
||||
);
|
||||
};
|
||||
|
||||
// Function to check if a file has a function declaration, function expression, object method or class
|
||||
const hasFunctionOrClass = async (filePath) => {
|
||||
const fileContent = await readFile(filePath, 'utf-8');
|
||||
const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
|
||||
|
||||
let hasFunctionOrClass = false;
|
||||
const visit = (node) => {
|
||||
if (
|
||||
ts.isFunctionDeclaration(node) ||
|
||||
ts.isFunctionExpression(node) ||
|
||||
ts.isArrowFunction(node) ||
|
||||
(ts.isMethodDeclaration(node) && !isAbstractMethod(node)) ||
|
||||
(ts.isClassDeclaration(node) && !isAbstractClass(node))
|
||||
) {
|
||||
hasFunctionOrClass = true;
|
||||
}
|
||||
node.forEachChild(visit);
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
|
||||
return hasFunctionOrClass;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
// Run a git command to get a list of all changed files in the branch (branch has to be up to date with master)
|
||||
const changedFiles = await execAsync(
|
||||
'git diff --name-only --diff-filter=d origin/master..HEAD',
|
||||
).then(({ stdout }) => stdout.trim().split('\n').filter(Boolean));
|
||||
|
||||
// Get all .spec.ts and .test.ts files from the packages
|
||||
const specAndTestTsFiles = await glob('packages/*/**/{test,__tests__}/**/*.{spec,test}.ts');
|
||||
const specAndTestTsFilesNames = specAndTestTsFiles.map((file) =>
|
||||
path.parse(file).name.replace(/\.(test|spec)/, ''),
|
||||
);
|
||||
|
||||
// Filter out the .ts and .vue files from the changed files
|
||||
const changedVueFiles = changedFiles.filter((file) => file.endsWith('.vue'));
|
||||
// .ts files with any kind of function declaration or class and not in any of the test folders
|
||||
const changedTsFilesWithFunction = await filterAsync(
|
||||
async (filePath) =>
|
||||
filePath.endsWith('.ts') &&
|
||||
!(await glob('packages/*/**/{test,__tests__}/*.ts')).includes(filePath) &&
|
||||
(await hasFunctionOrClass(filePath)),
|
||||
changedFiles,
|
||||
);
|
||||
|
||||
// For each .ts or .vue file, check if there's a corresponding .test.ts or .spec.ts file in the repository
|
||||
const missingTests = changedVueFiles
|
||||
.concat(changedTsFilesWithFunction)
|
||||
.reduce((filesList, nextFile) => {
|
||||
const fileName = path.parse(nextFile).name;
|
||||
|
||||
if (!specAndTestTsFilesNames.includes(fileName)) {
|
||||
filesList.push(nextFile);
|
||||
}
|
||||
|
||||
return filesList;
|
||||
}, []);
|
||||
|
||||
if (missingTests.length) {
|
||||
console.error(`Missing tests for:\n${missingTests.join('\n')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
3
.github/scripts/package.json
vendored
3
.github/scripts/package.json
vendored
|
@ -7,7 +7,6 @@
|
|||
"p-limit": "3.1.0",
|
||||
"picocolors": "1.0.1",
|
||||
"semver": "7.5.4",
|
||||
"tempfile": "5.0.0",
|
||||
"typescript": "*"
|
||||
"tempfile": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
30
.github/workflows/check-tests.yml
vendored
30
.github/workflows/check-tests.yml
vendored
|
@ -1,30 +0,0 @@
|
|||
name: Check Test Files
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
- '!release/*'
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check-tests:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- run: npm install --prefix=.github/scripts --no-package-lock
|
||||
|
||||
- name: Check for test files
|
||||
run: node .github/scripts/check-tests.mjs
|
7
.github/workflows/chromatic.yml
vendored
7
.github/workflows/chromatic.yml
vendored
|
@ -1,6 +1,8 @@
|
|||
name: Chromatic
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
@ -65,11 +67,12 @@ jobs:
|
|||
continue-on-error: true
|
||||
with:
|
||||
workingDir: packages/design-system
|
||||
onlyChanged: true
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
exitZeroOnChanges: false
|
||||
|
||||
- name: Success comment
|
||||
if: steps.chromatic_tests.outcome == 'success'
|
||||
if: steps.chromatic_tests.outcome == 'success' && github.ref != 'refs/heads/master'
|
||||
uses: peter-evans/create-or-update-comment@v4.0.0
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -79,7 +82,7 @@ jobs:
|
|||
:white_check_mark: No visual regressions found.
|
||||
|
||||
- name: Fail comment
|
||||
if: steps.chromatic_tests.outcome != 'success'
|
||||
if: steps.chromatic_tests.outcome != 'success' && github.ref != 'refs/heads/master'
|
||||
uses: peter-evans/create-or-update-comment@v4.0.0
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
|
1
.github/workflows/ci-master.yml
vendored
1
.github/workflows/ci-master.yml
vendored
|
@ -47,6 +47,7 @@ jobs:
|
|||
nodeVersion: ${{ matrix.node-version }}
|
||||
cacheKey: ${{ github.sha }}-base:build
|
||||
collectCoverage: ${{ matrix.node-version == '20.x' }}
|
||||
ignoreTurboCache: ${{ matrix.node-version == '20.x' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
|
17
.github/workflows/ci-postgres-mysql.yml
vendored
17
.github/workflows/ci-postgres-mysql.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
|||
paths:
|
||||
- packages/cli/src/databases/**
|
||||
- .github/workflows/ci-postgres-mysql.yml
|
||||
- .github/docker-compose.yml
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
|
@ -71,8 +72,8 @@ jobs:
|
|||
working-directory: packages/cli
|
||||
run: pnpm jest
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
mariadb:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
|
@ -96,16 +97,16 @@ jobs:
|
|||
path: ./packages/**/dist
|
||||
key: ${{ github.sha }}:db-tests
|
||||
|
||||
- name: Start MySQL
|
||||
- name: Start MariaDB
|
||||
uses: isbang/compose-action@v2.0.0
|
||||
with:
|
||||
compose-file: ./.github/docker-compose.yml
|
||||
services: |
|
||||
mysql
|
||||
mariadb
|
||||
|
||||
- name: Test MySQL
|
||||
- name: Test MariaDB
|
||||
working-directory: packages/cli
|
||||
run: pnpm test:mysql --testTimeout 20000
|
||||
run: pnpm test:mariadb --testTimeout 30000
|
||||
|
||||
postgres:
|
||||
name: Postgres
|
||||
|
@ -147,7 +148,7 @@ jobs:
|
|||
notify-on-failure:
|
||||
name: Notify Slack on failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [mysql, postgres]
|
||||
needs: [mariadb, postgres]
|
||||
steps:
|
||||
- name: Notify Slack on failure
|
||||
uses: act10ns/slack@v2.0.0
|
||||
|
@ -156,4 +157,4 @@ jobs:
|
|||
status: ${{ job.status }}
|
||||
channel: '#alerts-build'
|
||||
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
message: Postgres or MySQL tests failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
message: Postgres or MariaDB tests failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
|
3
.github/workflows/ci-pull-requests.yml
vendored
3
.github/workflows/ci-pull-requests.yml
vendored
|
@ -49,6 +49,9 @@ jobs:
|
|||
with:
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
cacheKey: ${{ github.sha }}-base:build
|
||||
collectCoverage: true
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
|
|
12
.github/workflows/docker-base-image.yml
vendored
12
.github/workflows/docker-base-image.yml
vendored
|
@ -20,26 +20,28 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n-base/Dockerfile
|
||||
|
|
10
.github/workflows/docker-images-benchmark.yml
vendored
10
.github/workflows/docker-images-benchmark.yml
vendored
|
@ -19,20 +19,22 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/@n8n/benchmark/Dockerfile
|
||||
|
|
83
.github/workflows/docker-images-custom.yml
vendored
Normal file
83
.github/workflows/docker-images-custom.yml
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
name: Docker Custom Image CI
|
||||
run-name: Build ${{ inputs.branch }} - ${{ inputs.user }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch to create image off.'
|
||||
required: true
|
||||
tag:
|
||||
description: 'Name of the docker tag to create.'
|
||||
required: true
|
||||
merge-master:
|
||||
description: 'Merge with master.'
|
||||
type: boolean
|
||||
required: true
|
||||
default: false
|
||||
user:
|
||||
description: ''
|
||||
required: false
|
||||
default: 'none'
|
||||
start-url:
|
||||
description: 'URL to call after workflow is kicked off.'
|
||||
required: false
|
||||
default: ''
|
||||
success-url:
|
||||
description: 'URL to call after Docker Image got built successfully.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Call Start URL - optionally
|
||||
if: ${{ github.event.inputs.start-url != '' }}
|
||||
run: curl -v -X POST -d 'url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' ${{github.event.inputs.start-url}} || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: Merge Master - optionally
|
||||
if: github.event.inputs.merge-master
|
||||
run: git remote add upstream https://github.com/n8n-io/n8n.git -f; git merge upstream/master --allow-unrelated-histories || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image to GHCR
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n-custom/Dockerfile
|
||||
build-args: |
|
||||
N8N_RELEASE_TYPE=development
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ghcr.io/${{ github.repository_owner }}/n8n:${{ inputs.tag }}
|
||||
|
||||
- name: Call Success URL - optionally
|
||||
if: ${{ github.event.inputs.success-url != '' }}
|
||||
run: curl -v ${{github.event.inputs.success-url}} || echo ""
|
||||
shell: bash
|
86
.github/workflows/docker-images-nightly.yml
vendored
86
.github/workflows/docker-images-nightly.yml
vendored
|
@ -1,74 +1,42 @@
|
|||
name: Docker Nightly Image CI
|
||||
run-name: Build ${{ inputs.branch }} - ${{ inputs.user }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch to create image off.'
|
||||
required: true
|
||||
default: 'master'
|
||||
tag:
|
||||
description: 'Name of the docker tag to create.'
|
||||
required: true
|
||||
default: 'nightly'
|
||||
merge-master:
|
||||
description: 'Merge with master.'
|
||||
type: boolean
|
||||
required: true
|
||||
default: false
|
||||
user:
|
||||
description: ''
|
||||
required: false
|
||||
default: 'schedule'
|
||||
start-url:
|
||||
description: 'URL to call after workflow is kicked off.'
|
||||
required: false
|
||||
default: ''
|
||||
success-url:
|
||||
description: 'URL to call after Docker Image got built successfully.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
N8N_TAG: ${{ inputs.tag || 'nightly' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Call Start URL - optionally
|
||||
run: |
|
||||
[[ "${{github.event.inputs.start-url}}" != "" ]] && curl -v -X POST -d 'url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' ${{github.event.inputs.start-url}} || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch || 'master' }}
|
||||
ref: master
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Merge Master - optionally
|
||||
run: |
|
||||
[[ "${{github.event.inputs.merge-master}}" == "true" ]] && git remote add upstream https://github.com/n8n-io/n8n.git -f; git merge upstream/master --allow-unrelated-histories || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Build and push to DockerHub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
- name: Build and push image to GHCR and DockerHub
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n-custom/Dockerfile
|
||||
|
@ -79,24 +47,6 @@ jobs:
|
|||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/n8n:${{ env.N8N_TAG }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: env.N8N_TAG == 'nightly'
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push image to GHCR
|
||||
if: env.N8N_TAG == 'nightly'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag ghcr.io/${{ github.repository_owner }}/n8n:nightly \
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:nightly
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:nightly
|
||||
|
||||
- name: Call Success URL - optionally
|
||||
run: |
|
||||
[[ "${{github.event.inputs.success-url}}" != "" ]] && curl -v ${{github.event.inputs.success-url}} || echo ""
|
||||
shell: bash
|
||||
|
|
52
.github/workflows/release-publish.yml
vendored
52
.github/workflows/release-publish.yml
vendored
|
@ -38,6 +38,12 @@ jobs:
|
|||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache/save@v4.0.0
|
||||
with:
|
||||
path: ./packages/**/dist
|
||||
key: ${{ github.sha }}-release:build
|
||||
|
||||
- name: Dry-run publishing
|
||||
run: pnpm publish -r --no-git-checks --dry-run
|
||||
|
||||
|
@ -67,26 +73,28 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: ./docker/images/n8n
|
||||
build-args: |
|
||||
|
@ -119,6 +127,40 @@ jobs:
|
|||
makeLatest: false
|
||||
body: ${{github.event.pull_request.body}}
|
||||
|
||||
create-sentry-release:
|
||||
name: Create a Sentry Release
|
||||
needs: [publish-to-npm, publish-to-docker-hub]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Restore cached build artifacts
|
||||
uses: actions/cache/restore@v4.0.0
|
||||
with:
|
||||
path: ./packages/**/dist
|
||||
key: ${{ github.sha }}-release:build
|
||||
|
||||
- name: Create a frontend release
|
||||
uses: getsentry/action-release@v1.7.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
projects: ${{ secrets.SENTRY_FRONTEND_PROJECT }}
|
||||
version: ${{ needs.publish-to-npm.outputs.release }}
|
||||
sourcemaps: packages/editor-ui/dist
|
||||
|
||||
- name: Create a backend release
|
||||
uses: getsentry/action-release@v1.7.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
projects: ${{ secrets.SENTRY_BACKEND_PROJECT }}
|
||||
version: ${{ needs.publish-to-npm.outputs.release }}
|
||||
sourcemaps: packages/cli/dist packages/core/dist packages/nodes-base/dist packages/@n8n/n8n-nodes-langchain/dist
|
||||
|
||||
trigger-release-note:
|
||||
name: Trigger a release note
|
||||
needs: [publish-to-npm, create-github-release]
|
||||
|
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: docker/login-action@v3.0.0
|
||||
- uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
@ -46,7 +46,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: docker/login-action@v3.0.0
|
||||
- uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
125
.github/workflows/test-workflows.yml
vendored
125
.github/workflows/test-workflows.yml
vendored
|
@ -4,18 +4,72 @@ on:
|
|||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/core/package.json
|
||||
- packages/nodes-base/package.json
|
||||
- packages/@n8n/nodes-langchain/package.json
|
||||
- .github/workflows/test-workflows.yml
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
run-test-workflows:
|
||||
build:
|
||||
name: Install & Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
timeout-minutes: 30
|
||||
|
||||
if: |
|
||||
(github.event_name != 'pull_request_review' || startsWith(github.event.pull_request.base.ref, 'release/')) &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'community')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
path: n8n
|
||||
node-version: 20.x
|
||||
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
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache/save@v4.0.0
|
||||
with:
|
||||
path: ./packages/**/dist
|
||||
key: ${{ github.sha }}:workflow-tests
|
||||
|
||||
run-test-workflows:
|
||||
name: Workflow Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 20.x
|
||||
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:
|
||||
path: ./packages/**/dist
|
||||
key: ${{ github.sha }}:workflow-tests
|
||||
|
||||
- name: Install OS dependencies
|
||||
run: |
|
||||
sudo apt update -y
|
||||
echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections
|
||||
echo 'tzdata tzdata/Zones/Europe select Paris' | sudo debconf-set-selections
|
||||
DEBIAN_FRONTEND="noninteractive" sudo apt-get install -y graphicsmagick
|
||||
|
||||
- name: Checkout workflows repo
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
@ -23,77 +77,34 @@ jobs:
|
|||
repository: n8n-io/test-workflows
|
||||
path: test-workflows
|
||||
|
||||
- run: corepack enable
|
||||
working-directory: n8n
|
||||
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'n8n/pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update -y
|
||||
echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections
|
||||
echo 'tzdata tzdata/Zones/Europe select Paris' | sudo debconf-set-selections
|
||||
DEBIAN_FRONTEND="noninteractive" sudo apt-get install -y graphicsmagick
|
||||
shell: bash
|
||||
|
||||
- name: pnpm install and build
|
||||
working-directory: n8n
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build:backend
|
||||
shell: bash
|
||||
|
||||
- name: Import credentials
|
||||
run: n8n/packages/cli/bin/n8n import:credentials --input=test-workflows/credentials.json
|
||||
shell: bash
|
||||
run: packages/cli/bin/n8n import:credentials --input=test-workflows/credentials.json
|
||||
env:
|
||||
N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}}
|
||||
|
||||
- name: Import workflows
|
||||
run: n8n/packages/cli/bin/n8n import:workflow --separate --input=test-workflows/workflows
|
||||
shell: bash
|
||||
run: packages/cli/bin/n8n import:workflow --separate --input=test-workflows/workflows
|
||||
env:
|
||||
N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}}
|
||||
|
||||
- name: Copy static assets
|
||||
run: |
|
||||
cp n8n/assets/n8n-logo.png /tmp/n8n-logo.png
|
||||
cp n8n/assets/n8n-screenshot.png /tmp/n8n-screenshot.png
|
||||
cp assets/n8n-logo.png /tmp/n8n-logo.png
|
||||
cp assets/n8n-screenshot.png /tmp/n8n-screenshot.png
|
||||
cp test-workflows/testData/pdfs/*.pdf /tmp/
|
||||
shell: bash
|
||||
|
||||
- name: Run tests
|
||||
run: n8n/packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --githubWorkflow --shortOutput --concurrency=16 --compare=test-workflows/snapshots
|
||||
shell: bash
|
||||
run: packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --githubWorkflow --shortOutput --concurrency=16 --compare=test-workflows/snapshots
|
||||
id: tests
|
||||
env:
|
||||
N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}}
|
||||
SKIP_STATISTICS_EVENTS: true
|
||||
DB_SQLITE_POOL_SIZE: 4
|
||||
# -
|
||||
# name: Export credentials
|
||||
# if: always()
|
||||
# run: n8n/packages/cli/bin/n8n export:credentials --output=test-workflows/credentials.json --all --pretty
|
||||
# shell: bash
|
||||
# env:
|
||||
# N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}}
|
||||
# -
|
||||
# name: Commit and push credential changes
|
||||
# if: always()
|
||||
# run: |
|
||||
# cd test-workflows
|
||||
# git config --global user.name 'n8n test bot'
|
||||
# git config --global user.email 'n8n-test-bot@users.noreply.github.com'
|
||||
# git commit -am "Automated credential update"
|
||||
# git push --force --quiet "https://janober:${{ secrets.TOKEN }}@github.com/n8n-io/test-workflows.git" main:main
|
||||
N8N_SENTRY_DSN: ${{secrets.CI_SENTRY_DSN}}
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: act10ns/slack@v2.0.0
|
||||
if: failure()
|
||||
if: failure() && github.ref == 'refs/heads/master'
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
channel: '#alerts-build'
|
||||
|
|
8
.github/workflows/units-tests-reusable.yml
vendored
8
.github/workflows/units-tests-reusable.yml
vendored
|
@ -22,6 +22,10 @@ on:
|
|||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
ignoreTurboCache:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: 'Codecov upload token.'
|
||||
|
@ -32,6 +36,7 @@ jobs:
|
|||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_FORCE: ${{ inputs.ignoreTurboCache }}
|
||||
COVERAGE_ENABLED: ${{ inputs.collectCoverage }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
@ -49,7 +54,6 @@ jobs:
|
|||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
if: inputs.collectCoverage != true
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build
|
||||
|
@ -74,6 +78,6 @@ jobs:
|
|||
|
||||
- name: Upload coverage to Codecov
|
||||
if: inputs.collectCoverage
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
uses: codecov/codecov-action@v5.1.2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -7,6 +7,7 @@
|
|||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"mjmlio.vscode-mjml",
|
||||
"Vue.volar"
|
||||
"Vue.volar",
|
||||
"vitest.explorer"
|
||||
]
|
||||
}
|
||||
|
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -47,9 +47,7 @@
|
|||
"request": "launch",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"type": "node",
|
||||
"env": {
|
||||
// "N8N_PORT": "5679",
|
||||
},
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"outputCapture": "std",
|
||||
"killBehavior": "polite"
|
||||
},
|
||||
|
|
473
CHANGELOG.md
473
CHANGELOG.md
|
@ -1,3 +1,476 @@
|
|||
# [1.75.0](https://github.com/n8n-io/n8n/compare/n8n@1.74.0...n8n@1.75.0) (2025-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** AugmentObject should check for own propeties correctly ([#12534](https://github.com/n8n-io/n8n/issues/12534)) ([0cdf393](https://github.com/n8n-io/n8n/commit/0cdf39374305e6bbcedb047db7d3756168e6e89e))
|
||||
* **core:** Disallow code generation in task runner ([#12522](https://github.com/n8n-io/n8n/issues/12522)) ([35b6180](https://github.com/n8n-io/n8n/commit/35b618098b7d23e272bf77b55c172dbe531c821f))
|
||||
* **core:** Fix node exclusion on the frontend types ([#12544](https://github.com/n8n-io/n8n/issues/12544)) ([b2cbed9](https://github.com/n8n-io/n8n/commit/b2cbed9865888f6f3bc528984d4091d86a88f0d6))
|
||||
* **core:** Fix orchestration flow with expired license ([#12444](https://github.com/n8n-io/n8n/issues/12444)) ([ecff3b7](https://github.com/n8n-io/n8n/commit/ecff3b732a028d7225bfbed4ffc65dc20c4ed608))
|
||||
* **core:** Fix Sentry error reporting on task runners ([#12495](https://github.com/n8n-io/n8n/issues/12495)) ([88c0838](https://github.com/n8n-io/n8n/commit/88c0838dd72f11646bdb3586223d6c16631cccab))
|
||||
* **core:** Improve cyclic dependency check in the DI container ([#12600](https://github.com/n8n-io/n8n/issues/12600)) ([c3c4a20](https://github.com/n8n-io/n8n/commit/c3c4a200024fb08afb9380357d1490c6707c5ec3))
|
||||
* **core:** Only show personal credentials in the personal space ([#12433](https://github.com/n8n-io/n8n/issues/12433)) ([8a42d55](https://github.com/n8n-io/n8n/commit/8a42d55d91f4a37fff5669d52d52428b3a4ddd44))
|
||||
* **core:** Prefix package name in `supportedNodes` on generated types as well ([#12514](https://github.com/n8n-io/n8n/issues/12514)) ([4a1a999](https://github.com/n8n-io/n8n/commit/4a1a9993624c92dd81f5418f9268cb93878069ab))
|
||||
* **core:** Prevent prototype pollution in task runner ([#12588](https://github.com/n8n-io/n8n/issues/12588)) ([bdf266c](https://github.com/n8n-io/n8n/commit/bdf266cf55032d05641b20dce8804412dc93b6d5))
|
||||
* **core:** Prevent prototype pollution of internal classes in task runner ([#12610](https://github.com/n8n-io/n8n/issues/12610)) ([eceee7f](https://github.com/n8n-io/n8n/commit/eceee7f3f8899d200b1c5720087cc494eec22e6a))
|
||||
* **core:** Use timing safe function to compare runner auth tokens ([#12485](https://github.com/n8n-io/n8n/issues/12485)) ([8fab98f](https://github.com/n8n-io/n8n/commit/8fab98f3f1f767d05825d24cbf155d56375fdb3e))
|
||||
* **core:** Validate values which are intentionally 0 ([#12382](https://github.com/n8n-io/n8n/issues/12382)) ([562506e](https://github.com/n8n-io/n8n/commit/562506e92aeb26423145801bff80037e5ce2ac46))
|
||||
* Don't break oauth credentials when updating them and allow fixing broken oauth credentials by repeating the authorization flow ([#12563](https://github.com/n8n-io/n8n/issues/12563)) ([73897c7](https://github.com/n8n-io/n8n/commit/73897c7662a432834eb6f9d0f9ace8d986c1acb5))
|
||||
* **editor:** Don't show toolsUnused notice if run had errors ([#12529](https://github.com/n8n-io/n8n/issues/12529)) ([3ec5b28](https://github.com/n8n-io/n8n/commit/3ec5b2850c47057032e61c2acdbdfc1dcdd931f7))
|
||||
* **editor:** Ensure proper "AI Template" URL construction in node creator ([#12566](https://github.com/n8n-io/n8n/issues/12566)) ([13bf69f](https://github.com/n8n-io/n8n/commit/13bf69f75c67bc37a37013e776525768676a4b88))
|
||||
* **editor:** Fix NDV resize handle and scrollbar overlapping ([#12509](https://github.com/n8n-io/n8n/issues/12509)) ([c28f302](https://github.com/n8n-io/n8n/commit/c28f302c2f863bd7aa73ad52e5d040f927e33220))
|
||||
* **editor:** Fix parameter input validation ([#12532](https://github.com/n8n-io/n8n/issues/12532)) ([6711cbc](https://github.com/n8n-io/n8n/commit/6711cbcc641a2fc70f5c15a7e2dcc640a3f98b66))
|
||||
* **editor:** Fix selection rectangle context menu on new canvas ([#12584](https://github.com/n8n-io/n8n/issues/12584)) ([c8e3c53](https://github.com/n8n-io/n8n/commit/c8e3c5399efde93486c1dd5c373cb2c5ff8a0691))
|
||||
* **editor:** Fix the `openselectivenodecreator` custom action on new canvas ([#12580](https://github.com/n8n-io/n8n/issues/12580)) ([2110e9a](https://github.com/n8n-io/n8n/commit/2110e9a0513b8c36beb85302e0d38a2658ea5d6e))
|
||||
* **editor:** Fix workflow initilisation for test definition routes & add unit tests ([#12507](https://github.com/n8n-io/n8n/issues/12507)) ([2775f61](https://github.com/n8n-io/n8n/commit/2775f617ae5c267c0a1ce7a54d05d4077cdbc0f7))
|
||||
* **editor:** Make clicking item in RLC work the first time on small screens ([#12585](https://github.com/n8n-io/n8n/issues/12585)) ([479933f](https://github.com/n8n-io/n8n/commit/479933fbd5c88e783827960e018abb979de8a039))
|
||||
* **editor:** Make sure code editors work correctly in fullscreen ([#12597](https://github.com/n8n-io/n8n/issues/12597)) ([aa1f3a7](https://github.com/n8n-io/n8n/commit/aa1f3a7d989883d55df3777775b8d7d336f6e3b7))
|
||||
* **editor:** Override selected nodes on single click without Meta/Ctrl key ([#12549](https://github.com/n8n-io/n8n/issues/12549)) ([02c2d5e](https://github.com/n8n-io/n8n/commit/02c2d5e71d15b9292fddd585f47bd8334da468c5))
|
||||
* **editor:** Show NDV errors when opening existing nodes with errors ([#12567](https://github.com/n8n-io/n8n/issues/12567)) ([bee7267](https://github.com/n8n-io/n8n/commit/bee7267fe38ab12a79fa4ec0e775f45d98d48aa5))
|
||||
* **editor:** Swap Activate/Deactivate texts in FloatingToolbar ([#12526](https://github.com/n8n-io/n8n/issues/12526)) ([44679b4](https://github.com/n8n-io/n8n/commit/44679b42aa1e14bc7069bee47d0a91ca84b1dba4))
|
||||
* **editor:** Update filter and feedback for source control ([#12504](https://github.com/n8n-io/n8n/issues/12504)) ([865fc21](https://github.com/n8n-io/n8n/commit/865fc21276727e8d88ccee0355147904b81c4421))
|
||||
* **editor:** Update selected node when navigating via flowing nodes ([#12581](https://github.com/n8n-io/n8n/issues/12581)) ([88659d8](https://github.com/n8n-io/n8n/commit/88659d8a2901786c894902e19466f395bcdaab8e))
|
||||
* **Google Calendar Node:** Updates and fixes ([#10715](https://github.com/n8n-io/n8n/issues/10715)) ([7227a29](https://github.com/n8n-io/n8n/commit/7227a29845fd178ced4d281597c62e7a03245456))
|
||||
* **Spotify Node:** Fix issue with null values breaking the response ([#12080](https://github.com/n8n-io/n8n/issues/12080)) ([a56a462](https://github.com/n8n-io/n8n/commit/a56a46259d257003c813103578260d625b3f17dd))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **editor:** Make node credential select searchable ([#12497](https://github.com/n8n-io/n8n/issues/12497)) ([91277c4](https://github.com/n8n-io/n8n/commit/91277c44f1cf3f334b3b50d47d7dcc79b11c7c63))
|
||||
* **editor:** Persist sidebar collapsed status preference ([#12505](https://github.com/n8n-io/n8n/issues/12505)) ([dba7d46](https://github.com/n8n-io/n8n/commit/dba7d46f3ec91d26a597a50dede7b6ca292c728f))
|
||||
|
||||
|
||||
|
||||
# [1.74.0](https://github.com/n8n-io/n8n/compare/n8n@1.73.0...n8n@1.74.0) (2025-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Align concurrency and timeout defaults between instance and runner ([#12503](https://github.com/n8n-io/n8n/issues/12503)) ([9953477](https://github.com/n8n-io/n8n/commit/9953477450c28ec2d211e55aadb825dbae2ee4d6))
|
||||
* **core:** Allow `index` as top-level item key for Code node ([#12469](https://github.com/n8n-io/n8n/issues/12469)) ([1b91000](https://github.com/n8n-io/n8n/commit/1b9100032fc9f8c33e263c8299e04054105da384))
|
||||
* **core:** Don't fail task runner task if logging fails ([#12401](https://github.com/n8n-io/n8n/issues/12401)) ([0860fbe](https://github.com/n8n-io/n8n/commit/0860fbe97108edc21bc01dec3b6ef13e60e728d4))
|
||||
* **core:** Ensure tasks timeout even if they don't receive settings ([#12431](https://github.com/n8n-io/n8n/issues/12431)) ([b194026](https://github.com/n8n-io/n8n/commit/b1940268e6110ed3d8949318a5252ac6563d624f))
|
||||
* **core:** Fix execution cancellation issues in scaling mode ([#12343](https://github.com/n8n-io/n8n/issues/12343)) ([e26b406](https://github.com/n8n-io/n8n/commit/e26b406665e20761279c4e315d04501350427de5))
|
||||
* **core:** Fix manually running a pinned trigger with offloading enabled ([#12491](https://github.com/n8n-io/n8n/issues/12491)) ([be2dcff](https://github.com/n8n-io/n8n/commit/be2dcffc9487973d3e287dd4f6956dbba03757e3))
|
||||
* **core:** Fix task runner sending too many offers ([#12415](https://github.com/n8n-io/n8n/issues/12415)) ([4498e35](https://github.com/n8n-io/n8n/commit/4498e3519276020d3eb01752b5ce0d8ecfbf5fa4))
|
||||
* **core:** Increase default concurrency and timeout in task runners ([#12496](https://github.com/n8n-io/n8n/issues/12496)) ([4182095](https://github.com/n8n-io/n8n/commit/4182095af1c02832af2523f31e9cb85d9a345e60))
|
||||
* **core:** Prevent `__default__` jobs in scaling mode ([#12402](https://github.com/n8n-io/n8n/issues/12402)) ([072664b](https://github.com/n8n-io/n8n/commit/072664b40e06943e0b8ff44287730f2ca569646f))
|
||||
* **core:** Register workflows as active only after all of the triggers and pollers setup successfully ([#12244](https://github.com/n8n-io/n8n/issues/12244)) ([f924f2a](https://github.com/n8n-io/n8n/commit/f924f2a6d736e33ab5fc12cbac6cba27340839db))
|
||||
* **core:** Return unredacted credentials from `GET credentials/:id` ([#12447](https://github.com/n8n-io/n8n/issues/12447)) ([ecabe34](https://github.com/n8n-io/n8n/commit/ecabe34705bbbba07613ba14760449ef38e1b31f))
|
||||
* **core:** Use rate limiter for task runner endpoints ([#12486](https://github.com/n8n-io/n8n/issues/12486)) ([491cb60](https://github.com/n8n-io/n8n/commit/491cb605e3c93d7a261bb0cef0d38f2ddc3affe8))
|
||||
* **editor:** Allow zooming when panning keycode is pressed on new canvas ([#12327](https://github.com/n8n-io/n8n/issues/12327)) ([983e87a](https://github.com/n8n-io/n8n/commit/983e87a9b0c83d35354ce4df34096f47173d0ea7))
|
||||
* **editor:** Consistent protected environment styling and messaging ([#12374](https://github.com/n8n-io/n8n/issues/12374)) ([6891cef](https://github.com/n8n-io/n8n/commit/6891cefa6d0359f85a596829b6055a13529fb1fb))
|
||||
* **editor:** First project button tweaks border and copy ([#12376](https://github.com/n8n-io/n8n/issues/12376)) ([e234756](https://github.com/n8n-io/n8n/commit/e234756457d3c3526531ced4471bf9e69a79fa55))
|
||||
* **editor:** Fix Multi option parameter expression when the value is an array ([#12430](https://github.com/n8n-io/n8n/issues/12430)) ([452a7bf](https://github.com/n8n-io/n8n/commit/452a7bfe2c1e786c46a3ed99de007b0cf3f28d15))
|
||||
* **editor:** Improve configurable nodes design on new canvas ([#12317](https://github.com/n8n-io/n8n/issues/12317)) ([0ecce10](https://github.com/n8n-io/n8n/commit/0ecce10faf60ae44d11007d45e87766b678d3a84))
|
||||
* **editor:** Minor styling improvements in project settings page ([#12405](https://github.com/n8n-io/n8n/issues/12405)) ([09ddce0](https://github.com/n8n-io/n8n/commit/09ddce05800f426d33489ae28c416bb6aab2fd91))
|
||||
* **editor:** Never show Pinned Data Callout for Input Panel ([#12446](https://github.com/n8n-io/n8n/issues/12446)) ([1d5c9bd](https://github.com/n8n-io/n8n/commit/1d5c9bd466becf8aa245a1e8d0b799616d18914a))
|
||||
* **editor:** Nodes' icon color in dark mode ([#12279](https://github.com/n8n-io/n8n/issues/12279)) ([01b781a](https://github.com/n8n-io/n8n/commit/01b781a10828ca2c4cf32762373ad40904c02d2c))
|
||||
* **editor:** Only ignore managed credentials in the HTTP node ([#12417](https://github.com/n8n-io/n8n/issues/12417)) ([6b46657](https://github.com/n8n-io/n8n/commit/6b46657412a1efff35be5083f0ff4c00f9b3e7f9))
|
||||
* **editor:** Remove primary highlight color from edge being executed on new canvas ([#12307](https://github.com/n8n-io/n8n/issues/12307)) ([50913de](https://github.com/n8n-io/n8n/commit/50913de2651450e18307a833ada57656d8959493))
|
||||
* **editor:** Render empty string instead of [empty] ([#12448](https://github.com/n8n-io/n8n/issues/12448)) ([2c72047](https://github.com/n8n-io/n8n/commit/2c72047d0b260db5a4b1fd0d7448ab19378e908f))
|
||||
* **editor:** Show all workflows in the error workflow dropdown in the workflow settings ([#12413](https://github.com/n8n-io/n8n/issues/12413)) ([ccda7f9](https://github.com/n8n-io/n8n/commit/ccda7f9c62e2ba04dbd8a86cfeb5016b56f19c7a))
|
||||
* **editor:** Unify disabled parameters background color ([#12306](https://github.com/n8n-io/n8n/issues/12306)) ([8c63599](https://github.com/n8n-io/n8n/commit/8c635993bd65c84707938d9564d54c1ae17f1c1f))
|
||||
* **HTTP Request Node:** Fix typo in hint ([#12439](https://github.com/n8n-io/n8n/issues/12439)) ([b6230b6](https://github.com/n8n-io/n8n/commit/b6230b63f2ed8c7531b53c896f8b033c599e156e))
|
||||
* **OpenAI Node:** Add quotes to default base URL ([#12312](https://github.com/n8n-io/n8n/issues/12312)) ([2e90eba](https://github.com/n8n-io/n8n/commit/2e90eba47eff81f8b17a305cbc1656f929d622f8))
|
||||
* **OpenAI Node:** Update node to account for URL in credentials ([#12356](https://github.com/n8n-io/n8n/issues/12356)) ([f78cceb](https://github.com/n8n-io/n8n/commit/f78ccebe514819dca03f5c220274b94fd6d1c73b))
|
||||
* **Postgres Node:** Account for JSON expressions ([#12012](https://github.com/n8n-io/n8n/issues/12012)) ([06b86af](https://github.com/n8n-io/n8n/commit/06b86af7356b3be0af146c49f9720b24157b9e61))
|
||||
* **Postgres Node:** Allow passing in arrays to JSON columns for insert ([#12452](https://github.com/n8n-io/n8n/issues/12452)) ([9dd0686](https://github.com/n8n-io/n8n/commit/9dd068632b1542126831baa83cf638ce369b0947))
|
||||
* **Postgres Node:** Re-use connection pool across executions ([#12346](https://github.com/n8n-io/n8n/issues/12346)) ([2ca37f5](https://github.com/n8n-io/n8n/commit/2ca37f5f7f7f80c50dbc8c87146b8bff510f01c8))
|
||||
* Run workflow if active and single webhook service has pin data ([#12425](https://github.com/n8n-io/n8n/issues/12425)) ([8053a4a](https://github.com/n8n-io/n8n/commit/8053a4a1763d143da80b9e4e00dcef9b716ce6b2))
|
||||
* Set correct default for added Resource Mapper boolean fields ([#12344](https://github.com/n8n-io/n8n/issues/12344)) ([b4c77f2](https://github.com/n8n-io/n8n/commit/b4c77f27b66275ddb58138e8d2fe1509265e9652))
|
||||
* **Supabase Node:** Allow for filtering on the same field multiple times ([#12429](https://github.com/n8n-io/n8n/issues/12429)) ([d7cc789](https://github.com/n8n-io/n8n/commit/d7cc789d79477aff40ff4eca0175c7578aef338a))
|
||||
* **Zep Vector Store Node:** Cloud vector store integration ([#12353](https://github.com/n8n-io/n8n/issues/12353)) ([2433d6b](https://github.com/n8n-io/n8n/commit/2433d6b7d3dede2595dd5b637ca8bbc1103272b3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* (Execute Workflow Node): Inputs for Sub-workflows ([#11830](https://github.com/n8n-io/n8n/issues/11830)) ([#11837](https://github.com/n8n-io/n8n/issues/11837)) ([d411663](https://github.com/n8n-io/n8n/commit/d4116630a638195c7d87e01e2b5c151941636056))
|
||||
* Add load options to new tool mode for vector stores ([#12462](https://github.com/n8n-io/n8n/issues/12462)) ([3109de6](https://github.com/n8n-io/n8n/commit/3109de6073b237ee3dcc93afb69345586f3b836d))
|
||||
* Add migration to add `managed` column to credentials table ([#12275](https://github.com/n8n-io/n8n/issues/12275)) ([3cb7081](https://github.com/n8n-io/n8n/commit/3cb70814465e8fa504e909ef36b21b79d4b70b28))
|
||||
* Allow using Vector Stores directly as Tools ([#12311](https://github.com/n8n-io/n8n/issues/12311)) ([76dded4](https://github.com/n8n-io/n8n/commit/76dded4bea9d26ad84fdbde74d577d244eb4e223))
|
||||
* **core:** Add endpoint to create free AI credits ([#12362](https://github.com/n8n-io/n8n/issues/12362)) ([ac4e042](https://github.com/n8n-io/n8n/commit/ac4e0422316a4dcd19151dd7d504e2b3cccbc038))
|
||||
* **core:** Add includeData parameter to `GET /credentials` ([#12220](https://github.com/n8n-io/n8n/issues/12220)) ([f56ad8c](https://github.com/n8n-io/n8n/commit/f56ad8cf49f7cf0665035d2e43bb7ff5b8fd75f3))
|
||||
* **core:** Comply with `NO_COLOR` in logs ([#12347](https://github.com/n8n-io/n8n/issues/12347)) ([1e60bbc](https://github.com/n8n-io/n8n/commit/1e60bbcf169e8624a97ddde543cdd1d406e5c7ca))
|
||||
* **core:** Offload manual executions to workers ([#11284](https://github.com/n8n-io/n8n/issues/11284)) ([9432aa0](https://github.com/n8n-io/n8n/commit/9432aa0b00e74faf4651ac673f18e16b7e56e145))
|
||||
* **editor:** Add free AI credits CTA ([#12365](https://github.com/n8n-io/n8n/issues/12365)) ([f873196](https://github.com/n8n-io/n8n/commit/f8731963f6754386f15c8417c0cc32dba87c481a))
|
||||
* **editor:** Add support for project icons ([#12349](https://github.com/n8n-io/n8n/issues/12349)) ([9117718](https://github.com/n8n-io/n8n/commit/9117718cc960e2bad5a5db07b10e9e7b561ec5e4))
|
||||
* **editor:** Easy AI workflow improvements ([#12400](https://github.com/n8n-io/n8n/issues/12400)) ([8dc691d](https://github.com/n8n-io/n8n/commit/8dc691dc62692f8af143c84032391397adeb790d))
|
||||
* **editor:** Make workflows, credentials, executions and new canvas usable on mobile and touch devices ([#12372](https://github.com/n8n-io/n8n/issues/12372)) ([06c9473](https://github.com/n8n-io/n8n/commit/06c94732103687705d71c5a1c5bfa993e3df3427))
|
||||
* **editor:** New Code editor based on the TypeScript language service ([#12285](https://github.com/n8n-io/n8n/issues/12285)) ([52ae02a](https://github.com/n8n-io/n8n/commit/52ae02abaa92e5bbfda58843c8eccc845506fa4b))
|
||||
* **editor:** Update Sub-Workflow Debugging Copy ([#12483](https://github.com/n8n-io/n8n/issues/12483)) ([04e2928](https://github.com/n8n-io/n8n/commit/04e2928d345f83c202c762e4673cf878b4762f33))
|
||||
* **Google Vertex Chat Model Node:** Add an option to specify GCP region ([#12300](https://github.com/n8n-io/n8n/issues/12300)) ([30f9c03](https://github.com/n8n-io/n8n/commit/30f9c033db28112e1f97bb55d41b5bfce265cb51))
|
||||
* **HighLevel Node:** Add support for calendar items ([#10820](https://github.com/n8n-io/n8n/issues/10820)) ([6e189fd](https://github.com/n8n-io/n8n/commit/6e189fda776051e09e90b3d86ecd0d1e80dcc0c6))
|
||||
* **Microsoft Entra ID Node:** New node ([#11779](https://github.com/n8n-io/n8n/issues/11779)) ([3006ccf](https://github.com/n8n-io/n8n/commit/3006ccf41bb911ba72f087a1479889fbf308c17d))
|
||||
|
||||
|
||||
|
||||
# [1.73.0](https://github.com/n8n-io/n8n/compare/n8n@1.72.0...n8n@1.73.0) (2024-12-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Ensure runners do not throw on unsupported console methods ([#12167](https://github.com/n8n-io/n8n/issues/12167)) ([57c6a61](https://github.com/n8n-io/n8n/commit/57c6a6167dd2b30f0082a416daefce994ecad33a))
|
||||
* **core:** Fix `$getWorkflowStaticData` on task runners ([#12153](https://github.com/n8n-io/n8n/issues/12153)) ([b479f14](https://github.com/n8n-io/n8n/commit/b479f14ef5012551b823bea5d2ffbddedfd50a77))
|
||||
* **core:** Fix binary data helpers (like `prepareBinaryData`) with task runner ([#12259](https://github.com/n8n-io/n8n/issues/12259)) ([0f1461f](https://github.com/n8n-io/n8n/commit/0f1461f2d5d7ec34236ed7fcec3e2f9ee7eb73c4))
|
||||
* **core:** Fix race condition in AI tool invocation with multiple items from the parent ([#12169](https://github.com/n8n-io/n8n/issues/12169)) ([dce0c58](https://github.com/n8n-io/n8n/commit/dce0c58f8605c33fc50ec8aa422f0fb5eee07637))
|
||||
* **core:** Fix serialization of circular json with task runner ([#12288](https://github.com/n8n-io/n8n/issues/12288)) ([a99d726](https://github.com/n8n-io/n8n/commit/a99d726f42d027b64f94eda0d385b597c5d5be2e))
|
||||
* **core:** Upgrade nanoid to address CVE-2024-55565 ([#12171](https://github.com/n8n-io/n8n/issues/12171)) ([8c0bd02](https://github.com/n8n-io/n8n/commit/8c0bd0200c386b122f495c453ccc97a001e4729c))
|
||||
* **editor:** Add new create first project CTA ([#12189](https://github.com/n8n-io/n8n/issues/12189)) ([878b419](https://github.com/n8n-io/n8n/commit/878b41904d76eda3ee230f850127b4d56993de24))
|
||||
* **editor:** Fix canvas ready opacity transition on new canvas ([#12264](https://github.com/n8n-io/n8n/issues/12264)) ([5d33a6b](https://github.com/n8n-io/n8n/commit/5d33a6ba8a2bccea097402fd04c0e2b00e423e76))
|
||||
* **editor:** Fix rendering of code-blocks in sticky notes ([#12227](https://github.com/n8n-io/n8n/issues/12227)) ([9b59035](https://github.com/n8n-io/n8n/commit/9b5903524b95bd21d5915908780942790cf88d27))
|
||||
* **editor:** Fix sticky color picker getting covered by nodes on new canvas ([#12263](https://github.com/n8n-io/n8n/issues/12263)) ([27bd3c8](https://github.com/n8n-io/n8n/commit/27bd3c85b3a4ddcf763a543b232069bb108130cf))
|
||||
* **editor:** Improve commit modal user facing messaging ([#12161](https://github.com/n8n-io/n8n/issues/12161)) ([ad39243](https://github.com/n8n-io/n8n/commit/ad392439826b17bd0b84f981e0958d88f09e7fe9))
|
||||
* **editor:** Prevent connection line from showing when clicking the plus button of a node ([#12265](https://github.com/n8n-io/n8n/issues/12265)) ([9180b46](https://github.com/n8n-io/n8n/commit/9180b46b52302b203eecf3bb81c3f2132527a1e6))
|
||||
* **editor:** Prevent stickies from being edited in preview mode in the new canvas ([#12222](https://github.com/n8n-io/n8n/issues/12222)) ([6706dcd](https://github.com/n8n-io/n8n/commit/6706dcdf72d54f33c1cf4956602c3a64a1578826))
|
||||
* **editor:** Reduce cases for Auto-Add of ChatTrigger for AI Agents ([#12154](https://github.com/n8n-io/n8n/issues/12154)) ([365e82d](https://github.com/n8n-io/n8n/commit/365e82d2008dff2f9c91664ee04d7a78363a8b30))
|
||||
* **editor:** Remove invalid connections after node handles change ([#12247](https://github.com/n8n-io/n8n/issues/12247)) ([6330bec](https://github.com/n8n-io/n8n/commit/6330bec4db0175b558f2747837323fdbb25b634a))
|
||||
* **editor:** Set dangerouslyUseHTMLString in composable ([#12280](https://github.com/n8n-io/n8n/issues/12280)) ([6ba91b5](https://github.com/n8n-io/n8n/commit/6ba91b5e1ed197c67146347a6f6e663ecdf3de48))
|
||||
* **editor:** Set RunData outputIndex based on incoming data ([#12182](https://github.com/n8n-io/n8n/issues/12182)) ([dc4261a](https://github.com/n8n-io/n8n/commit/dc4261ae7eca6cf277404cd514c90fad42f14ae0))
|
||||
* **editor:** Update the universal create button interaction ([#12105](https://github.com/n8n-io/n8n/issues/12105)) ([5300e0a](https://github.com/n8n-io/n8n/commit/5300e0ac45bf832b3d2957198a49a1c687f3fe1f))
|
||||
* **Elasticsearch Node:** Fix issue stopping search queries being sent ([#11464](https://github.com/n8n-io/n8n/issues/11464)) ([388a83d](https://github.com/n8n-io/n8n/commit/388a83dfbdc6ac301e4df704666df9f09fb7d0b3))
|
||||
* **Extract from File Node:** Detect file encoding ([#12081](https://github.com/n8n-io/n8n/issues/12081)) ([92af245](https://github.com/n8n-io/n8n/commit/92af245d1aab5bfad8618fda69b2405f5206875d))
|
||||
* **Github Node:** Fix fetch of file names with ? character ([#12206](https://github.com/n8n-io/n8n/issues/12206)) ([39462ab](https://github.com/n8n-io/n8n/commit/39462abe1fde7e82b5e5b8f3ceebfcadbfd7c925))
|
||||
* **Invoice Ninja Node:** Fix actions for bank transactions ([#11511](https://github.com/n8n-io/n8n/issues/11511)) ([80eea49](https://github.com/n8n-io/n8n/commit/80eea49cf0bf9db438eb85af7cd22aeb11fbfed2))
|
||||
* **Linear Node:** Fix issue with error handling ([#12191](https://github.com/n8n-io/n8n/issues/12191)) ([b8eae5f](https://github.com/n8n-io/n8n/commit/b8eae5f28a7d523195f4715cd8da77b3a884ae4c))
|
||||
* **MongoDB Node:** Fix checks on projection feature call ([#10563](https://github.com/n8n-io/n8n/issues/10563)) ([58bab46](https://github.com/n8n-io/n8n/commit/58bab461c4c5026b2ca5ea143cbcf98bf3a4ced8))
|
||||
* **Postgres Node:** Allow users to wrap strings with $$ ([#12034](https://github.com/n8n-io/n8n/issues/12034)) ([0c15e30](https://github.com/n8n-io/n8n/commit/0c15e30778cc5cb10ed368df144d6fbb2504ec70))
|
||||
* **Redis Node:** Add support for username auth ([#12274](https://github.com/n8n-io/n8n/issues/12274)) ([64c0414](https://github.com/n8n-io/n8n/commit/64c0414ef28acf0f7ec42b4b0bb21cbf2921ebe7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add solarwinds ipam credentials ([#12005](https://github.com/n8n-io/n8n/issues/12005)) ([882484e](https://github.com/n8n-io/n8n/commit/882484e8ee7d1841d5d600414ca48e9915abcfa8))
|
||||
* Add SolarWinds Observability node credentials ([#11805](https://github.com/n8n-io/n8n/issues/11805)) ([e8a5db5](https://github.com/n8n-io/n8n/commit/e8a5db5beb572edbb61dd9100b70827ccc4cca58))
|
||||
* **AI Agent Node:** Update descriptions and titles for Chat Trigger options in AI Agents and Memory ([#12155](https://github.com/n8n-io/n8n/issues/12155)) ([07a6ae1](https://github.com/n8n-io/n8n/commit/07a6ae11b3291c1805553d55ba089fe8dd919fd8))
|
||||
* **API:** Exclude pinned data from workflows ([#12261](https://github.com/n8n-io/n8n/issues/12261)) ([e0dc385](https://github.com/n8n-io/n8n/commit/e0dc385f8bc8ee13fbc5bbf35e07654e52b193e9))
|
||||
* **editor:** Params pane collection improvements ([#11607](https://github.com/n8n-io/n8n/issues/11607)) ([6e44c71](https://github.com/n8n-io/n8n/commit/6e44c71c9ca82cce20eb55bb9003930bbf66a16c))
|
||||
* **editor:** Support adding nodes via drag and drop from node creator on new canvas ([#12197](https://github.com/n8n-io/n8n/issues/12197)) ([1bfd9c0](https://github.com/n8n-io/n8n/commit/1bfd9c0e913f3eefc4593f6c344db1ae1f6e4df4))
|
||||
* **Facebook Graph API Node:** Update node to support API v21.0 ([#12116](https://github.com/n8n-io/n8n/issues/12116)) ([14c33f6](https://github.com/n8n-io/n8n/commit/14c33f666fe92f7173e4f471fb478e629e775c62))
|
||||
* **Linear Trigger Node:** Add support for admin scope ([#12211](https://github.com/n8n-io/n8n/issues/12211)) ([410ea9a](https://github.com/n8n-io/n8n/commit/410ea9a2ef2e14b5e8e4493e5db66cfc2290d8f6))
|
||||
* **MailerLite Node:** Update node to support new api ([#11933](https://github.com/n8n-io/n8n/issues/11933)) ([d6b8e65](https://github.com/n8n-io/n8n/commit/d6b8e65abeb411f86538c1630dcce832ee0846a9))
|
||||
* Send and wait operation - freeText and customForm response types ([#12106](https://github.com/n8n-io/n8n/issues/12106)) ([e98c7f1](https://github.com/n8n-io/n8n/commit/e98c7f160b018243dc88490d46fb1047a4d7fcdc))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **editor:** SchemaView performance improvement by ≈90% 🚀 ([#12180](https://github.com/n8n-io/n8n/issues/12180)) ([6a58309](https://github.com/n8n-io/n8n/commit/6a5830959f5fb493a4119869b8298d8ed702c84a))
|
||||
|
||||
|
||||
|
||||
# [1.72.0](https://github.com/n8n-io/n8n/compare/n8n@1.71.0...n8n@1.72.0) (2024-12-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Allow disabling MFA with recovery codes ([#12014](https://github.com/n8n-io/n8n/issues/12014)) ([95d56fe](https://github.com/n8n-io/n8n/commit/95d56fee8d0168b75fca6dcf41702d2f10c930a8))
|
||||
* Chat triggers don't work with the new partial execution flow ([#11952](https://github.com/n8n-io/n8n/issues/11952)) ([2b6a72f](https://github.com/n8n-io/n8n/commit/2b6a72f1289c01145edf2b88e5027d2b9b2ed624))
|
||||
* **core:** Execute nodes after loops correctly with the new partial execution flow ([#11978](https://github.com/n8n-io/n8n/issues/11978)) ([891dd7f](https://github.com/n8n-io/n8n/commit/891dd7f995c78a2355a049b7ced981a5f6b1c40c))
|
||||
* **core:** Fix support for multiple invocation of AI tools ([#12141](https://github.com/n8n-io/n8n/issues/12141)) ([c572c06](https://github.com/n8n-io/n8n/commit/c572c0648ca5b644b222157b3cabac9c05704a84))
|
||||
* **core:** Make sure task runner exits ([#12123](https://github.com/n8n-io/n8n/issues/12123)) ([c5effca](https://github.com/n8n-io/n8n/commit/c5effca7d47a713f157eea21d7892002e9ab7283))
|
||||
* **core:** Remove run data of nodes unrelated to the current partial execution ([#12099](https://github.com/n8n-io/n8n/issues/12099)) ([c4e4d37](https://github.com/n8n-io/n8n/commit/c4e4d37a8785d1a4bcd376cb1c49b82a80aa4391))
|
||||
* **core:** Return homeProject when filtering workflows by project id ([#12077](https://github.com/n8n-io/n8n/issues/12077)) ([efafeed](https://github.com/n8n-io/n8n/commit/efafeed33482100a23fa0163a53b9ce93cd6b2c3))
|
||||
* **editor:** Don't reset all Parameter Inputs when switched to read-only ([#12063](https://github.com/n8n-io/n8n/issues/12063)) ([706702d](https://github.com/n8n-io/n8n/commit/706702dff8da3c2e949e2c98dd5b34b299a1f17c))
|
||||
* **editor:** Fix canvas panning using `Control` + `Left Mouse Button` on Windows ([#12104](https://github.com/n8n-io/n8n/issues/12104)) ([43009b6](https://github.com/n8n-io/n8n/commit/43009b6aa820f24b9e6f519e7a45592aa21db03e))
|
||||
* **editor:** Fix Nodeview.v2 reinitialise based on route changes ([#12062](https://github.com/n8n-io/n8n/issues/12062)) ([b1f8663](https://github.com/n8n-io/n8n/commit/b1f866326574974eb2936e6b02771346e83e7137))
|
||||
* **editor:** Fix svg background pattern rendering on safari ([#12079](https://github.com/n8n-io/n8n/issues/12079)) ([596f221](https://github.com/n8n-io/n8n/commit/596f22103c01e14063ebb2388c4dabf4714d37c6))
|
||||
* **editor:** Fix switching from v2 to v1 ([#12050](https://github.com/n8n-io/n8n/issues/12050)) ([5c76de3](https://github.com/n8n-io/n8n/commit/5c76de324c2e25b0d8b74cdab79f04aa616d8c4f))
|
||||
* **editor:** Improvements to the commit modal ([#12031](https://github.com/n8n-io/n8n/issues/12031)) ([4fe1952](https://github.com/n8n-io/n8n/commit/4fe1952e2fb3379d95da42a7bb531851af6d0094))
|
||||
* **editor:** Load node types in demo and preview modes ([#12048](https://github.com/n8n-io/n8n/issues/12048)) ([4ac5f95](https://github.com/n8n-io/n8n/commit/4ac5f9527bbec382a65ed3f1d9c41d6948c154e3))
|
||||
* **editor:** Polyfill crypto.randomUUID ([#12052](https://github.com/n8n-io/n8n/issues/12052)) ([0537524](https://github.com/n8n-io/n8n/commit/0537524c3e45d7633415c7a9175a3857ad52cd58))
|
||||
* **editor:** Redirect Settings to the proper sub page depending on the instance type (cloud or not) ([#12053](https://github.com/n8n-io/n8n/issues/12053)) ([a16d006](https://github.com/n8n-io/n8n/commit/a16d006f893cac927d674fa447b08c1205b67c54))
|
||||
* **editor:** Render sanitized HTML content in toast messages ([#12139](https://github.com/n8n-io/n8n/issues/12139)) ([0468945](https://github.com/n8n-io/n8n/commit/0468945c99f083577c4cc71f671b4b950f6aeb86))
|
||||
* **editor:** Universal button snags ([#11974](https://github.com/n8n-io/n8n/issues/11974)) ([956b11a](https://github.com/n8n-io/n8n/commit/956b11a560528336a74be40f722fa05bf3cca94d))
|
||||
* **editor:** Update concurrency UI considering different types of instances ([#12068](https://github.com/n8n-io/n8n/issues/12068)) ([fa572bb](https://github.com/n8n-io/n8n/commit/fa572bbca4397b1cc42668530497444630ed17eb))
|
||||
* **FTP Node:** Fix issue with creating folders on rename ([#9340](https://github.com/n8n-io/n8n/issues/9340)) ([eb7d593](https://github.com/n8n-io/n8n/commit/eb7d5934ef8bc6e999d6de4c0b8025ce175df5dd))
|
||||
* **n8n Form Node:** Completion page display if EXECUTIONS_DATA_SAVE_ON_SUCCESS=none ([#11869](https://github.com/n8n-io/n8n/issues/11869)) ([f4c2523](https://github.com/n8n-io/n8n/commit/f4c252341985fe03927a2fd5d60ba846ec3dfc77))
|
||||
* **OpenAI Node:** Allow updating assistant files ([#12042](https://github.com/n8n-io/n8n/issues/12042)) ([7b20f8a](https://github.com/n8n-io/n8n/commit/7b20f8aaa8befd19dbad0af3bf1b881342c1fca5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **AI Transform Node:** Reduce payload size ([#11965](https://github.com/n8n-io/n8n/issues/11965)) ([d8ca8de](https://github.com/n8n-io/n8n/commit/d8ca8de13a4cbb856696873bdb56c66b12a5b027))
|
||||
* **core:** Add option to filter for empty variables ([#12112](https://github.com/n8n-io/n8n/issues/12112)) ([a63f0e8](https://github.com/n8n-io/n8n/commit/a63f0e878e21da9924451e2679939209b34b6583))
|
||||
* **core:** Cancel runner task on timeout in external mode ([#12101](https://github.com/n8n-io/n8n/issues/12101)) ([addb4fa](https://github.com/n8n-io/n8n/commit/addb4fa352c88d856e463bb2b7001173c4fd6a7d))
|
||||
* **core:** Parent workflows should wait for sub-workflows to finish ([#11985](https://github.com/n8n-io/n8n/issues/11985)) ([60b3dcc](https://github.com/n8n-io/n8n/commit/60b3dccf9317da6f3013be35a78ce21d0416ad80))
|
||||
* **editor:** Implementing the `Easy AI Workflow` experiment ([#12043](https://github.com/n8n-io/n8n/issues/12043)) ([67ed1d2](https://github.com/n8n-io/n8n/commit/67ed1d2c3c2e69d5a96daf7de2795c02f5d8f15b))
|
||||
* **Redis Node:** Add support for continue on fail / error output branch ([#11714](https://github.com/n8n-io/n8n/issues/11714)) ([ed35958](https://github.com/n8n-io/n8n/commit/ed359586c88a7662f4d94d58c5a87cf91d027ab9))
|
||||
|
||||
|
||||
|
||||
# [1.71.0](https://github.com/n8n-io/n8n/compare/n8n@1.70.0...n8n@1.71.0) (2024-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Fix push for waiting executions ([#11984](https://github.com/n8n-io/n8n/issues/11984)) ([8d71307](https://github.com/n8n-io/n8n/commit/8d71307da0398e7e39bf53e8e1cfa21ac1ceaf69))
|
||||
* **core:** Improve header parameter parsing on http client responses ([#11953](https://github.com/n8n-io/n8n/issues/11953)) ([41e9e39](https://github.com/n8n-io/n8n/commit/41e9e39b5b53ecd9d8d1b385df65a26ecb9bccd8))
|
||||
* **core:** Opt-out from optimizations if `$item` is used ([#12036](https://github.com/n8n-io/n8n/issues/12036)) ([872535a](https://github.com/n8n-io/n8n/commit/872535a40c85dcfad3a4b27c57c026ae003f562f))
|
||||
* **core:** Use the configured timezone in task runner ([#12032](https://github.com/n8n-io/n8n/issues/12032)) ([2e6845a](https://github.com/n8n-io/n8n/commit/2e6845afcbc30dff73c3f3f15f21278cab397387))
|
||||
* **core:** Validate node name when creating `NodeOperationErrror` ([#11999](https://github.com/n8n-io/n8n/issues/11999)) ([e68c9da](https://github.com/n8n-io/n8n/commit/e68c9da30c31cd5f994cb01ce759192562bfbd40))
|
||||
* **editor:** Add execution concurrency info and paywall ([#11847](https://github.com/n8n-io/n8n/issues/11847)) ([57d3269](https://github.com/n8n-io/n8n/commit/57d3269e400ee4e7e3636614870ebdfdb0aa8c1d))
|
||||
* **editor:** Fix bug causing connection lines to disappear when hovering stickies ([#11950](https://github.com/n8n-io/n8n/issues/11950)) ([439a1cc](https://github.com/n8n-io/n8n/commit/439a1cc4f39243e91715b21a84b8e7266ce872cd))
|
||||
* **editor:** Fix canvas keybindings using splitter keys such as zooming using `+` key ([#12022](https://github.com/n8n-io/n8n/issues/12022)) ([6af9c82](https://github.com/n8n-io/n8n/commit/6af9c82af6020e99d61e442ee9c2d40761baf027))
|
||||
* **editor:** Fix community check ([#11979](https://github.com/n8n-io/n8n/issues/11979)) ([af0398a](https://github.com/n8n-io/n8n/commit/af0398a5e3a8987c01c7112e6f689b35e1ef92fe))
|
||||
* **editor:** Fix copy/paste keyboard events in canvas chat ([#12004](https://github.com/n8n-io/n8n/issues/12004)) ([967340a](https://github.com/n8n-io/n8n/commit/967340a2938a79c89319121bf57a8d654f88e06c))
|
||||
* **editor:** Fix node showing as successful if errors exists on subsequent runs ([#12019](https://github.com/n8n-io/n8n/issues/12019)) ([8616b17](https://github.com/n8n-io/n8n/commit/8616b17cc6c305da69bbb54fd56ab7cb34213f7c))
|
||||
* **editor:** Fix pin data showing up in production executions on new canvas ([#11951](https://github.com/n8n-io/n8n/issues/11951)) ([5f6f8a1](https://github.com/n8n-io/n8n/commit/5f6f8a1bddfd76b586c08da821e8b59070f449fc))
|
||||
* **editor:** Handle source control initialization to prevent UI form crashing ([#11776](https://github.com/n8n-io/n8n/issues/11776)) ([6be8e86](https://github.com/n8n-io/n8n/commit/6be8e86c45bd64d000bc95d2ef2d68220e930c02))
|
||||
* **editor:** Implement dirty nodes for partial executions ([#11739](https://github.com/n8n-io/n8n/issues/11739)) ([b8da4ff](https://github.com/n8n-io/n8n/commit/b8da4ff9edb0fbb0093c4c41fe11f8e67b696ca3))
|
||||
* **editor:** Resolve going back from Settings ([#11958](https://github.com/n8n-io/n8n/issues/11958)) ([d74423c](https://github.com/n8n-io/n8n/commit/d74423c75198d38d0d99a1879051b5e964ecae74))
|
||||
* **editor:** Unify executions card label color ([#11949](https://github.com/n8n-io/n8n/issues/11949)) ([fc79718](https://github.com/n8n-io/n8n/commit/fc797188d63e87df34b3a153eb4a0d0b7361b3f5))
|
||||
* **editor:** Use optional chaining for all members in execution data when using the debug feature ([#12024](https://github.com/n8n-io/n8n/issues/12024)) ([67aa0c9](https://github.com/n8n-io/n8n/commit/67aa0c9107bda16b1cb6d273e17c3cde77035f51))
|
||||
* **GraphQL Node:** Throw error if GraphQL variables are not objects or strings ([#11904](https://github.com/n8n-io/n8n/issues/11904)) ([85f30b2](https://github.com/n8n-io/n8n/commit/85f30b27ae282da58a25186d13ff17196dcd7d9c))
|
||||
* **HTTP Request Node:** Use iconv-lite to decode http responses, to support more encoding types ([#11930](https://github.com/n8n-io/n8n/issues/11930)) ([461b39c](https://github.com/n8n-io/n8n/commit/461b39c5df5dd446cb8ceef469b204c7c5111229))
|
||||
* Load workflows with unconnected Switch outputs ([#12020](https://github.com/n8n-io/n8n/issues/12020)) ([abc851c](https://github.com/n8n-io/n8n/commit/abc851c0cff298607a0dc2f2882aa17136898f45))
|
||||
* **n8n Form Node:** Use https to load google fonts ([#11948](https://github.com/n8n-io/n8n/issues/11948)) ([eccd924](https://github.com/n8n-io/n8n/commit/eccd924f5e8dbe59e37099d1a6fbe8866fef55bf))
|
||||
* **Telegram Trigger Node:** Fix header secret check ([#12018](https://github.com/n8n-io/n8n/issues/12018)) ([f16de4d](https://github.com/n8n-io/n8n/commit/f16de4db01c0496205635a3203a44098e7908453))
|
||||
* **Webflow Node:** Fix issue with pagination in v2 node ([#11934](https://github.com/n8n-io/n8n/issues/11934)) ([1eb94bc](https://github.com/n8n-io/n8n/commit/1eb94bcaf54d9e581856ce0b87253e1c28fa68e2))
|
||||
* **Webflow Node:** Fix issue with publishing items ([#11982](https://github.com/n8n-io/n8n/issues/11982)) ([0a8a57e](https://github.com/n8n-io/n8n/commit/0a8a57e4ec8081ab1a53f36d686b3d5dcaae2476))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **AI Transform Node:** Node Prompt improvements ([#11611](https://github.com/n8n-io/n8n/issues/11611)) ([40a7445](https://github.com/n8n-io/n8n/commit/40a7445f0873af2cdbd10b12bd691c07a43e27cc))
|
||||
* **Code Node:** Warning if pairedItem absent or could not be auto mapped ([#11737](https://github.com/n8n-io/n8n/issues/11737)) ([3a5bd12](https://github.com/n8n-io/n8n/commit/3a5bd129459272cbac960ae2754db3028943f87e))
|
||||
* **editor:** Canvas chat UI & UX improvements ([#11924](https://github.com/n8n-io/n8n/issues/11924)) ([1e25774](https://github.com/n8n-io/n8n/commit/1e25774541461c86da5c4af8efec792e2814eeb1))
|
||||
* **editor:** Persist user's preferred display modes on localStorage ([#11929](https://github.com/n8n-io/n8n/issues/11929)) ([bd69316](https://github.com/n8n-io/n8n/commit/bd693162b86a21c90880bab2c2e67aab733095ff))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **editor:** Virtualize SchemaView ([#11694](https://github.com/n8n-io/n8n/issues/11694)) ([9c6def9](https://github.com/n8n-io/n8n/commit/9c6def91975764522fa52cdf21e9cb5bdb4d721d))
|
||||
|
||||
|
||||
|
||||
# [1.70.0](https://github.com/n8n-io/n8n/compare/n8n@1.69.0...n8n@1.70.0) (2024-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AI Agent Node:** Add binary message before scratchpad to prevent tool calling loops ([#11845](https://github.com/n8n-io/n8n/issues/11845)) ([5c80cb5](https://github.com/n8n-io/n8n/commit/5c80cb57cf709a1097a38e0394aad6fce5330eba))
|
||||
* CodeNodeEditor walk cannot read properties of null ([#11129](https://github.com/n8n-io/n8n/issues/11129)) ([d99e0a7](https://github.com/n8n-io/n8n/commit/d99e0a7c979a1ee96b2eea1b9011d5bce375289a))
|
||||
* **core:** Bring back execution data on the `executionFinished` push message ([#11821](https://github.com/n8n-io/n8n/issues/11821)) ([0313570](https://github.com/n8n-io/n8n/commit/03135702f18e750ba44840dccfec042270629a2b))
|
||||
* **core:** Correct invalid WS status code on removing connection ([#11901](https://github.com/n8n-io/n8n/issues/11901)) ([1d80225](https://github.com/n8n-io/n8n/commit/1d80225d26ba01f78934a455acdcca7b83be7205))
|
||||
* **core:** Don't use unbound context methods in code sandboxes ([#11914](https://github.com/n8n-io/n8n/issues/11914)) ([f6c0d04](https://github.com/n8n-io/n8n/commit/f6c0d045e9683cd04ee849f37b96697097c5b41d))
|
||||
* **core:** Fix broken execution query when using projectId ([#11852](https://github.com/n8n-io/n8n/issues/11852)) ([a061dbc](https://github.com/n8n-io/n8n/commit/a061dbca07ad686c563e85c56081bc1a7830259b))
|
||||
* **core:** Fix validation of items returned in the task runner ([#11897](https://github.com/n8n-io/n8n/issues/11897)) ([a535e88](https://github.com/n8n-io/n8n/commit/a535e88f1aec8fbbf2eb9397d38748f49773de2d))
|
||||
* **editor:** Add missing trigger waiting tooltip on new canvas ([#11918](https://github.com/n8n-io/n8n/issues/11918)) ([a8df221](https://github.com/n8n-io/n8n/commit/a8df221bfbb5428d93d03f539bcfdaf29ee20c21))
|
||||
* **editor:** Don't re-render input panel after node finishes executing ([#11813](https://github.com/n8n-io/n8n/issues/11813)) ([b3a99a2](https://github.com/n8n-io/n8n/commit/b3a99a2351079c37ed6d83f43920ba80f3832234))
|
||||
* **editor:** Fix AI assistant loading message layout ([#11819](https://github.com/n8n-io/n8n/issues/11819)) ([89b4807](https://github.com/n8n-io/n8n/commit/89b48072432753137b498c338af7777036fdde7a))
|
||||
* **editor:** Fix new canvas discovery tooltip position after adding github stars button ([#11898](https://github.com/n8n-io/n8n/issues/11898)) ([f4ab5c7](https://github.com/n8n-io/n8n/commit/f4ab5c7b9244b8fdde427c12c1a152fbaaba0c34))
|
||||
* **editor:** Fix node position not getting set when dragging selection on new canvas ([#11871](https://github.com/n8n-io/n8n/issues/11871)) ([595de81](https://github.com/n8n-io/n8n/commit/595de81c03b3e488ab41fb8d1d316c3db6a8372a))
|
||||
* **editor:** Restore workers view ([#11876](https://github.com/n8n-io/n8n/issues/11876)) ([3aa72f6](https://github.com/n8n-io/n8n/commit/3aa72f613f64c16d7dff67ffe66037894e45aa7c))
|
||||
* **editor:** Turn NPS survey into a modal and make sure it shows above the Ask AI button ([#11814](https://github.com/n8n-io/n8n/issues/11814)) ([ca169f3](https://github.com/n8n-io/n8n/commit/ca169f3f3455fa39ce9120b30d7b409bade6561e))
|
||||
* **editor:** Use `crypto.randomUUID()` to initialize node id if missing on new canvas ([#11873](https://github.com/n8n-io/n8n/issues/11873)) ([bc4857a](https://github.com/n8n-io/n8n/commit/bc4857a1b3d6ea389f11fb8246a1cee33b8a008e))
|
||||
* **n8n Form Node:** Duplicate popup in manual mode ([#11925](https://github.com/n8n-io/n8n/issues/11925)) ([2c34bf4](https://github.com/n8n-io/n8n/commit/2c34bf4ea6137fb0fb321969684ffa621da20fa3))
|
||||
* **n8n Form Node:** Redirect if completion page to trigger ([#11822](https://github.com/n8n-io/n8n/issues/11822)) ([1a8fb7b](https://github.com/n8n-io/n8n/commit/1a8fb7bdc428c6a23c8708e2dcf924f1f10b47a9))
|
||||
* **OpenAI Node:** Remove preview chatInput parameter for `Assistant:Messsage` operation ([#11825](https://github.com/n8n-io/n8n/issues/11825)) ([4dde287](https://github.com/n8n-io/n8n/commit/4dde287cde3af7c9c0e57248e96b8f1270da9332))
|
||||
* Retain execution data between partial executions (new flow) ([#11828](https://github.com/n8n-io/n8n/issues/11828)) ([3320436](https://github.com/n8n-io/n8n/commit/3320436a6fdf8472b3843b9fe8d4de7af7f5ef5c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add SharePoint credentials ([#11570](https://github.com/n8n-io/n8n/issues/11570)) ([05c6109](https://github.com/n8n-io/n8n/commit/05c61091db9bdd62fdcca910ead50d0bd512966a))
|
||||
* Add Zabbix credential only node ([#11489](https://github.com/n8n-io/n8n/issues/11489)) ([fbd1ecf](https://github.com/n8n-io/n8n/commit/fbd1ecfb29461fee393914bc200ec72c654d8944))
|
||||
* **AI Transform Node:** Support for drag and drop ([#11276](https://github.com/n8n-io/n8n/issues/11276)) ([2c252b0](https://github.com/n8n-io/n8n/commit/2c252b0b2d5282f4a87bce76f93c4c02dd8ff5e3))
|
||||
* **editor:** Drop `response` wrapper requirement from Subworkflow Tool output ([#11785](https://github.com/n8n-io/n8n/issues/11785)) ([cd3598a](https://github.com/n8n-io/n8n/commit/cd3598aaab6cefe58a4cb9df7d93fb501415e9d3))
|
||||
* **editor:** Improve node and edge bring-to-front mechanism on new canvas ([#11793](https://github.com/n8n-io/n8n/issues/11793)) ([b89ca9d](https://github.com/n8n-io/n8n/commit/b89ca9d482faa5cb542898f3973fb6e7c9a8437a))
|
||||
* **editor:** Make new canvas connections go underneath node when looping backwards ([#11833](https://github.com/n8n-io/n8n/issues/11833)) ([91d1bd8](https://github.com/n8n-io/n8n/commit/91d1bd8d333454f3971605df73c3703102d2a9e9))
|
||||
* **editor:** Make the left sidebar in Expressions editor draggable ([#11838](https://github.com/n8n-io/n8n/issues/11838)) ([a713b3e](https://github.com/n8n-io/n8n/commit/a713b3ed25feb1790412fc320cf41a0967635263))
|
||||
* **editor:** Migrate existing users to new canvas and set new canvas as default ([#11896](https://github.com/n8n-io/n8n/issues/11896)) ([caa7447](https://github.com/n8n-io/n8n/commit/caa744785a2cc5063a5fb9d269c0ea53ea432298))
|
||||
* **Slack Node:** Update wait for approval to use markdown ([#11754](https://github.com/n8n-io/n8n/issues/11754)) ([40dd02f](https://github.com/n8n-io/n8n/commit/40dd02f360d0d8752fe89c4304c18cac9858c530))
|
||||
|
||||
|
||||
|
||||
# [1.69.0](https://github.com/n8n-io/n8n/compare/n8n@1.68.0...n8n@1.69.0) (2024-11-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add supported versions warning to Zep memory node ([#11803](https://github.com/n8n-io/n8n/issues/11803)) ([9cc5bc1](https://github.com/n8n-io/n8n/commit/9cc5bc1aef974fe6c2511c1597b90c8b54ba6b9c))
|
||||
* **AI Agent Node:** Escape curly brackets in tools description for non Tool agents ([#11772](https://github.com/n8n-io/n8n/issues/11772)) ([83abdfa](https://github.com/n8n-io/n8n/commit/83abdfaf027a0533824a3ac3e4bab3cad971821a))
|
||||
* **Anthropic Chat Model Node:** Update credentials test endpoint ([#11756](https://github.com/n8n-io/n8n/issues/11756)) ([6cf0aba](https://github.com/n8n-io/n8n/commit/6cf0abab5bcddb407571271b9f174e66bb209790))
|
||||
* **core:** Add missing env vars to task runner config ([#11810](https://github.com/n8n-io/n8n/issues/11810)) ([870c576](https://github.com/n8n-io/n8n/commit/870c576ed9d7ce4ef005db9c8bedd78e91084c9c))
|
||||
* **core:** Allow Azure's SAML metadata XML containing WS-Federation nodes to pass validation ([#11724](https://github.com/n8n-io/n8n/issues/11724)) ([3b62bd5](https://github.com/n8n-io/n8n/commit/3b62bd58c264be0225a74ae0eb35c4761c419b79))
|
||||
* **core:** Delete binary data parent folder when pruning executions ([#11790](https://github.com/n8n-io/n8n/issues/11790)) ([17ef2c6](https://github.com/n8n-io/n8n/commit/17ef2c63f69b811bdd28006df3b6edd446837971))
|
||||
* **core:** Fix `diagnostics.enabled` default value ([#11809](https://github.com/n8n-io/n8n/issues/11809)) ([5fa72b0](https://github.com/n8n-io/n8n/commit/5fa72b0512b00bdc6a1065b7b604c9640f469454))
|
||||
* **core:** Improve the security on OAuth callback endpoints ([#11593](https://github.com/n8n-io/n8n/issues/11593)) ([274fcf4](https://github.com/n8n-io/n8n/commit/274fcf45d393d8db1d2fb5ae1e774a4c9198a178))
|
||||
* **core:** Restore old names for pruning config keys ([#11782](https://github.com/n8n-io/n8n/issues/11782)) ([d15b8d0](https://github.com/n8n-io/n8n/commit/d15b8d05092d2ed9dd45fcfa34b4177f60469ebd))
|
||||
* **core:** Unload any existing version of a community nodes package before upgrading it ([#11727](https://github.com/n8n-io/n8n/issues/11727)) ([1d8fd13](https://github.com/n8n-io/n8n/commit/1d8fd13d841b73466ba5f8044d17d7199da7e856))
|
||||
* **editor:** Add documentation link to insufficient quota message ([#11777](https://github.com/n8n-io/n8n/issues/11777)) ([1987363](https://github.com/n8n-io/n8n/commit/1987363f7941285c51fda849a4ac92832368b25a))
|
||||
* **editor:** Add project header subtitle ([#11797](https://github.com/n8n-io/n8n/issues/11797)) ([ff4261c](https://github.com/n8n-io/n8n/commit/ff4261c16845c7de1790fdf0eaa9f57b37822289))
|
||||
* **editor:** Change Home label to Overview ([#11736](https://github.com/n8n-io/n8n/issues/11736)) ([1a78360](https://github.com/n8n-io/n8n/commit/1a783606b4ef22d85e173a2a780d5c49ff208932))
|
||||
* **editor:** Fix executions sorting ([#11808](https://github.com/n8n-io/n8n/issues/11808)) ([cd5ad65](https://github.com/n8n-io/n8n/commit/cd5ad65e90a3be4d67b51521772e0fceb7f4abc7))
|
||||
* **editor:** Fix partial executions not working due to broken push message queue and race conditions ([#11798](https://github.com/n8n-io/n8n/issues/11798)) ([b05d435](https://github.com/n8n-io/n8n/commit/b05d43519994abdd34a65462d14184c779d0b667))
|
||||
* **editor:** Fix reordered switch connections when copying nodes on new canvas ([#11788](https://github.com/n8n-io/n8n/issues/11788)) ([6c2dad7](https://github.com/n8n-io/n8n/commit/6c2dad79143f5b0c255ab8c97c3255314834c458))
|
||||
* **editor:** Fix the issue with RMC Values to Send collection disappears ([#11710](https://github.com/n8n-io/n8n/issues/11710)) ([7bb9002](https://github.com/n8n-io/n8n/commit/7bb9002cbc10cf58550f53a30c6fd7151f8e7355))
|
||||
* **editor:** Improve formatting of expired trial error message ([#11708](https://github.com/n8n-io/n8n/issues/11708)) ([8a0ad0f](https://github.com/n8n-io/n8n/commit/8a0ad0f910feeada6d0c63e81c3e97a1a6e44de7))
|
||||
* **editor:** Optimize application layout ([#11769](https://github.com/n8n-io/n8n/issues/11769)) ([91f9390](https://github.com/n8n-io/n8n/commit/91f9390b90a68d064ea00d10505bf3767ddec1d4))
|
||||
* **Google Sheets Trigger Node:** Fix issue with regex showing correct sheet as invalid ([#11770](https://github.com/n8n-io/n8n/issues/11770)) ([d5ba1a0](https://github.com/n8n-io/n8n/commit/d5ba1a059b7a67154f17f8ad3fcfe66c5c031059))
|
||||
* **HTTP Request Node:** Continue using error ([#11733](https://github.com/n8n-io/n8n/issues/11733)) ([d1bae1a](https://github.com/n8n-io/n8n/commit/d1bae1ace062dd5b64087e0313e78599b5994355))
|
||||
* **n8n Form Node:** Support expressions in completion page ([#11781](https://github.com/n8n-io/n8n/issues/11781)) ([1099167](https://github.com/n8n-io/n8n/commit/10991675fe2e6913e8f03d565b670257941f18e5))
|
||||
* Prevent workflow to run if active and single webhook service ([#11752](https://github.com/n8n-io/n8n/issues/11752)) ([bcb9a20](https://github.com/n8n-io/n8n/commit/bcb9a2078186ff80e03ca3b8532d3585c307d86b))
|
||||
* **Read/Write Files from Disk Node:** Escape parenthesis when reading file ([#11753](https://github.com/n8n-io/n8n/issues/11753)) ([285534e](https://github.com/n8n-io/n8n/commit/285534e6d0ceb60290bd0a928054e494252148fe))
|
||||
* **YouTube Node:** Issue in published before and after dates filters ([#11741](https://github.com/n8n-io/n8n/issues/11741)) ([7381c28](https://github.com/n8n-io/n8n/commit/7381c28af00148b329690021b921267a48a6eaa3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Improve debugging of sub-workflows ([#11602](https://github.com/n8n-io/n8n/issues/11602)) ([fd3254d](https://github.com/n8n-io/n8n/commit/fd3254d5874a03b57421246b77a519787536a93e))
|
||||
* **core:** Improve handling of manual executions with wait nodes ([#11750](https://github.com/n8n-io/n8n/issues/11750)) ([61696c3](https://github.com/n8n-io/n8n/commit/61696c3db313cdc97925af728ff5c68415f9b6b2))
|
||||
* **editor:** Add Info Note to NDV Output Panel if no existing Tools were used during Execution ([#11672](https://github.com/n8n-io/n8n/issues/11672)) ([de0e861](https://github.com/n8n-io/n8n/commit/de0e86150f4d0615481e5ec3869465cfd1ce822f))
|
||||
* **editor:** Add option to create sub workflow from workflows list in `Execute Workflow` node ([#11706](https://github.com/n8n-io/n8n/issues/11706)) ([c265d44](https://github.com/n8n-io/n8n/commit/c265d44841eb147115563ce24c56666b1e321433))
|
||||
* **editor:** Add selection navigation using the keyboard on new canvas ([#11679](https://github.com/n8n-io/n8n/issues/11679)) ([6cd9b99](https://github.com/n8n-io/n8n/commit/6cd9b996af0406caf65941503276524de9e2add4))
|
||||
* **editor:** Add universal Create Resource Menu ([#11564](https://github.com/n8n-io/n8n/issues/11564)) ([b38ce14](https://github.com/n8n-io/n8n/commit/b38ce14ec94d74aa1c9780a0572804ff6266588d))
|
||||
* **Embeddings Azure OpenAI Node, Azure OpenAI Chat Model Node:** Add support for basePath url in Azure Open AI nodes ([#11784](https://github.com/n8n-io/n8n/issues/11784)) ([e298ebe](https://github.com/n8n-io/n8n/commit/e298ebe90d69f466ee897855472eaa7be1d96aba))
|
||||
* **Embeddings OpenAI Node, Embeddings Azure OpenAI Node:** Add dimensions option ([#11773](https://github.com/n8n-io/n8n/issues/11773)) ([de01a8a](https://github.com/n8n-io/n8n/commit/de01a8a01d37f33ab8bcbc65588cafebda969922))
|
||||
* GitHub stars dismiss button ([#11794](https://github.com/n8n-io/n8n/issues/11794)) ([8fbad74](https://github.com/n8n-io/n8n/commit/8fbad74ab685c2ba0395c30cee0ddf9498fb8984))
|
||||
|
||||
|
||||
|
||||
# [1.68.0](https://github.com/n8n-io/n8n/compare/n8n@1.67.0...n8n@1.68.0) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AI Agent Node:** Throw better errors for non-tool agents when using structured tools ([#11582](https://github.com/n8n-io/n8n/issues/11582)) ([9b6123d](https://github.com/n8n-io/n8n/commit/9b6123dfb2648f880c7829211fa07666611ad0ea))
|
||||
* **Auto-fixing Output Parser Node:** Only run retry chain on parsing errors ([#11569](https://github.com/n8n-io/n8n/issues/11569)) ([21b31e4](https://github.com/n8n-io/n8n/commit/21b31e488ff6ab0bcf3c79edcd17b9e37d4c64a4))
|
||||
* **core:** Continue with error output reverse items in success branch ([#11684](https://github.com/n8n-io/n8n/issues/11684)) ([6d5ee83](https://github.com/n8n-io/n8n/commit/6d5ee832966fab96043b0d65697c059ced61d334))
|
||||
* **core:** Ensure task runner server closes websocket connection correctly ([#11633](https://github.com/n8n-io/n8n/issues/11633)) ([b496bf3](https://github.com/n8n-io/n8n/commit/b496bf3147d2cd873d24371be02cb7ea5dbd8621))
|
||||
* **core:** Handle websocket connection error more gracefully in task runners ([#11635](https://github.com/n8n-io/n8n/issues/11635)) ([af7d6e6](https://github.com/n8n-io/n8n/commit/af7d6e68d0436ff8a3d4e8410dc8ee4f3a035c44))
|
||||
* **core:** Improve model sub-nodes error handling ([#11418](https://github.com/n8n-io/n8n/issues/11418)) ([57467d0](https://github.com/n8n-io/n8n/commit/57467d0285d67509322630c4c01130022f274a41))
|
||||
* **core:** Make push work for waiting webhooks ([#11678](https://github.com/n8n-io/n8n/issues/11678)) ([600479b](https://github.com/n8n-io/n8n/commit/600479bf36ba8870d4aecacad19a2dc5f2d97959))
|
||||
* **core:** Revert all the context helpers changes ([#11616](https://github.com/n8n-io/n8n/issues/11616)) ([20fd38f](https://github.com/n8n-io/n8n/commit/20fd38f3517f7ef35604ba16abb4d951270b4d50))
|
||||
* **core:** Set the authentication methad to `email` during startup if the SAML configuration in the database has been corrupted ([#11600](https://github.com/n8n-io/n8n/issues/11600)) ([6439291](https://github.com/n8n-io/n8n/commit/6439291738dec16261979d6d835acbc63743d51a))
|
||||
* **core:** Use cached value in retrieval of personal project owner ([#11533](https://github.com/n8n-io/n8n/issues/11533)) ([04029d8](https://github.com/n8n-io/n8n/commit/04029d82a11b52990890380ba7094055b18e7c1f))
|
||||
* Credentials save button is hidden unless you make changes to the ([#11492](https://github.com/n8n-io/n8n/issues/11492)) ([835fbfe](https://github.com/n8n-io/n8n/commit/835fbfe337dd8dc0d0b0318c7227e174484e1328))
|
||||
* **editor:** Add stickies to node insert position conflict check allowlist ([#11624](https://github.com/n8n-io/n8n/issues/11624)) ([fc39e3c](https://github.com/n8n-io/n8n/commit/fc39e3ca16231c176957e2504d55df6b416874fe))
|
||||
* **editor:** Adjust Scrollbar Width of RunData Header Row ([#11561](https://github.com/n8n-io/n8n/issues/11561)) ([d17d76a](https://github.com/n8n-io/n8n/commit/d17d76a85d5425bc091d29fc84605ffbccbca984))
|
||||
* **editor:** Cap NDV Output View Tab Index to prevent rare edge case ([#11614](https://github.com/n8n-io/n8n/issues/11614)) ([a6c8ee4](https://github.com/n8n-io/n8n/commit/a6c8ee4a82e6055766dc1307f79c774c17bb5f4d))
|
||||
* **editor:** Do not show hover tooltip when autocomplete is active ([#11653](https://github.com/n8n-io/n8n/issues/11653)) ([23caf43](https://github.com/n8n-io/n8n/commit/23caf43e30342a21d45c825f438aa1e6193601d1))
|
||||
* **editor:** Enable pinning main output with error and always allow unpinning ([#11452](https://github.com/n8n-io/n8n/issues/11452)) ([40c8882](https://github.com/n8n-io/n8n/commit/40c88822acdcda6401bd92b9cf89d013c44b8453))
|
||||
* **editor:** Fix collapsing nested items in expression modal schema view ([#11645](https://github.com/n8n-io/n8n/issues/11645)) ([41dea52](https://github.com/n8n-io/n8n/commit/41dea522fbfb1c9acee51f47f384973914454b5f))
|
||||
* **editor:** Fix default workflow settings ([#11632](https://github.com/n8n-io/n8n/issues/11632)) ([658568e](https://github.com/n8n-io/n8n/commit/658568e2700bfd5b61da53f3052403d0098c2d90))
|
||||
* **editor:** Fix duplicate chat trigger ([#11693](https://github.com/n8n-io/n8n/issues/11693)) ([a025848](https://github.com/n8n-io/n8n/commit/a025848ec4be96f74d4de2ab104256b6d89bb837))
|
||||
* **editor:** Fix hiding SQL query output when trying to select ([#11649](https://github.com/n8n-io/n8n/issues/11649)) ([4dbf2f4](https://github.com/n8n-io/n8n/commit/4dbf2f4256111985b367030020f1494b8a8b95af))
|
||||
* **editor:** Fix scrolling in code edit modal ([#11647](https://github.com/n8n-io/n8n/issues/11647)) ([8f695f3](https://github.com/n8n-io/n8n/commit/8f695f3417820e7b9bb04b78972f6abbd61abbe8))
|
||||
* **editor:** Prevent error being thrown in RLC while loading ([#11676](https://github.com/n8n-io/n8n/issues/11676)) ([ca8cb45](https://github.com/n8n-io/n8n/commit/ca8cb455ba59831295c238afb11aeab6ad18428e))
|
||||
* **editor:** Prevent NodeCreator from swallowing AskAssistant enter event ([#11532](https://github.com/n8n-io/n8n/issues/11532)) ([db94f16](https://github.com/n8n-io/n8n/commit/db94f169fcd03983fc78a3b4c5e11543610825bf))
|
||||
* **editor:** Show node executing status shortly before switching to success on new canvas ([#11675](https://github.com/n8n-io/n8n/issues/11675)) ([b0ba24c](https://github.com/n8n-io/n8n/commit/b0ba24cbbc55cebc26e9f62ead7396c4c8fbd062))
|
||||
* **editor:** Show only error title and 'Open errored node' button; hide 'Ask Assistant' in root for sub-node errors ([#11573](https://github.com/n8n-io/n8n/issues/11573)) ([8cba100](https://github.com/n8n-io/n8n/commit/8cba1004888f60ca653ee069501c13b3cadcc561))
|
||||
* **Facebook Lead Ads Trigger Node:** Fix issue with optional fields ([#11692](https://github.com/n8n-io/n8n/issues/11692)) ([70d315b](https://github.com/n8n-io/n8n/commit/70d315b3d5b8f5f3e0f39527bba11e254a52028e))
|
||||
* **Google BigQuery Node:** Add item index to insert error ([#11702](https://github.com/n8n-io/n8n/issues/11702)) ([145d092](https://github.com/n8n-io/n8n/commit/145d0921b217bbd4b625beaacfa14429560bf51b))
|
||||
* **Google Drive Node:** Fix file upload for streams ([#11698](https://github.com/n8n-io/n8n/issues/11698)) ([770230f](https://github.com/n8n-io/n8n/commit/770230fbfe0b9e86527254e201c4602fbced94ff))
|
||||
* **In-Memory Vector Store Node:** Fix displaying execution data of connected embedding nodes ([#11701](https://github.com/n8n-io/n8n/issues/11701)) ([40ade15](https://github.com/n8n-io/n8n/commit/40ade151724f4af28a6ed959fd9363450ea711fd))
|
||||
* **Item List Output Parser Node:** Fix number of items parameter issue ([#11696](https://github.com/n8n-io/n8n/issues/11696)) ([01ebe9d](https://github.com/n8n-io/n8n/commit/01ebe9dd38629afbab954fb489f3ef2bb7ab5b34))
|
||||
* **n8n Form Node:** Find completion page ([#11674](https://github.com/n8n-io/n8n/issues/11674)) ([ed3ad6d](https://github.com/n8n-io/n8n/commit/ed3ad6d684597f7c4b7419dfa81d476e66f10eba))
|
||||
* **n8n Form Node:** Open form page if form trigger has pin data ([#11673](https://github.com/n8n-io/n8n/issues/11673)) ([f0492bd](https://github.com/n8n-io/n8n/commit/f0492bd3bb0d94802a2707fb1cf861313b6ea808))
|
||||
* **n8n Form Node:** Trigger page stack in waiting if error in workflow ([#11671](https://github.com/n8n-io/n8n/issues/11671)) ([94b5873](https://github.com/n8n-io/n8n/commit/94b5873248212a5500f02cf3c0d74df6f9d8fb26))
|
||||
* **n8n Form Trigger Node:** Checkboxes different sizes ([#11677](https://github.com/n8n-io/n8n/issues/11677)) ([c08d23c](https://github.com/n8n-io/n8n/commit/c08d23c00f01bb6fcb3b75f02e0338af375f9b32))
|
||||
* NDV search bugs ([#11613](https://github.com/n8n-io/n8n/issues/11613)) ([c32cf64](https://github.com/n8n-io/n8n/commit/c32cf644a6b8c21558e802449329877845de70b1))
|
||||
* **Notion Node:** Extract page url ([#11643](https://github.com/n8n-io/n8n/issues/11643)) ([cbdd535](https://github.com/n8n-io/n8n/commit/cbdd535fe0cb4e032ea82f008dcf35cc5f2264c2))
|
||||
* **Redis Chat Memory Node:** Respect the SSL flag from the credential ([#11689](https://github.com/n8n-io/n8n/issues/11689)) ([b5cbf75](https://github.com/n8n-io/n8n/commit/b5cbf7566d351d8a8e9972f13ff5867ff1c8d7d0))
|
||||
* **Supabase Node:** Reset query parameters in get many operation ([#11630](https://github.com/n8n-io/n8n/issues/11630)) ([7458229](https://github.com/n8n-io/n8n/commit/74582290c04d2dd32300b1a6c7715862ae837d34))
|
||||
* **Switch Node:** Maintain output connections ([#11162](https://github.com/n8n-io/n8n/issues/11162)) ([9bd79fc](https://github.com/n8n-io/n8n/commit/9bd79fceebc4453d0fe40ae5f628d5e31ff2b326))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **AI Transform Node:** Show warning for binary data ([#11560](https://github.com/n8n-io/n8n/issues/11560)) ([ddbb263](https://github.com/n8n-io/n8n/commit/ddbb263dce0fc458abc95d850217251bb49d2b83))
|
||||
* **core:** Make all http requests made with `httpRequestWithAuthentication` abortable ([#11704](https://github.com/n8n-io/n8n/issues/11704)) ([0d8aada](https://github.com/n8n-io/n8n/commit/0d8aada49005d6a6078e8460003a0de61a8f423c))
|
||||
* **editor:** Improve how we show default Agent prompt and Memory session parameters ([#11491](https://github.com/n8n-io/n8n/issues/11491)) ([565f8cd](https://github.com/n8n-io/n8n/commit/565f8cd8c78b534a50e272997d659d162fa86625))
|
||||
* **editor:** Improve workflow loading performance on new canvas ([#11629](https://github.com/n8n-io/n8n/issues/11629)) ([f1e2df7](https://github.com/n8n-io/n8n/commit/f1e2df7d0753aa0f33cf299100a063bf89cc8b35))
|
||||
* **editor:** Redesign Canvas Chat ([#11634](https://github.com/n8n-io/n8n/issues/11634)) ([a412ab7](https://github.com/n8n-io/n8n/commit/a412ab7ebfcd6aa9051a8ca36e34f1067102c998))
|
||||
* **editor:** Restrict when a ChatTrigger Node is added automatically ([#11523](https://github.com/n8n-io/n8n/issues/11523)) ([93a6f85](https://github.com/n8n-io/n8n/commit/93a6f858fa3eb53f8b48b2a3d6b7377279dd6ed1))
|
||||
* Github star button in-app ([#11695](https://github.com/n8n-io/n8n/issues/11695)) ([0fd684d](https://github.com/n8n-io/n8n/commit/0fd684d90c830f8b0aab12b7f78a1fa5619c62c9))
|
||||
* **Oura Node:** Update node for v2 api ([#11604](https://github.com/n8n-io/n8n/issues/11604)) ([3348fbb](https://github.com/n8n-io/n8n/commit/3348fbb1547c430ff8707b298640e3461d3f6536))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **editor:** Add lint rules for optimization-friendly syntax ([#11681](https://github.com/n8n-io/n8n/issues/11681)) ([88295c7](https://github.com/n8n-io/n8n/commit/88295c70495ae3d017674d5745972a346fcbaf12))
|
||||
|
||||
|
||||
|
||||
# [1.67.0](https://github.com/n8n-io/n8n/compare/n8n@1.66.0...n8n@1.67.0) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Bring back nodes panel telemetry events ([#11456](https://github.com/n8n-io/n8n/issues/11456)) ([130c942](https://github.com/n8n-io/n8n/commit/130c942f633788d1b2f937d6fea342d4450c6e3d))
|
||||
* **core:** Account for double quotes in instance base URL ([#11495](https://github.com/n8n-io/n8n/issues/11495)) ([c5191e6](https://github.com/n8n-io/n8n/commit/c5191e697a9a9ebfa2b67587cd01b5835ebf6ea8))
|
||||
* **core:** Do not delete waiting executions when saving of successful executions is disabled ([#11458](https://github.com/n8n-io/n8n/issues/11458)) ([e8757e5](https://github.com/n8n-io/n8n/commit/e8757e58f69e091ac3d2a2f8e8c8e33ac57c1e47))
|
||||
* **core:** Don't send a `executionFinished` event to the browser with no run data if the execution has already been cleaned up ([#11502](https://github.com/n8n-io/n8n/issues/11502)) ([d1153f5](https://github.com/n8n-io/n8n/commit/d1153f51e80911cbc8f34ba5f038f349b75295c3))
|
||||
* **core:** Include `projectId` in range query middleware ([#11590](https://github.com/n8n-io/n8n/issues/11590)) ([a6070af](https://github.com/n8n-io/n8n/commit/a6070afdda29631fd36e5213f52bf815268bcda4))
|
||||
* **core:** Save exeution progress for waiting executions, even when progress saving is disabled ([#11535](https://github.com/n8n-io/n8n/issues/11535)) ([6b9353c](https://github.com/n8n-io/n8n/commit/6b9353c80f61ab36945fff434d98242dc1cab7b3))
|
||||
* **core:** Use the correct docs URL for regular nodes when used as tools ([#11529](https://github.com/n8n-io/n8n/issues/11529)) ([a092b8e](https://github.com/n8n-io/n8n/commit/a092b8e972e1253d92df416f19096a045858e7c1))
|
||||
* **Edit Image Node:** Fix Text operation by setting Arial as default font ([#11125](https://github.com/n8n-io/n8n/issues/11125)) ([60c1ace](https://github.com/n8n-io/n8n/commit/60c1ace64be29d651ce7b777fbb576598e38b9d7))
|
||||
* **editor:** Auto focus first fields on SignIn, SignUp and ForgotMyPassword views ([#11445](https://github.com/n8n-io/n8n/issues/11445)) ([5b5bd72](https://github.com/n8n-io/n8n/commit/5b5bd7291dde17880b7699f7e6832938599ffd8f))
|
||||
* **editor:** Do not overwrite the webhookId in the new canvas ([#11562](https://github.com/n8n-io/n8n/issues/11562)) ([dfd785b](https://github.com/n8n-io/n8n/commit/dfd785bc0894257eb6e62b0dd8f71248c27aae53))
|
||||
* **editor:** Ensure Enter key on Cancel button correctly cancels node rename ([#11563](https://github.com/n8n-io/n8n/issues/11563)) ([be05ae3](https://github.com/n8n-io/n8n/commit/be05ae36e7790156cb48b317fc254ae46a3b2d8c))
|
||||
* **editor:** Fix emitting `n8nReady` notification via `postmessage` on new canvas ([#11558](https://github.com/n8n-io/n8n/issues/11558)) ([463d101](https://github.com/n8n-io/n8n/commit/463d101f3592e6df4afd66c4d0fde0cb4aec34cc))
|
||||
* **editor:** Fix run index input for RunData view in sub-nodes ([#11538](https://github.com/n8n-io/n8n/issues/11538)) ([434d31c](https://github.com/n8n-io/n8n/commit/434d31ce928342d52b6ab8b78639afd7829216d4))
|
||||
* **editor:** Fix selected credential being overwritten in NDV ([#11496](https://github.com/n8n-io/n8n/issues/11496)) ([a26c0e2](https://github.com/n8n-io/n8n/commit/a26c0e2c3c7da87bfaba9737a967aa0070810d85))
|
||||
* **editor:** Keep workflow pristine after load on new canvas ([#11579](https://github.com/n8n-io/n8n/issues/11579)) ([7254359](https://github.com/n8n-io/n8n/commit/7254359855b89769613cd5cc24dbb4f45a7cc76f))
|
||||
* Show Pinned data in demo mode ([#11490](https://github.com/n8n-io/n8n/issues/11490)) ([ca2a583](https://github.com/n8n-io/n8n/commit/ca2a583b5cbb0cba3ecb694261806de16547aa91))
|
||||
* Toast not aligned to the bottom when AI assistant disable ([#11549](https://github.com/n8n-io/n8n/issues/11549)) ([e80f7e0](https://github.com/n8n-io/n8n/commit/e80f7e0a02a972379f73af6a44de11768081086e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add Rapid7 InsightVm credentials ([#11462](https://github.com/n8n-io/n8n/issues/11462)) ([46eceab](https://github.com/n8n-io/n8n/commit/46eceabc27ac219b11b85c16c533a2cff848c5dd))
|
||||
* **AI Transform Node:** UX improvements ([#11280](https://github.com/n8n-io/n8n/issues/11280)) ([8a48407](https://github.com/n8n-io/n8n/commit/8a484077af3d3e1fe2d1b90b1ea9edf4ba41fcb6))
|
||||
* **Anthropic Chat Model Node:** Add support for Haiku 3.5 ([#11551](https://github.com/n8n-io/n8n/issues/11551)) ([8b39825](https://github.com/n8n-io/n8n/commit/8b398256a81594a52f20f8eb8adf8ff205209bc1))
|
||||
* **Convert to File Node:** Add delimiter convert to csv ([#11556](https://github.com/n8n-io/n8n/issues/11556)) ([63d454b](https://github.com/n8n-io/n8n/commit/63d454b776c092ff8c6c521a7e083774adb8f649))
|
||||
* **editor:** Update panning and selection keybindings on new canvas ([#11534](https://github.com/n8n-io/n8n/issues/11534)) ([5e2e205](https://github.com/n8n-io/n8n/commit/5e2e205394adf76faf02aee2d4f21df71848e1d4))
|
||||
* **Gmail Trigger Node:** Add filter option to include drafts ([#11441](https://github.com/n8n-io/n8n/issues/11441)) ([7a2be77](https://github.com/n8n-io/n8n/commit/7a2be77f384a32ede3acad8fe24fb89227c058bf))
|
||||
* **Intercom Node:** Update credential to new style ([#11485](https://github.com/n8n-io/n8n/issues/11485)) ([b137e13](https://github.com/n8n-io/n8n/commit/b137e13845f0714ebf7421c837f5ab104b66709b))
|
||||
|
||||
|
||||
|
||||
# [1.66.0](https://github.com/n8n-io/n8n/compare/n8n@1.65.0...n8n@1.66.0) (2024-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Asana Node:** Fix issue with pagination ([#11415](https://github.com/n8n-io/n8n/issues/11415)) ([04c075a](https://github.com/n8n-io/n8n/commit/04c075a46bcc7b1964397f0244b0fde99476212d))
|
||||
* **core:** Add 'user_id' to `license-community-plus-registered` telemetry event ([#11430](https://github.com/n8n-io/n8n/issues/11430)) ([7a8dafe](https://github.com/n8n-io/n8n/commit/7a8dafe9902fbc0d5001c50579c34959b95211ab))
|
||||
* **core:** Add safeguard for command publishing ([#11337](https://github.com/n8n-io/n8n/issues/11337)) ([656439e](https://github.com/n8n-io/n8n/commit/656439e87138f9f96dea5a683cfdac3f661ffefb))
|
||||
* **core:** Ensure `LoggerProxy` is not scoped ([#11379](https://github.com/n8n-io/n8n/issues/11379)) ([f4ea943](https://github.com/n8n-io/n8n/commit/f4ea943c9cb2321e41705de6c5c27535a0f5eae0))
|
||||
* **core:** Ensure `remove-triggers-and-pollers` command is not debounced ([#11486](https://github.com/n8n-io/n8n/issues/11486)) ([529d4fc](https://github.com/n8n-io/n8n/commit/529d4fc3ef5206bd1b02d27634342cc50b45997e))
|
||||
* **core:** Ensure job processor does not reprocess amended executions ([#11438](https://github.com/n8n-io/n8n/issues/11438)) ([c152a3a](https://github.com/n8n-io/n8n/commit/c152a3ac56f140a39eea4771a94f5a3082118df7))
|
||||
* **core:** Fix Message Event Bus Metrics not counting up for labeled metrics ([#11396](https://github.com/n8n-io/n8n/issues/11396)) ([7fc3b25](https://github.com/n8n-io/n8n/commit/7fc3b25d21c6c4f1802f34b1ae065a65cac3001b))
|
||||
* **core:** Fix resolving of $fromAI expression via `evaluateExpression` ([#11397](https://github.com/n8n-io/n8n/issues/11397)) ([2e64464](https://github.com/n8n-io/n8n/commit/2e6446454defbd3e5a47b66e6fd46d4f1b9fbd0f))
|
||||
* **core:** Make execution and its data creation atomic ([#11392](https://github.com/n8n-io/n8n/issues/11392)) ([ed30d43](https://github.com/n8n-io/n8n/commit/ed30d43236bf3c6b657022636a02a41be01aa152))
|
||||
* **core:** On unhandled rejections, extract the original exception correctly ([#11389](https://github.com/n8n-io/n8n/issues/11389)) ([8608bae](https://github.com/n8n-io/n8n/commit/8608baeb7ec302daddc8adca6e39778dcf7b2eda))
|
||||
* **editor:** Fix TypeError: Cannot read properties of undefined (reading '0') ([#11399](https://github.com/n8n-io/n8n/issues/11399)) ([ae37c52](https://github.com/n8n-io/n8n/commit/ae37c520a91c75e353e818944b36a3619c0d8b4a))
|
||||
* **editor:** Add Retry button for AI Assistant errors ([#11345](https://github.com/n8n-io/n8n/issues/11345)) ([7699240](https://github.com/n8n-io/n8n/commit/7699240073122cdef31cf109fd37fa66961f588a))
|
||||
* **editor:** Change tooltip for workflow with execute workflow trigger ([#11374](https://github.com/n8n-io/n8n/issues/11374)) ([dcd6038](https://github.com/n8n-io/n8n/commit/dcd6038c3085135803cdaa546a239359a6d449eb))
|
||||
* **editor:** Ensure toasts show above modal overlays ([#11410](https://github.com/n8n-io/n8n/issues/11410)) ([351134f](https://github.com/n8n-io/n8n/commit/351134f786af933f5f310bf8d9897269387635a0))
|
||||
* **editor:** Fit view consistently after nodes are initialized on new canvas ([#11457](https://github.com/n8n-io/n8n/issues/11457)) ([497d637](https://github.com/n8n-io/n8n/commit/497d637fc5308b9c4a06bc764152fde1f1a9c130))
|
||||
* **editor:** Fix adding connections when initializing workspace in templates view on new canvas ([#11451](https://github.com/n8n-io/n8n/issues/11451)) ([ea47b02](https://github.com/n8n-io/n8n/commit/ea47b025fb16c967d4fc73dcacc6e260d2aecd61))
|
||||
* **editor:** Fix rendering of AI logs ([#11450](https://github.com/n8n-io/n8n/issues/11450)) ([73b0a80](https://github.com/n8n-io/n8n/commit/73b0a80ac92b4f4b5a300d0ec1c833b4395a222a))
|
||||
* **editor:** Hide data mapping tooltip in credential edit modal ([#11356](https://github.com/n8n-io/n8n/issues/11356)) ([ff14dcb](https://github.com/n8n-io/n8n/commit/ff14dcb3a1ddaea4eca7c1ecd2e92c0abb0c413c))
|
||||
* **editor:** Prevent running workflow that has issues if listening to webhook ([#11402](https://github.com/n8n-io/n8n/issues/11402)) ([8b0a48f](https://github.com/n8n-io/n8n/commit/8b0a48f53010378e497e4cc362fda75a958cf363))
|
||||
* **editor:** Run external hooks after settings have been initialized ([#11423](https://github.com/n8n-io/n8n/issues/11423)) ([0ab24c8](https://github.com/n8n-io/n8n/commit/0ab24c814abd1787268750ba808993ab2735ac52))
|
||||
* **editor:** Support middle click to scroll when using a mouse on new canvas ([#11384](https://github.com/n8n-io/n8n/issues/11384)) ([46f3b4a](https://github.com/n8n-io/n8n/commit/46f3b4a258f89f02e0d2bd1eef25a22e3a721167))
|
||||
* **HTTP Request Tool Node:** Fix HTML response optimization issue ([#11439](https://github.com/n8n-io/n8n/issues/11439)) ([cf37e94](https://github.com/n8n-io/n8n/commit/cf37e94dd875e9f6ab1f189146fb34e7296af93c))
|
||||
* **n8n Form Node:** Form Trigger does not wait in multi-form mode ([#11404](https://github.com/n8n-io/n8n/issues/11404)) ([151f4dd](https://github.com/n8n-io/n8n/commit/151f4dd7b8f800af424f8ae64cb8238975fb3cb8))
|
||||
* Update required node js version in CONTRIBUTING.md ([#11437](https://github.com/n8n-io/n8n/issues/11437)) ([4f511aa](https://github.com/n8n-io/n8n/commit/4f511aab68651caa8fe47f70cd7cdb88bb06a3e2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Anthropic Chat Model Node:** Add model claude-3-5-sonnet-20241022 ([#11465](https://github.com/n8n-io/n8n/issues/11465)) ([f6c8890](https://github.com/n8n-io/n8n/commit/f6c8890a8069de221b9b96e735418ecc9624cf7b))
|
||||
* **core:** Handle nodes with multiple inputs and connections during partial executions ([#11376](https://github.com/n8n-io/n8n/issues/11376)) ([cb7c4d2](https://github.com/n8n-io/n8n/commit/cb7c4d29a6f042b590822e5b9c67fff0a8f0863d))
|
||||
* **editor:** Add descriptive header to projects /workflow ([#11203](https://github.com/n8n-io/n8n/issues/11203)) ([5d19e8f](https://github.com/n8n-io/n8n/commit/5d19e8f2b45dc1abc5a8253f9e3a0fdacb1ebd91))
|
||||
* **editor:** Improve placeholder for vector store tool ([#11483](https://github.com/n8n-io/n8n/issues/11483)) ([629e092](https://github.com/n8n-io/n8n/commit/629e09240785bc648ff6575f97910fbb4e77cdab))
|
||||
* **editor:** Remove edge execution animation on new canvas ([#11446](https://github.com/n8n-io/n8n/issues/11446)) ([a701d87](https://github.com/n8n-io/n8n/commit/a701d87f5ba94ffc811e424b60e188b26ac6c1c5))
|
||||
* **editor:** Update ownership pills ([#11155](https://github.com/n8n-io/n8n/issues/11155)) ([8147038](https://github.com/n8n-io/n8n/commit/8147038cf87dca657602e617e49698065bf1a63f))
|
||||
|
||||
|
||||
|
||||
# [1.65.0](https://github.com/n8n-io/n8n/compare/n8n@1.64.0...n8n@1.65.0) (2024-10-24)
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ Great that you are here and you want to contribute to n8n
|
|||
- [Actual n8n setup](#actual-n8n-setup)
|
||||
- [Start](#start)
|
||||
- [Development cycle](#development-cycle)
|
||||
- [Community PR Guidelines](#community-pr-guidelines)
|
||||
- [Test suite](#test-suite)
|
||||
- [Unit tests](#unit-tests)
|
||||
- [E2E tests](#e2e-tests)
|
||||
|
@ -68,7 +69,7 @@ If you already have VS Code and Docker installed, you can click [here](https://v
|
|||
|
||||
#### Node.js
|
||||
|
||||
[Node.js](https://nodejs.org/en/) version 18.10 or newer is required for development purposes.
|
||||
[Node.js](https://nodejs.org/en/) version 20.15 or newer is required for development purposes.
|
||||
|
||||
#### pnpm
|
||||
|
||||
|
@ -191,6 +192,51 @@ automatically build your code, restart the backend and refresh the frontend
|
|||
```
|
||||
1. Commit code and [create a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
|
||||
|
||||
---
|
||||
|
||||
### Community PR Guidelines
|
||||
|
||||
#### **1. Change Request/Comment**
|
||||
|
||||
Please address the requested changes or provide feedback within 14 days. If there is no response or updates to the pull request during this time, it will be automatically closed. The PR can be reopened once the requested changes are applied.
|
||||
|
||||
#### **2. General Requirements**
|
||||
|
||||
- **Follow the Style Guide:**
|
||||
- Ensure your code adheres to n8n's coding standards and conventions (e.g., formatting, naming, indentation). Use linting tools where applicable.
|
||||
- **TypeScript Compliance:**
|
||||
- Do not use `ts-ignore` .
|
||||
- Ensure code adheres to TypeScript rules.
|
||||
- **Avoid Repetitive Code:**
|
||||
- Reuse existing components, parameters, and logic wherever possible instead of redefining or duplicating them.
|
||||
- For nodes: Use the same parameter across multiple operations rather than defining a new parameter for each operation (if applicable).
|
||||
- **Testing Requirements:**
|
||||
- PRs **must include tests**:
|
||||
- Unit tests
|
||||
- Workflow tests for nodes (example [here](https://github.com/n8n-io/n8n/tree/master/packages/nodes-base/nodes/Switch/V3/test))
|
||||
- UI tests (if applicable)
|
||||
- **Typos:**
|
||||
- Use a spell-checking tool, such as [**Code Spell Checker**](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker), to avoid typos.
|
||||
|
||||
#### **3. PR Specific Requirements**
|
||||
|
||||
- **Small PRs Only:**
|
||||
- Focus on a single feature or fix per PR.
|
||||
- **Naming Convention:**
|
||||
- Follow [n8n's PR Title Conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md#L36).
|
||||
- **New Nodes:**
|
||||
- PRs that introduce new nodes will be **auto-closed** unless they are explicitly requested by the n8n team and aligned with an agreed project scope. However, you can still explore [building your own nodes](https://docs.n8n.io/integrations/creating-nodes/) , as n8n offers the flexibility to create your own custom nodes.
|
||||
- **Typo-Only PRs:**
|
||||
- Typos are not sufficient justification for a PR and will be rejected.
|
||||
|
||||
#### **4. Workflow Summary for Non-Compliant PRs**
|
||||
|
||||
- **No Tests:** If tests are not provided, the PR will be auto-closed after **14 days**.
|
||||
- **Non-Small PRs:** Large or multifaceted PRs will be returned for segmentation.
|
||||
- **New Nodes/Typo PRs:** Automatically rejected if not aligned with project scope or guidelines.
|
||||
|
||||
---
|
||||
|
||||
### Test suite
|
||||
|
||||
#### Unit tests
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
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".
|
||||
- Source code files that contain ".ee." in their filename or ".ee" in their dirname are NOT licensed under
|
||||
the Sustainable Use License.
|
||||
To use source code files that contain ".ee." in their filename or ".ee" in their dirname 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
|
||||
|
|
134
README.md
134
README.md
|
@ -1,104 +1,72 @@
|
|||

|
||||

|
||||
|
||||
# n8n - Workflow automation tool
|
||||
# n8n - Secure Workflow Automation for Technical Teams
|
||||
|
||||
n8n is an extendable workflow automation tool. With a [fair-code](https://faircode.io) distribution model, n8n
|
||||
will always have visible source code, be available to self-host, and allow you to add your own custom
|
||||
functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect
|
||||
anything to everything.
|
||||
n8n is a workflow automation platform that gives technical teams the flexibility of code with the speed of no-code. With 400+ integrations, native AI capabilities, and a fair-code license, n8n lets you build powerful automations while maintaining full control over your data and deployments.
|
||||
|
||||

|
||||

|
||||
|
||||
## Demo
|
||||
## Key Capabilities
|
||||
|
||||
[:tv: A short video (< 5 min)](https://www.youtube.com/watch?v=1MwSoB0gnM4) that goes over key concepts of
|
||||
creating workflows in n8n.
|
||||
- **Code When You Need It**: Write JavaScript/Python, add npm packages, or use the visual interface
|
||||
- **AI-Native Platform**: Build AI agent workflows based on LangChain with your own data and models
|
||||
- **Full Control**: Self-host with our fair-code license or use our [cloud offering](https://app.n8n.cloud/login)
|
||||
- **Enterprise-Ready**: Advanced permissions, SSO, and air-gapped deployments
|
||||
- **Active Community**: 400+ integrations and 900+ ready-to-use [templates](https://n8n.io/workflows)
|
||||
|
||||
## Available integrations
|
||||
## Quick Start
|
||||
|
||||
n8n has 200+ different nodes to automate workflows. The list can be found on:
|
||||
[https://n8n.io/integrations](https://n8n.io/integrations)
|
||||
Try n8n instantly with [npx](https://docs.n8n.io/hosting/installation/npm/) (requires [Node.js](https://nodejs.org/en/)):
|
||||
|
||||
## Documentation
|
||||
```
|
||||
npx n8n
|
||||
```
|
||||
|
||||
The official n8n documentation can be found on our [documentation website](https://docs.n8n.io)
|
||||
Or deploy with [Docker](https://docs.n8n.io/hosting/installation/docker/):
|
||||
|
||||
Additional information and example workflows on the [n8n.io website](https://n8n.io)
|
||||
```
|
||||
docker volume create n8n_data
|
||||
docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n
|
||||
```
|
||||
|
||||
The release notes can be found [here](https://docs.n8n.io/release-notes/) and the list of breaking
|
||||
changes [here](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md).
|
||||
Access the editor at http://localhost:5678
|
||||
|
||||
## Usage
|
||||
## Resources
|
||||
|
||||
- :books: Learn
|
||||
[how to **use** it from the command line](https://docs.n8n.io/reference/cli-commands/)
|
||||
- :whale: Learn
|
||||
[how to run n8n in **Docker**](https://docs.n8n.io/hosting/installation/docker/)
|
||||
|
||||
## Start
|
||||
|
||||
You can try n8n without installing it using npx. You must have [Node.js](https://nodejs.org/en/) installed.
|
||||
From the terminal, run:
|
||||
|
||||
`npx n8n`
|
||||
|
||||
This command will download everything that is needed to start n8n. You can then access n8n and start building workflows by opening [http://localhost:5678](http://localhost:5678).
|
||||
|
||||
## n8n cloud
|
||||
|
||||
Sign-up for an [n8n cloud](https://www.n8n.io/cloud/) account.
|
||||
|
||||
While n8n cloud and n8n are the same in terms of features, n8n cloud provides certain conveniences such as:
|
||||
|
||||
- Not having to set up and maintain your n8n instance
|
||||
- Managed OAuth for authentication
|
||||
- Easily upgrading to the newer n8n versions
|
||||
|
||||
## Build with LangChain and AI in n8n (beta)
|
||||
|
||||
With n8n's LangChain nodes you can build AI-powered functionality within your workflows. The LangChain nodes are configurable, meaning you can choose your preferred agent, LLM, memory, and so on. Alongside the LangChain nodes, you can connect any n8n node as normal: this means you can integrate your LangChain logic with other data sources and services.
|
||||
|
||||
Learn more in the [documentation](https://docs.n8n.io/langchain/).
|
||||
|
||||
- [LangChain nodes package](https://www.npmjs.com/package/@n8n/n8n-nodes-langchain)
|
||||
- [Chatbot package](https://www.npmjs.com/package/@n8n/chat)
|
||||
- 📚 [Documentation](https://docs.n8n.io)
|
||||
- 🔧 [400+ Integrations](https://n8n.io/integrations)
|
||||
- 💡 [Example Workflows](https://n8n.io/workflows)
|
||||
- 🤖 [AI & LangChain Guide](https://docs.n8n.io/langchain/)
|
||||
- 👥 [Community Forum](https://community.n8n.io)
|
||||
- 📖 [Community Tutorials](https://community.n8n.io/c/tutorials/28)
|
||||
|
||||
## Support
|
||||
|
||||
If you have problems or questions go to our forum, we will then try to help you asap:
|
||||
|
||||
[https://community.n8n.io](https://community.n8n.io)
|
||||
|
||||
## Jobs
|
||||
|
||||
If you are interested in working for n8n and so shape the future of the project check out our
|
||||
[job posts](https://apply.workable.com/n8n/)
|
||||
|
||||
## What does n8n mean and how do you pronounce it?
|
||||
|
||||
**Short answer:** It means "nodemation" and it is pronounced as n-eight-n.
|
||||
|
||||
**Long answer:** "I get that question quite often (more often than I expected) so I decided it is probably
|
||||
best to answer it here. While looking for a good name for the project with a free domain I realized very
|
||||
quickly that all the good ones I could think of were already taken. So, in the end, I chose nodemation.
|
||||
'node-' in the sense that it uses a Node-View and that it uses Node.js and '-mation' for 'automation' which is
|
||||
what the project is supposed to help with. However, I did not like how long the name was and I could not
|
||||
imagine writing something that long every time in the CLI. That is when I then ended up on 'n8n'." - **Jan
|
||||
Oberhauser, Founder and CEO, n8n.io**
|
||||
|
||||
## Development setup
|
||||
|
||||
Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to contribute ? The
|
||||
[CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you get your
|
||||
development environment ready in minutes.
|
||||
Need help? Our community forum is the place to get support and connect with other users:
|
||||
[community.n8n.io](https://community.n8n.io)
|
||||
|
||||
## License
|
||||
|
||||
n8n is [fair-code](https://faircode.io) distributed under the
|
||||
[**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).
|
||||
n8n is [fair-code](https://faircode.io) distributed under the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) and [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)
|
||||
- **Source Available**: Always visible source code
|
||||
- **Self-Hostable**: Deploy anywhere
|
||||
- **Extensible**: Add your own nodes and functionality
|
||||
|
||||
Additional information about the license model can be found in the
|
||||
[docs](https://docs.n8n.io/reference/license/).
|
||||
[Enterprise licenses](mailto:license@n8n.io) available for additional features and support.
|
||||
|
||||
Additional information about the license model can be found in the [docs](https://docs.n8n.io/reference/license/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Found a bug 🐛 or have a feature idea ✨? Check our [Contributing Guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) to get started.
|
||||
|
||||
## Join the Team
|
||||
|
||||
Want to shape the future of automation? Check out our [job posts](https://n8n.io/careers) and join our team!
|
||||
|
||||
## What does n8n mean?
|
||||
|
||||
**Short answer:** It means "nodemation" and is pronounced as n-eight-n.
|
||||
|
||||
**Long answer:** "I get that question quite often (more often than I expected) so I decided it is probably best to answer it here. While looking for a good name for the project with a free domain I realized very quickly that all the good ones I could think of were already taken. So, in the end, I chose nodemation. 'node-' in the sense that it uses a Node-View and that it uses Node.js and '-mation' for 'automation' which is what the project is supposed to help with. However, I did not like how long the name was and I could not imagine writing something that long every time in the CLI. That is when I then ended up on 'n8n'." - **Jan Oberhauser, Founder and CEO, n8n.io**
|
||||
|
|
BIN
assets/n8n-screenshot-readme.png
Normal file
BIN
assets/n8n-screenshot-readme.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
60
codecov.yml
Normal file
60
codecov.yml
Normal file
|
@ -0,0 +1,60 @@
|
|||
codecov:
|
||||
max_report_age: off
|
||||
require_ci_to_pass: true
|
||||
|
||||
coverage:
|
||||
status:
|
||||
patch: false
|
||||
project:
|
||||
default:
|
||||
threshold: 0.5%
|
||||
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
||||
flags:
|
||||
tests:
|
||||
paths:
|
||||
- "**"
|
||||
carryforward: true
|
||||
|
||||
component_management:
|
||||
default_rules:
|
||||
statuses:
|
||||
- type: project
|
||||
target: auto
|
||||
branches:
|
||||
- "!master"
|
||||
individual_components:
|
||||
- component_id: backend_packages
|
||||
name: Backend
|
||||
paths:
|
||||
- packages/@n8n/api-types/**
|
||||
- packages/@n8n/config/**
|
||||
- packages/@n8n/client-oauth2/**
|
||||
- packages/@n8n/di/**
|
||||
- packages/@n8n/imap/**
|
||||
- packages/@n8n/permissions/**
|
||||
- packages/@n8n/task-runner/**
|
||||
- packages/workflow/**
|
||||
- packages/core/**
|
||||
- packages/cli/**
|
||||
- component_id: frontend_packages
|
||||
name: Frontend
|
||||
paths:
|
||||
- packages/@n8n/chat/**
|
||||
- packages/@n8n/codemirror-lang/**
|
||||
- packages/design-system/**
|
||||
- packages/editor-ui/**
|
||||
- component_id: nodes_packages
|
||||
name: Nodes
|
||||
paths:
|
||||
- packages/node-dev/**
|
||||
- packages/nodes-base/**
|
||||
- packages/@n8n/json-schema-to-zod/**
|
||||
- packages/@n8n/nodes-langchain/**
|
||||
|
||||
ignore:
|
||||
- (?s:.*/[^\/]*\.spec\.ts.*)\Z
|
||||
- (?s:.*/[^\/]*\.test\.ts.*)\Z
|
||||
- (?s:.*/[^\/]*e2e[^\/]*\.ts.*)\Z
|
29
cypress/composables/executions.ts
Normal file
29
cypress/composables/executions.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Getters
|
||||
*/
|
||||
|
||||
export const getExecutionsSidebar = () => cy.getByTestId('executions-sidebar');
|
||||
|
||||
export const getWorkflowExecutionPreviewIframe = () => cy.getByTestId('workflow-preview-iframe');
|
||||
|
||||
export const getExecutionPreviewBody = () =>
|
||||
getWorkflowExecutionPreviewIframe()
|
||||
.its('0.contentDocument.body')
|
||||
.then((el) => cy.wrap(el));
|
||||
|
||||
export const getExecutionPreviewBodyNodes = () =>
|
||||
getExecutionPreviewBody().findChildByTestId('canvas-node');
|
||||
|
||||
export const getExecutionPreviewBodyNodesByName = (name: string) =>
|
||||
getExecutionPreviewBody().findChildByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0);
|
||||
|
||||
export function getExecutionPreviewOutputPanelRelatedExecutionLink() {
|
||||
return getExecutionPreviewBody().findChildByTestId('related-execution-link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
||||
export const openExecutionPreviewNode = (name: string) =>
|
||||
getExecutionPreviewBodyNodesByName(name).dblclick();
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
export function getManualChatModal() {
|
||||
return cy.getByTestId('lmChat-modal');
|
||||
return cy.getByTestId('canvas-chat');
|
||||
}
|
||||
|
||||
export function getManualChatInput() {
|
||||
|
@ -19,11 +19,11 @@ export function getManualChatMessages() {
|
|||
}
|
||||
|
||||
export function getManualChatModalCloseButton() {
|
||||
return getManualChatModal().get('.el-dialog__close');
|
||||
return cy.getByTestId('workflow-chat-button');
|
||||
}
|
||||
|
||||
export function getManualChatModalLogs() {
|
||||
return getManualChatModal().getByTestId('lm-chat-logs');
|
||||
return cy.getByTestId('canvas-chat-logs');
|
||||
}
|
||||
export function getManualChatDialog() {
|
||||
return getManualChatModal().getByTestId('workflow-lm-chat-dialog');
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
* Getters
|
||||
*/
|
||||
|
||||
import { clearNotifications } from '../../pages/notifications';
|
||||
|
||||
export function getCredentialConnectionParameterInputs() {
|
||||
return cy.getByTestId('credential-connection-parameter');
|
||||
}
|
||||
|
@ -35,7 +37,12 @@ export function setCredentialConnectionParameterInputByName(name: string, value:
|
|||
}
|
||||
|
||||
export function saveCredential() {
|
||||
getCredentialSaveButton().click({ force: true });
|
||||
getCredentialSaveButton()
|
||||
.click({ force: true })
|
||||
.within(() => {
|
||||
cy.get('button').should('not.exist');
|
||||
});
|
||||
getCredentialSaveButton().should('have.text', 'Saved');
|
||||
}
|
||||
|
||||
export function closeCredentialModal() {
|
||||
|
@ -50,5 +57,6 @@ export function setCredentialValues(values: Record<string, string>, save = true)
|
|||
if (save) {
|
||||
saveCredential();
|
||||
closeCredentialModal();
|
||||
clearNotifications();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Getters
|
||||
*/
|
||||
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils/popper';
|
||||
|
||||
export function getCredentialSelect(eq = 0) {
|
||||
return cy.getByTestId('node-credentials-select').eq(eq);
|
||||
|
@ -36,14 +36,90 @@ export function getOutputPanel() {
|
|||
return cy.getByTestId('output-panel');
|
||||
}
|
||||
|
||||
export function getFixedCollection(collectionName: string) {
|
||||
return cy.getByTestId(`fixed-collection-${collectionName}`);
|
||||
}
|
||||
|
||||
export function getResourceLocator(paramName: string) {
|
||||
return cy.getByTestId(`resource-locator-${paramName}`);
|
||||
}
|
||||
|
||||
export function getResourceLocatorInput(paramName: string) {
|
||||
return getResourceLocator(paramName).find('[data-test-id="rlc-input-container"]');
|
||||
}
|
||||
|
||||
export function getOutputPanelDataContainer() {
|
||||
return getOutputPanel().getByTestId('ndv-data-container');
|
||||
}
|
||||
|
||||
export function getOutputTableRows() {
|
||||
return getOutputPanelDataContainer().find('table tr');
|
||||
}
|
||||
|
||||
export function getOutputTableRow(row: number) {
|
||||
return getOutputTableRows().eq(row);
|
||||
}
|
||||
|
||||
export function getOutputTableHeaders() {
|
||||
return getOutputPanelDataContainer().find('table thead th');
|
||||
}
|
||||
|
||||
export function getOutputTableHeaderByText(text: string) {
|
||||
return getOutputTableHeaders().contains(text);
|
||||
}
|
||||
|
||||
export function getOutputTbodyCell(row: number, col: number) {
|
||||
return getOutputTableRows().eq(row).find('td').eq(col);
|
||||
}
|
||||
|
||||
export function getOutputRunSelector() {
|
||||
return getOutputPanel().findChildByTestId('run-selector');
|
||||
}
|
||||
|
||||
export function getOutputRunSelectorInput() {
|
||||
return getOutputRunSelector().find('input');
|
||||
}
|
||||
|
||||
export function getOutputPanelTable() {
|
||||
return getOutputPanelDataContainer().get('table');
|
||||
}
|
||||
|
||||
export function getRunDataInfoCallout() {
|
||||
return cy.getByTestId('run-data-callout');
|
||||
}
|
||||
|
||||
export function getOutputPanelItemsCount() {
|
||||
return getOutputPanel().getByTestId('ndv-items-count');
|
||||
}
|
||||
|
||||
export function getOutputPanelRelatedExecutionLink() {
|
||||
return getOutputPanel().getByTestId('related-execution-link');
|
||||
}
|
||||
|
||||
export function getNodeOutputHint() {
|
||||
return cy.getByTestId('ndv-output-run-node-hint');
|
||||
}
|
||||
|
||||
export function getWorkflowCards() {
|
||||
return cy.getByTestId('resources-list-item');
|
||||
}
|
||||
|
||||
export function getWorkflowCard(workflowName: string) {
|
||||
return getWorkflowCards().contains(workflowName).parents('[data-test-id="resources-list-item"]');
|
||||
}
|
||||
|
||||
export function getWorkflowCardContent(workflowName: string) {
|
||||
return getWorkflowCard(workflowName).findChildByTestId('card-content');
|
||||
}
|
||||
|
||||
export function getNodeRunInfoStale() {
|
||||
return cy.getByTestId('node-run-info-stale');
|
||||
}
|
||||
|
||||
export function getNodeOutputErrorMessage() {
|
||||
return getOutputPanel().findChildByTestId('node-error-message');
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
@ -70,15 +146,111 @@ export function clickExecuteNode() {
|
|||
getExecuteNodeButton().click();
|
||||
}
|
||||
|
||||
export function clickResourceLocatorInput(paramName: string) {
|
||||
getResourceLocatorInput(paramName).click();
|
||||
}
|
||||
|
||||
export function setParameterInputByName(name: string, value: string) {
|
||||
getParameterInputByName(name).clear().type(value);
|
||||
}
|
||||
|
||||
export function toggleParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').realClick();
|
||||
export function checkParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').check({ force: true });
|
||||
}
|
||||
|
||||
export function uncheckParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').uncheck({ force: true });
|
||||
}
|
||||
|
||||
export function setParameterSelectByContent(name: string, content: string) {
|
||||
getParameterInputByName(name).realClick();
|
||||
getVisibleSelect().find('.option-headline').contains(content).click();
|
||||
}
|
||||
|
||||
export function changeOutputRunSelector(runName: string) {
|
||||
getOutputRunSelector().click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click();
|
||||
}
|
||||
|
||||
export function addItemToFixedCollection(collectionName: string) {
|
||||
getFixedCollection(collectionName).getByTestId('fixed-collection-add').click();
|
||||
}
|
||||
|
||||
export function typeIntoFixedCollectionItem(collectionName: string, index: number, value: string) {
|
||||
getFixedCollection(collectionName).within(() =>
|
||||
cy.getByTestId('parameter-input').eq(index).type(value),
|
||||
);
|
||||
}
|
||||
|
||||
export function selectResourceLocatorItem(
|
||||
resourceLocator: string,
|
||||
index: number,
|
||||
expectedText: string,
|
||||
) {
|
||||
clickResourceLocatorInput(resourceLocator);
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).should('exist');
|
||||
getVisiblePopper()
|
||||
.findChildByTestId('rlc-item')
|
||||
.eq(index)
|
||||
.find('span')
|
||||
.should('contain.text', expectedText)
|
||||
.click();
|
||||
}
|
||||
|
||||
export function clickWorkflowCardContent(workflowName: string) {
|
||||
getWorkflowCardContent(workflowName).click();
|
||||
}
|
||||
|
||||
export function assertNodeOutputHintExists() {
|
||||
getNodeOutputHint().should('exist');
|
||||
}
|
||||
|
||||
export function assertNodeOutputErrorMessageExists() {
|
||||
return getNodeOutputErrorMessage().should('exist');
|
||||
}
|
||||
|
||||
// Note that this only validates the expectedContent is *included* in the output table
|
||||
export function assertOutputTableContent(expectedContent: unknown[][]) {
|
||||
for (const [i, row] of expectedContent.entries()) {
|
||||
for (const [j, value] of row.entries()) {
|
||||
// + 1 to skip header
|
||||
getOutputTbodyCell(1 + i, j).should('have.text', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function populateMapperFields(fields: ReadonlyArray<[string, string]>) {
|
||||
for (const [name, value] of fields) {
|
||||
getParameterInputByName(name).type(value);
|
||||
|
||||
// Click on a parent to dismiss the pop up which hides the field below.
|
||||
getParameterInputByName(name).parent().parent().parent().click('topLeft');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate multiValue fixedCollections. Only supports fixedCollections for which all fields can be defined via keyboard typing
|
||||
*
|
||||
* @param items - 2D array of items to populate, i.e. [["myField1", "String"], ["myField2", "Number"]]
|
||||
* @param collectionName - name of the fixedCollection to populate
|
||||
* @param offset - amount of 'parameter-input's before start, e.g. from a controlling dropdown that makes the fields appear
|
||||
* @returns
|
||||
*/
|
||||
export function populateFixedCollection<T extends readonly string[]>(
|
||||
items: readonly T[],
|
||||
collectionName: string,
|
||||
offset: number = 0,
|
||||
) {
|
||||
if (items.length === 0) return;
|
||||
const n = items[0].length;
|
||||
for (const [i, params] of items.entries()) {
|
||||
addItemToFixedCollection(collectionName);
|
||||
for (const [j, param] of params.entries()) {
|
||||
getFixedCollection(collectionName)
|
||||
.getByTestId('parameter-input')
|
||||
.eq(offset + i * n + j)
|
||||
.type(`${param}{downArrow}{enter}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,40 @@ 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').should('contain', 'Add project').should('be.visible');
|
||||
export const getAddProjectButton = () => {
|
||||
cy.getByTestId('universal-add').should('be.visible').click();
|
||||
cy.getByTestId('universal-add')
|
||||
.find('.el-sub-menu__title')
|
||||
.as('menuitem')
|
||||
.should('have.attr', 'aria-describedby');
|
||||
|
||||
cy.get('@menuitem')
|
||||
.invoke('attr', 'aria-describedby')
|
||||
.then((el) => cy.get(`[id="${el}"]`))
|
||||
.as('submenu');
|
||||
|
||||
cy.get('@submenu').within((submenu) =>
|
||||
cy
|
||||
.wrap(submenu)
|
||||
.getByTestId('navigation-menu-item')
|
||||
.should('be.visible')
|
||||
.filter(':contains("Project")')
|
||||
.as('button'),
|
||||
);
|
||||
|
||||
return cy.get('@button');
|
||||
};
|
||||
export const getAddFirstProjectButton = () => cy.getByTestId('add-first-project-button');
|
||||
export const getIconPickerButton = () => cy.getByTestId('icon-picker-button');
|
||||
export const getIconPickerTab = (tab: string) => cy.getByTestId('icon-picker-tabs').contains(tab);
|
||||
export const getIconPickerIcons = () => cy.getByTestId('icon-picker-icon');
|
||||
export const getIconPickerEmojis = () => cy.getByTestId('icon-picker-emoji');
|
||||
// export const getAddProjectButton = () =>
|
||||
// cy.getByTestId('universal-add').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 getProjectTabExecutions = () => getProjectTabs().filter('a[href$="/executions"]');
|
||||
export const getProjectTabSettings = () => getProjectTabs().filter('a[href$="/settings"]');
|
||||
export const getProjectSettingsNameInput = () =>
|
||||
cy.getByTestId('project-settings-name-input').find('input');
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { getManualChatModal } from './modals/chat-modal';
|
||||
import { clickGetBackToCanvas, getParameterInputByName } from './ndv';
|
||||
import { ROUTES } from '../constants';
|
||||
|
||||
/**
|
||||
|
@ -6,6 +7,7 @@ import { ROUTES } from '../constants';
|
|||
*/
|
||||
|
||||
export type EndpointType =
|
||||
| 'main'
|
||||
| 'ai_chain'
|
||||
| 'ai_document'
|
||||
| 'ai_embedding'
|
||||
|
@ -23,8 +25,15 @@ export type EndpointType =
|
|||
*/
|
||||
|
||||
export function getAddInputEndpointByType(nodeName: string, endpointType: EndpointType) {
|
||||
return cy.get(
|
||||
`.add-input-endpoint[data-jtk-scope-${endpointType}][data-endpoint-name="${nodeName}"]`,
|
||||
return cy.ifCanvasVersion(
|
||||
() =>
|
||||
cy.get(
|
||||
`.add-input-endpoint[data-jtk-scope-${endpointType}][data-endpoint-name="${nodeName}"]`,
|
||||
),
|
||||
() =>
|
||||
cy.get(
|
||||
`[data-test-id="canvas-node-input-handle"][data-connection-type="${endpointType}"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,7 +54,14 @@ export function getNodes() {
|
|||
}
|
||||
|
||||
export function getNodeByName(name: string) {
|
||||
return cy.getByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0);
|
||||
return cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0),
|
||||
() => cy.getByTestId('canvas-node').filter(`[data-node-name="${name}"]`).eq(0),
|
||||
);
|
||||
}
|
||||
|
||||
export function getWorkflowHistoryCloseButton() {
|
||||
return cy.getByTestId('workflow-history-close-button');
|
||||
}
|
||||
|
||||
export function disableNode(name: string) {
|
||||
|
@ -55,10 +71,18 @@ export function disableNode(name: string) {
|
|||
}
|
||||
|
||||
export function getConnectionBySourceAndTarget(source: string, target: string) {
|
||||
return cy
|
||||
.get('.jtk-connector')
|
||||
.filter(`[data-source-node="${source}"][data-target-node="${target}"]`)
|
||||
.eq(0);
|
||||
return cy.ifCanvasVersion(
|
||||
() =>
|
||||
cy
|
||||
.get('.jtk-connector')
|
||||
.filter(`[data-source-node="${source}"][data-target-node="${target}"]`)
|
||||
.eq(0),
|
||||
() =>
|
||||
cy
|
||||
.getByTestId('edge')
|
||||
.filter(`[data-source-node-name="${source}"][data-target-node-name="${target}"]`)
|
||||
.eq(0),
|
||||
);
|
||||
}
|
||||
|
||||
export function getNodeCreatorSearchBar() {
|
||||
|
@ -69,6 +93,25 @@ export function getNodeCreatorPlusButton() {
|
|||
return cy.getByTestId('node-creator-plus-button');
|
||||
}
|
||||
|
||||
export function getCanvasNodes() {
|
||||
return cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('canvas-node'),
|
||||
() => cy.getByTestId('canvas-node').not('[data-node-type="n8n-nodes-internal.addNodes"]'),
|
||||
);
|
||||
}
|
||||
|
||||
export function getCanvasNodeByName(nodeName: string) {
|
||||
return getCanvasNodes().filter(`:contains(${nodeName})`);
|
||||
}
|
||||
|
||||
export function getSaveButton() {
|
||||
return cy.getByTestId('workflow-save-button');
|
||||
}
|
||||
|
||||
export function getZoomToFitButton() {
|
||||
return cy.getByTestId('zoom-to-fit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
@ -108,7 +151,7 @@ export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
|
|||
});
|
||||
}
|
||||
|
||||
export function addSupplementalNodeToParent(
|
||||
function connectNodeToParent(
|
||||
nodeName: string,
|
||||
endpointType: EndpointType,
|
||||
parentNodeName: string,
|
||||
|
@ -122,7 +165,28 @@ export function addSupplementalNodeToParent(
|
|||
} else {
|
||||
getNodeCreatorItems().contains(nodeName).click();
|
||||
}
|
||||
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
|
||||
}
|
||||
|
||||
export function addSupplementalNodeToParent(
|
||||
nodeName: string,
|
||||
endpointType: EndpointType,
|
||||
parentNodeName: string,
|
||||
exactMatch = false,
|
||||
) {
|
||||
connectNodeToParent(nodeName, endpointType, parentNodeName, exactMatch);
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
|
||||
},
|
||||
() => {
|
||||
if (endpointType === 'main') {
|
||||
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
|
||||
} else {
|
||||
getConnectionBySourceAndTarget(nodeName, parentNodeName).should('exist');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function addLanguageModelNodeToParent(
|
||||
|
@ -141,6 +205,15 @@ export function addToolNodeToParent(nodeName: string, parentNodeName: string) {
|
|||
addSupplementalNodeToParent(nodeName, 'ai_tool', parentNodeName);
|
||||
}
|
||||
|
||||
export function addVectorStoreToolToParent(nodeName: string, parentNodeName: string) {
|
||||
connectNodeToParent(nodeName, 'ai_tool', parentNodeName, false);
|
||||
getParameterInputByName('mode')
|
||||
.find('input')
|
||||
.should('have.value', 'Retrieve Documents (As Tool for AI Agent)');
|
||||
clickGetBackToCanvas();
|
||||
getConnectionBySourceAndTarget(nodeName, parentNodeName).should('exist');
|
||||
}
|
||||
|
||||
export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) {
|
||||
addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName);
|
||||
}
|
||||
|
@ -163,3 +236,24 @@ export function clickManualChatButton() {
|
|||
export function openNode(nodeName: string) {
|
||||
getNodeByName(nodeName).dblclick();
|
||||
}
|
||||
|
||||
export function saveWorkflowOnButtonClick() {
|
||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||
getSaveButton().should('contain', 'Save');
|
||||
getSaveButton().click();
|
||||
getSaveButton().should('contain', 'Saved');
|
||||
cy.url().should('not.have.string', '/new');
|
||||
}
|
||||
|
||||
export function pasteWorkflow(workflow: object) {
|
||||
cy.get('body').paste(JSON.stringify(workflow));
|
||||
}
|
||||
|
||||
export function clickZoomToFit() {
|
||||
getZoomToFitButton().click();
|
||||
}
|
||||
|
||||
export function deleteNode(name: string) {
|
||||
getCanvasNodeByName(name).first().click();
|
||||
cy.get('body').type('{del}');
|
||||
}
|
||||
|
|
15
cypress/composables/workflowsPage.ts
Normal file
15
cypress/composables/workflowsPage.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Getters
|
||||
*/
|
||||
|
||||
export function getWorkflowsPageUrl() {
|
||||
return '/home/workflows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
||||
export function visitWorkflowsPage() {
|
||||
cy.visit(getWorkflowsPageUrl());
|
||||
}
|
|
@ -129,7 +129,7 @@ describe('Inline expression editor', () => {
|
|||
|
||||
// Run workflow
|
||||
ndv.actions.close();
|
||||
WorkflowPage.actions.executeNode('No Operation');
|
||||
WorkflowPage.actions.executeNode('No Operation', { anchor: 'topLeft' });
|
||||
WorkflowPage.actions.openNode('Hacker News');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ describe('Data pinning', () => {
|
|||
it('Should be able to pin data from canvas (context menu or shortcut)', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
workflowPage.actions.openContextMenu(EDIT_FIELDS_SET_NODE_NAME, 'overflow-button');
|
||||
workflowPage.actions.openContextMenu(EDIT_FIELDS_SET_NODE_NAME, { method: 'overflow-button' });
|
||||
workflowPage.getters
|
||||
.contextMenuAction('toggle_pin')
|
||||
.parent()
|
||||
|
|
|
@ -41,7 +41,9 @@ describe('Data mapping', () => {
|
|||
ndv.actions.mapDataFromHeader(1, 'value');
|
||||
ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.timestamp }}');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '2024');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.should('include.text', new Date().getFullYear());
|
||||
|
||||
ndv.actions.mapDataFromHeader(2, 'value');
|
||||
ndv.getters
|
||||
|
@ -113,6 +115,8 @@ describe('Data mapping', () => {
|
|||
});
|
||||
|
||||
it('maps expressions from json view', () => {
|
||||
// ADO-3063 - followup to make this viewport global
|
||||
cy.viewport('macbook-16');
|
||||
cy.fixture('Test_workflow_3.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
});
|
||||
|
@ -121,17 +125,17 @@ describe('Data mapping', () => {
|
|||
workflowPage.actions.openNode('Set');
|
||||
ndv.actions.switchInputMode('JSON');
|
||||
|
||||
ndv.getters.inputDataContainer().should('exist');
|
||||
|
||||
ndv.getters
|
||||
.inputDataContainer()
|
||||
.should('exist')
|
||||
.find('.json-data')
|
||||
.should(
|
||||
'have.text',
|
||||
'[{"input": [{"count": 0,"with space": "!!","with.dot": "!!","with"quotes": "!!"}]},{"input": [{"count": 1}]}]',
|
||||
)
|
||||
.find('span')
|
||||
.contains('"count"')
|
||||
.realMouseDown();
|
||||
);
|
||||
|
||||
ndv.getters.inputDataContainer().find('span').contains('"count"').realMouseDown();
|
||||
|
||||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}');
|
||||
|
@ -185,7 +189,6 @@ describe('Data mapping', () => {
|
|||
workflowPage.actions.openNode('Set1');
|
||||
|
||||
ndv.actions.executePrevious();
|
||||
ndv.actions.expandSchemaViewNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
||||
const dataPill = ndv.getters
|
||||
.inputDataContainer()
|
||||
|
|
|
@ -44,8 +44,7 @@ describe('n8n Form Trigger', () => {
|
|||
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
|
||||
)
|
||||
.find('input[placeholder*="e.g. What is your name?"]')
|
||||
.type('Test Field 3')
|
||||
.blur();
|
||||
.type('Test Field 3');
|
||||
cy.get(
|
||||
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
|
||||
).click();
|
||||
|
@ -56,27 +55,24 @@ describe('n8n Form Trigger', () => {
|
|||
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
|
||||
)
|
||||
.find('input[placeholder*="e.g. What is your name?"]')
|
||||
.type('Test Field 4')
|
||||
.blur();
|
||||
.type('Test Field 4');
|
||||
cy.get(
|
||||
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
|
||||
).click();
|
||||
getVisibleSelect().contains('Dropdown').click();
|
||||
cy.get(
|
||||
'.border-top-dashed > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > :nth-child(2) > .button',
|
||||
).click();
|
||||
cy.get(
|
||||
':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(1)',
|
||||
)
|
||||
.find('input')
|
||||
.type('Option 1')
|
||||
.blur();
|
||||
cy.get(
|
||||
':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(2)',
|
||||
)
|
||||
.find('input')
|
||||
.type('Option 2')
|
||||
.blur();
|
||||
cy.contains('button', 'Add Field Option').click();
|
||||
cy.contains('label', 'Field Options')
|
||||
.parent()
|
||||
.nextAll()
|
||||
.find('[data-test-id="parameter-input-field"]')
|
||||
.eq(0)
|
||||
.type('Option 1');
|
||||
cy.contains('label', 'Field Options')
|
||||
.parent()
|
||||
.nextAll()
|
||||
.find('[data-test-id="parameter-input-field"]')
|
||||
.eq(1)
|
||||
.type('Option 2');
|
||||
|
||||
//add optional submitted message
|
||||
cy.get('.param-options').click();
|
||||
|
@ -94,7 +90,6 @@ describe('n8n Form Trigger', () => {
|
|||
.children()
|
||||
.children()
|
||||
.first()
|
||||
.clear()
|
||||
.type('Your test form was successfully submitted');
|
||||
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
|
|
@ -250,7 +250,7 @@ describe('Webhook Trigger node', () => {
|
|||
});
|
||||
// add credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
|
||||
|
@ -293,7 +293,7 @@ describe('Webhook Trigger node', () => {
|
|||
});
|
||||
// add credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { saveCredential } from '../composables/modals/credential-modal';
|
||||
import * as projects from '../composables/projects';
|
||||
import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN, NOTION_NODE_NAME } from '../constants';
|
||||
import {
|
||||
|
@ -88,7 +89,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowsPage.getters.workflowCardContent('Workflow W1').click();
|
||||
workflowPage.actions.addNodeToCanvas('Airtable', true, true);
|
||||
ndv.getters.credentialInput().find('input').should('have.value', 'Credential C2');
|
||||
ndv.actions.close();
|
||||
|
@ -104,7 +105,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowsPage.getters.workflowCardContent('Workflow W1').click();
|
||||
workflowPage.actions.addNodeToCanvas('Airtable', true, true);
|
||||
ndv.getters.credentialInput().find('input').should('have.value', 'Credential C2');
|
||||
ndv.actions.close();
|
||||
|
@ -133,7 +134,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowsPage.getters.workflowCardContent('Workflow W1').click();
|
||||
workflowPage.actions.openNode('Notion');
|
||||
ndv.getters
|
||||
.credentialInput()
|
||||
|
@ -144,7 +145,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
|
||||
cy.waitForLoad();
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCard('Workflow W2').click('top');
|
||||
workflowsPage.getters.workflowCardContent('Workflow W2').click('top');
|
||||
workflowPage.actions.executeWorkflow();
|
||||
});
|
||||
|
||||
|
@ -225,8 +226,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
.filter(':contains("Development")')
|
||||
.should('have.length', 1)
|
||||
.click();
|
||||
credentialsModal.getters.saveButton().click();
|
||||
credentialsModal.getters.saveButton().should('have.text', 'Saved');
|
||||
saveCredential();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
projects.getProjectTabWorkflows().click();
|
||||
|
@ -252,8 +252,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
credentialsModal.actions.changeTab('Sharing');
|
||||
credentialsModal.getters.usersSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 4).first().click();
|
||||
credentialsModal.getters.saveButton().click();
|
||||
credentialsModal.getters.saveButton().should('have.text', 'Saved');
|
||||
saveCredential();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
credentialsPage.getters
|
||||
|
@ -298,10 +297,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.actions.createWorkflowFromCard();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the credential in this project (+ the 'Create new' option) should
|
||||
// be in the dropdown
|
||||
// Only the credential in this project should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should only show credentials in their personal project for members', () => {
|
||||
|
@ -326,10 +324,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.actions.createWorkflowFromCard();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the own credential the shared one (+ the 'Create new' option)
|
||||
// should be in the dropdown
|
||||
// Only the own credential the shared one should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should only show credentials in their personal project for members if the workflow was shared with them', () => {
|
||||
|
@ -353,13 +350,12 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCard(workflowName).click();
|
||||
workflowsPage.getters.workflowCardContent(workflowName).click();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the own credential the shared one (+ the 'Create new' option)
|
||||
// should be in the dropdown
|
||||
// Only the own credential the shared one should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 1);
|
||||
});
|
||||
|
||||
it("should show all credentials from all personal projects the workflow's been shared into for the global owner", () => {
|
||||
|
@ -398,13 +394,12 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
credentialsPage.getters.createCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCard(workflowName).click();
|
||||
workflowsPage.getters.workflowCardContent(workflowName).click();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the personal credentials of the workflow owner and the global owner
|
||||
// should show up.
|
||||
// Only the personal credentials of the workflow owner and the global owner should show up.
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 4);
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should show all personal credentials if the global owner owns the workflow', () => {
|
||||
|
@ -422,6 +417,6 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
|
||||
// Show all personal credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.have.length', 1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -148,24 +148,9 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
|||
|
||||
personalSettingsPage.actions.changeTheme('Dark');
|
||||
cy.get('body').should('have.attr', 'data-theme', 'dark');
|
||||
settingsSidebar.actions.back();
|
||||
mainSidebar.getters
|
||||
.logo()
|
||||
.should('have.attr', 'src')
|
||||
.then((src) => {
|
||||
expect(src).to.include('/static/logo/channel/dev-dark.svg');
|
||||
});
|
||||
|
||||
cy.visit(personalSettingsPage.url);
|
||||
personalSettingsPage.actions.changeTheme('Light');
|
||||
cy.get('body').should('have.attr', 'data-theme', 'light');
|
||||
settingsSidebar.actions.back();
|
||||
mainSidebar.getters
|
||||
.logo()
|
||||
.should('have.attr', 'src')
|
||||
.then((src) => {
|
||||
expect(src).to.include('/static/logo/channel/dev.svg');
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete user and their data', () => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { type ICredentialType } from 'n8n-workflow';
|
||||
|
||||
import { getCredentialSaveButton, saveCredential } from '../composables/modals/credential-modal';
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
AI_TOOL_HTTP_NODE_NAME,
|
||||
|
@ -26,6 +27,22 @@ const nodeDetailsView = new NDV();
|
|||
const NEW_CREDENTIAL_NAME = 'Something else';
|
||||
const NEW_CREDENTIAL_NAME2 = 'Something else entirely';
|
||||
|
||||
function createNotionCredential() {
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME);
|
||||
workflowPage.actions.openNode(NOTION_NODE_NAME);
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
cy.get('body').type('{esc}');
|
||||
workflowPage.actions.deleteNode(NOTION_NODE_NAME);
|
||||
}
|
||||
|
||||
function deleteSelectedCredential() {
|
||||
workflowPage.getters.nodeCredentialsEditButton().click();
|
||||
credentialsModal.getters.deleteButton().click();
|
||||
cy.get('.el-message-box').find('button').contains('Yes').click();
|
||||
}
|
||||
|
||||
describe('Credentials', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(credentialsPage.url);
|
||||
|
@ -62,7 +79,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
|
@ -82,7 +99,7 @@ describe('Credentials', () => {
|
|||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
// Add oAuth credentials
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
|
@ -90,14 +107,13 @@ describe('Credentials', () => {
|
|||
cy.get('.el-message-box').find('button').contains('Close').click();
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
// Add Service account credentials
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().last().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
// Both (+ the 'Create new' option) should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length.greaterThan', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should correctly render required and optional credentials', () => {
|
||||
|
@ -113,13 +129,13 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().should('have.length', 2);
|
||||
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().first().click();
|
||||
// This one should show auth type selector
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
workflowPage.getters.nodeCredentialsSelect().last().click();
|
||||
getVisibleSelect().find('li').contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().last().click();
|
||||
// This one should not show auth type selector
|
||||
credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist');
|
||||
});
|
||||
|
@ -131,7 +147,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
|
@ -147,7 +163,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -172,13 +188,13 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().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();
|
||||
saveCredential();
|
||||
credentialsModal.getters.closeButton().click();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -196,7 +212,7 @@ describe('Credentials', () => {
|
|||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.name().click();
|
||||
credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME2);
|
||||
credentialsModal.getters.saveButton().click();
|
||||
saveCredential();
|
||||
credentialsModal.getters.closeButton().click();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -215,13 +231,13 @@ describe('Credentials', () => {
|
|||
cy.getByTestId('credential-select').click();
|
||||
cy.contains('Adalo API').click();
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().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();
|
||||
saveCredential();
|
||||
credentialsModal.getters.closeButton().click();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -229,6 +245,40 @@ describe('Credentials', () => {
|
|||
.should('have.value', NEW_CREDENTIAL_NAME);
|
||||
});
|
||||
|
||||
it('should set a default credential when adding nodes', () => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
createNotionCredential();
|
||||
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
.find('input')
|
||||
.should('have.value', NEW_NOTION_ACCOUNT_NAME);
|
||||
|
||||
deleteSelectedCredential();
|
||||
});
|
||||
|
||||
it('should set a default credential when editing a node', () => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
createNotionCredential();
|
||||
|
||||
workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true);
|
||||
nodeDetailsView.getters.parameterInput('authentication').click();
|
||||
getVisibleSelect().find('li').contains('Predefined').click();
|
||||
|
||||
nodeDetailsView.getters.parameterInput('nodeCredentialType').click();
|
||||
getVisibleSelect().find('li').contains('Notion API').click();
|
||||
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
.find('input')
|
||||
.should('have.value', NEW_NOTION_ACCOUNT_NAME);
|
||||
|
||||
deleteSelectedCredential();
|
||||
});
|
||||
|
||||
it('should setup generic authentication for HTTP node', () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
@ -245,7 +295,7 @@ describe('Credentials', () => {
|
|||
|
||||
workflowPage.getters.nodeCredentialsSelect().should('exist');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -274,7 +324,7 @@ describe('Credentials', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
||||
workflowPage.getters.nodeCredentialsSelect().should('exist');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
nodeDetailsView.getters.copyInput().should('not.exist');
|
||||
});
|
||||
|
@ -292,7 +342,8 @@ describe('Credentials', () => {
|
|||
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
|
||||
|
||||
credentialsModal.actions.setName('My awesome Notion account');
|
||||
credentialsModal.getters.saveButton().click({ force: true });
|
||||
getCredentialSaveButton().click();
|
||||
|
||||
errorToast().should('have.length', 1);
|
||||
errorToast().should('be.visible');
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ describe('Community and custom nodes in canvas', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('E2E Node with native n8n credential', true, true);
|
||||
workflowPage.getters.nodeCredentialsLabel().click();
|
||||
cy.contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.editCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.editCredentialModal().should('contain.text', 'Notion API');
|
||||
});
|
||||
|
@ -98,7 +98,7 @@ describe('Community and custom nodes in canvas', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('E2E Node with custom credential', true, true);
|
||||
workflowPage.getters.nodeCredentialsLabel().click();
|
||||
cy.contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.editCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.editCredentialModal().should('contain.text', 'Custom E2E Credential');
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { WEBHOOK_NODE_NAME } from '../constants';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('ADO-2270 Save button resets on webhook node open', () => {
|
||||
it('should not reset the save button if webhook node is opened and closed', () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addInitialNodeToCanvas(WEBHOOK_NODE_NAME);
|
||||
workflowPage.getters.saveButton().click();
|
||||
workflowPage.actions.openNode(WEBHOOK_NODE_NAME);
|
||||
|
||||
ndv.actions.close();
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('workflow-save-button').should('not.contain', 'Saved'),
|
||||
() => cy.getByTestId('workflow-save-button').should('contain', 'Saved'),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -90,6 +90,14 @@ function createRunDataWithError(inputMessage: string) {
|
|||
routine: 'InitPostgres',
|
||||
} as unknown as Error,
|
||||
} as ExecutionError,
|
||||
metadata: {
|
||||
subRun: [
|
||||
{
|
||||
node: 'Postgres Chat Memory',
|
||||
runIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
executionStatus: 'error',
|
||||
|
@ -124,14 +132,6 @@ function createRunDataWithError(inputMessage: string) {
|
|||
description: 'Internal error',
|
||||
message: 'Internal error',
|
||||
} as unknown as ExecutionError,
|
||||
metadata: {
|
||||
subRun: [
|
||||
{
|
||||
node: 'Postgres Chat Memory',
|
||||
runIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -31,29 +31,31 @@ describe('NDV', () => {
|
|||
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.outputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.inputTableRow(2).realHover();
|
||||
ndv.getters.inputTableRow(2).realMouseMove(10, 1);
|
||||
ndv.getters.outputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.inputTableRow(3).realHover();
|
||||
ndv.getters.inputTableRow(3).realMouseMove(10, 1);
|
||||
ndv.getters.outputTableRow(6).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
// output to input
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(4).realHover();
|
||||
ndv.getters.outputTableRow(4).realMouseMove(10, 1);
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(2).realHover();
|
||||
ndv.getters.outputTableRow(2).realMouseMove(10, 1);
|
||||
ndv.getters.inputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(6).realHover();
|
||||
ndv.getters.outputTableRow(6).realMouseMove(10, 1);
|
||||
ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
});
|
||||
|
||||
|
@ -75,31 +77,32 @@ describe('NDV', () => {
|
|||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.switchOutputMode('Table');
|
||||
|
||||
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||
ndv.getters.backToCanvas().realMouseMove(10, 1); // reset to default hover
|
||||
ndv.getters.outputHoveringItem().should('not.exist');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||
|
||||
ndv.actions.selectInputNode('Set1');
|
||||
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||
ndv.getters.backToCanvas().realMouseMove(10, 1); // reset to default hover
|
||||
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1000');
|
||||
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
cy.wait(50);
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTbodyCell(1, 0).realMouseMove(10, 1);
|
||||
ndv.getters.outputHoveringItem().should('have.text', '1000');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||
|
||||
ndv.actions.selectInputNode('Sort');
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
|
||||
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||
ndv.getters.backToCanvas().realMouseMove(10, 1); // 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(50);
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTbodyCell(1, 0).realMouseMove(10, 1);
|
||||
ndv.getters.outputHoveringItem().should('have.text', '1111');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||
});
|
||||
|
@ -132,20 +135,22 @@ describe('NDV', () => {
|
|||
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1111');
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.getters.outputTableRow(1).should('have.text', '1111');
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.outputTableRow(3).should('have.text', '4444');
|
||||
ndv.getters.outputTableRow(3).realHover();
|
||||
ndv.getters.outputTableRow(3).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.inputTableRow(3).should('have.text', '4444');
|
||||
ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('2 of 2 (6 items)');
|
||||
cy.wait(50);
|
||||
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1000');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTableRow(1).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.outputTableRow(1).should('have.text', '1000');
|
||||
ndv.getters
|
||||
|
@ -155,7 +160,8 @@ describe('NDV', () => {
|
|||
.should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(3).should('have.text', '2000');
|
||||
ndv.getters.outputTableRow(3).realHover();
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.getters.outputTableRow(3).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.inputTableRow(3).should('have.text', '2000');
|
||||
|
||||
|
@ -175,14 +181,15 @@ describe('NDV', () => {
|
|||
|
||||
ndv.actions.switchOutputBranch('False Branch (2 items)');
|
||||
ndv.getters.outputTableRow(1).should('have.text', '8888');
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.inputTableRow(5).should('have.text', '8888');
|
||||
|
||||
ndv.getters.inputTableRow(5).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputTableRow(2).should('have.text', '9999');
|
||||
ndv.getters.outputTableRow(2).realHover();
|
||||
ndv.getters.outputTableRow(2).realMouseMove(10, 1);
|
||||
|
||||
ndv.getters.inputTableRow(6).should('have.text', '9999');
|
||||
|
||||
|
@ -192,29 +199,35 @@ describe('NDV', () => {
|
|||
|
||||
workflowPage.actions.openNode('Set5');
|
||||
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.actions.switchInputBranch('True Branch');
|
||||
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)');
|
||||
ndv.getters.outputTableRow(1).should('have.text', '8888');
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
cy.wait(100);
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.inputHoveringItem().should('not.exist');
|
||||
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1111');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
cy.wait(100);
|
||||
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.outputHoveringItem().should('not.exist');
|
||||
|
||||
ndv.actions.switchInputBranch('False Branch');
|
||||
ndv.getters.inputTableRow(1).should('have.text', '8888');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTableRow(1).realMouseMove(10, 1);
|
||||
|
||||
ndv.actions.dragMainPanelToLeft();
|
||||
ndv.actions.changeOutputRunSelector('2 of 2 (4 items)');
|
||||
ndv.getters.outputTableRow(1).should('have.text', '1111');
|
||||
ndv.getters.outputTableRow(1).realHover();
|
||||
ndv.getters.outputTableRow(1).realMouseMove(10, 1);
|
||||
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)');
|
||||
ndv.getters.inputTableRow(1).should('have.text', '8888');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.actions.dragMainPanelToRight();
|
||||
ndv.getters.inputTableRow(1).realMouseMove(10, 1);
|
||||
ndv.getters.outputHoveringItem().should('have.text', '8888');
|
||||
// todo there's a bug here need to fix ADO-534
|
||||
// ndv.getters.outputHoveringItem().should('not.exist');
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import planData from '../fixtures/Plan_data_opt_in_trial.json';
|
||||
import {
|
||||
BannerStack,
|
||||
MainSidebar,
|
||||
WorkflowPage,
|
||||
visitPublicApiPage,
|
||||
getPublicApiUpgradeCTA,
|
||||
WorkflowsPage,
|
||||
} from '../pages';
|
||||
|
||||
const NUMBER_OF_AI_CREDITS = 100;
|
||||
|
||||
const mainSidebar = new MainSidebar();
|
||||
const bannerStack = new BannerStack();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
describe('Cloud', () => {
|
||||
before(() => {
|
||||
|
@ -22,6 +24,10 @@ describe('Cloud', () => {
|
|||
cy.overrideSettings({
|
||||
deployment: { type: 'cloud' },
|
||||
n8nMetadata: { userId: '1' },
|
||||
aiCredits: {
|
||||
enabled: true,
|
||||
credits: NUMBER_OF_AI_CREDITS,
|
||||
},
|
||||
});
|
||||
cy.intercept('GET', '/rest/admin/cloud-plan', planData).as('getPlanData');
|
||||
cy.intercept('GET', '/rest/cloud/proxy/user/me', {}).as('getCloudUserInfo');
|
||||
|
@ -40,11 +46,11 @@ describe('Cloud', () => {
|
|||
it('should render trial banner for opt-in cloud user', () => {
|
||||
visitWorkflowPage();
|
||||
|
||||
bannerStack.getters.banner().should('be.visible');
|
||||
cy.getByTestId('banner-stack').should('be.visible');
|
||||
|
||||
mainSidebar.actions.signout();
|
||||
|
||||
bannerStack.getters.banner().should('not.be.visible');
|
||||
cy.getByTestId('banner-stack').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -64,4 +70,66 @@ describe('Cloud', () => {
|
|||
getPublicApiUpgradeCTA().should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Easy AI workflow experiment', () => {
|
||||
it('should not show option to take you to the easy AI workflow if experiment is control', () => {
|
||||
window.localStorage.setItem(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ '026_easy_ai_workflow': 'control' }),
|
||||
);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.getByTestId('easy-ai-workflow-card').should('not.exist');
|
||||
});
|
||||
|
||||
it('should show option to take you to the easy AI workflow if experiment is variant', () => {
|
||||
window.localStorage.setItem(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ '026_easy_ai_workflow': 'variant' }),
|
||||
);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.getByTestId('easy-ai-workflow-card').should('to.exist');
|
||||
});
|
||||
|
||||
it('should show default instructions if free AI credits experiment is control', () => {
|
||||
window.localStorage.setItem(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ '027_free_openai_calls': 'control', '026_easy_ai_workflow': 'variant' }),
|
||||
);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.getByTestId('easy-ai-workflow-card').click();
|
||||
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(0)
|
||||
.should(($el) => {
|
||||
expect($el).contains.text('Set up your OpenAI credentials in the OpenAI Model node');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show updated instructions if free AI credits experiment is variant', () => {
|
||||
window.localStorage.setItem(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ '027_free_openai_calls': 'variant', '026_easy_ai_workflow': 'variant' }),
|
||||
);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.getByTestId('easy-ai-workflow-card').click();
|
||||
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(0)
|
||||
.should(($el) => {
|
||||
expect($el).contains.text(
|
||||
`Claim your free ${NUMBER_OF_AI_CREDITS} OpenAI calls in the OpenAI model node`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,33 +49,47 @@ describe('Two-factor authentication', { disableAutoLogin: true }, () => {
|
|||
cy.intercept('GET', '/rest/mfa/qr').as('getMfaQrCode');
|
||||
});
|
||||
|
||||
it('Should be able to login with MFA token', () => {
|
||||
it('Should be able to login with MFA code', () => {
|
||||
const { email, password } = user;
|
||||
signinPage.actions.loginWithEmailAndPassword(email, password);
|
||||
personalSettingsPage.actions.enableMfa();
|
||||
mainSidebar.actions.signout();
|
||||
const token = generateOTPToken(user.mfaSecret);
|
||||
mfaLoginPage.actions.loginWithMfaToken(email, password, token);
|
||||
const mfaCode = generateOTPToken(user.mfaSecret);
|
||||
mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode);
|
||||
mainSidebar.actions.signout();
|
||||
});
|
||||
|
||||
it('Should be able to login with recovery code', () => {
|
||||
it('Should be able to login with MFA recovery code', () => {
|
||||
const { email, password } = user;
|
||||
signinPage.actions.loginWithEmailAndPassword(email, password);
|
||||
personalSettingsPage.actions.enableMfa();
|
||||
mainSidebar.actions.signout();
|
||||
mfaLoginPage.actions.loginWithRecoveryCode(email, password, user.mfaRecoveryCodes[0]);
|
||||
mfaLoginPage.actions.loginWithMfaRecoveryCode(email, password, user.mfaRecoveryCodes[0]);
|
||||
mainSidebar.actions.signout();
|
||||
});
|
||||
|
||||
it('Should be able to disable MFA in account', () => {
|
||||
it('Should be able to disable MFA in account with MFA code', () => {
|
||||
const { email, password } = user;
|
||||
signinPage.actions.loginWithEmailAndPassword(email, password);
|
||||
personalSettingsPage.actions.enableMfa();
|
||||
mainSidebar.actions.signout();
|
||||
const token = generateOTPToken(user.mfaSecret);
|
||||
mfaLoginPage.actions.loginWithMfaToken(email, password, token);
|
||||
personalSettingsPage.actions.disableMfa();
|
||||
const mfaCode = generateOTPToken(user.mfaSecret);
|
||||
mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode);
|
||||
const disableToken = generateOTPToken(user.mfaSecret);
|
||||
personalSettingsPage.actions.disableMfa(disableToken);
|
||||
personalSettingsPage.getters.enableMfaButton().should('exist');
|
||||
mainSidebar.actions.signout();
|
||||
});
|
||||
|
||||
it('Should be able to disable MFA in account with recovery code', () => {
|
||||
const { email, password } = user;
|
||||
signinPage.actions.loginWithEmailAndPassword(email, password);
|
||||
personalSettingsPage.actions.enableMfa();
|
||||
mainSidebar.actions.signout();
|
||||
const mfaCode = generateOTPToken(user.mfaSecret);
|
||||
mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode);
|
||||
personalSettingsPage.actions.disableMfa(user.mfaRecoveryCodes[0]);
|
||||
personalSettingsPage.getters.enableMfaButton().should('exist');
|
||||
mainSidebar.actions.signout();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,11 +87,28 @@ describe('Debug', () => {
|
|||
confirmDialog.get('.btn--confirm').click();
|
||||
cy.url().should('include', '/debug');
|
||||
|
||||
workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.not(':first')
|
||||
.should('not.have.descendants', '.node-pin-data-icon');
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.should('have.descendants', '.node-pin-data-icon');
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.not(':first')
|
||||
.should('not.have.descendants', '.node-pin-data-icon');
|
||||
},
|
||||
() => {
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.should('have.descendants', '[data-test-id="canvas-node-status-pinned"]');
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.not(':first')
|
||||
.should('not.have.descendants', '[data-test-id="canvas-node-status-pinned"]');
|
||||
},
|
||||
);
|
||||
|
||||
cy.reload(true);
|
||||
cy.wait(['@getExecution']);
|
||||
|
@ -114,7 +131,18 @@ describe('Debug', () => {
|
|||
confirmDialog.get('.btn--confirm').click();
|
||||
cy.url().should('include', '/debug');
|
||||
|
||||
workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty');
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty');
|
||||
},
|
||||
() => {
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.find('[class*="statusIcons"]')
|
||||
.should('not.exist');
|
||||
},
|
||||
);
|
||||
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
ndv.actions.unPinData();
|
||||
|
|
|
@ -129,7 +129,6 @@ describe('Workflow templates', () => {
|
|||
workflowPage.actions.shouldHaveWorkflowName('Demo: ' + OnboardingWorkflow.name);
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
|
||||
});
|
||||
|
||||
it('can import template', () => {
|
||||
|
@ -142,6 +141,7 @@ describe('Workflow templates', () => {
|
|||
});
|
||||
|
||||
it('should save template id with the workflow', () => {
|
||||
cy.intercept('POST', '/rest/workflows').as('saveWorkflow');
|
||||
templatesPage.actions.importTemplate();
|
||||
|
||||
cy.visit(templatesPage.url);
|
||||
|
@ -159,10 +159,8 @@ describe('Workflow templates', () => {
|
|||
workflowPage.actions.hitSelectAll();
|
||||
workflowPage.actions.hitCopy();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
// Check workflow JSON by copying it to clipboard
|
||||
cy.readClipboard().then((workflowJSON) => {
|
||||
expect(workflowJSON).to.contain('"templateId": "1"');
|
||||
cy.wait('@saveWorkflow').then((interception) => {
|
||||
expect(interception.request.body.meta.templateId).to.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
deleteNode,
|
||||
getCanvasNodes,
|
||||
navigateToNewWorkflowPage,
|
||||
pasteWorkflow,
|
||||
} from '../composables/workflow';
|
||||
import Workflow from '../fixtures/Switch_node_with_null_connection.json';
|
||||
|
||||
describe('ADO-2929 can load Switch nodes', () => {
|
||||
it('can load workflows with Switch nodes with null at connection index', () => {
|
||||
navigateToNewWorkflowPage();
|
||||
pasteWorkflow(Workflow);
|
||||
getCanvasNodes().should('have.length', 3);
|
||||
deleteNode('Switch');
|
||||
getCanvasNodes().should('have.length', 2);
|
||||
});
|
||||
});
|
|
@ -1,18 +1,14 @@
|
|||
import { getWorkflowHistoryCloseButton } from '../composables/workflow';
|
||||
import {
|
||||
CODE_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from '../constants';
|
||||
import {
|
||||
WorkflowExecutionsTab,
|
||||
WorkflowPage as WorkflowPageClass,
|
||||
WorkflowHistoryPage,
|
||||
} from '../pages';
|
||||
import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
const workflowHistoryPage = new WorkflowHistoryPage();
|
||||
|
||||
const createNewWorkflowAndActivate = () => {
|
||||
workflowPage.actions.visit();
|
||||
|
@ -92,7 +88,7 @@ const switchBetweenEditorAndHistory = () => {
|
|||
cy.wait(['@getVersion']);
|
||||
|
||||
cy.intercept('GET', '/rest/workflows/*').as('workflowGet');
|
||||
workflowHistoryPage.getters.workflowHistoryCloseButton().click();
|
||||
getWorkflowHistoryCloseButton().click();
|
||||
cy.wait(['@workflowGet']);
|
||||
cy.wait(1000);
|
||||
|
||||
|
@ -168,7 +164,7 @@ describe('Editor actions should work', () => {
|
|||
cy.wait(['@getVersion']);
|
||||
|
||||
cy.intercept('GET', '/rest/workflows/*').as('workflowGet');
|
||||
workflowHistoryPage.getters.workflowHistoryCloseButton().click();
|
||||
getWorkflowHistoryCloseButton().click();
|
||||
cy.wait(['@workflowGet']);
|
||||
cy.wait(1000);
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
} from './../constants';
|
||||
import {
|
||||
closeManualChatModal,
|
||||
getManualChatDialog,
|
||||
getManualChatMessages,
|
||||
getManualChatModal,
|
||||
getManualChatModalLogs,
|
||||
|
@ -27,7 +26,9 @@ import {
|
|||
clickCreateNewCredential,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
toggleParameterCheckboxInputByName,
|
||||
getRunDataInfoCallout,
|
||||
getOutputPanelTable,
|
||||
checkParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
|
@ -44,6 +45,7 @@ import {
|
|||
openNode,
|
||||
getConnectionBySourceAndTarget,
|
||||
} from '../composables/workflow';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||
|
||||
describe('Langchain Integration', () => {
|
||||
|
@ -95,7 +97,7 @@ describe('Langchain Integration', () => {
|
|||
it('should add nodes to all Agent node input types', () => {
|
||||
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
toggleParameterCheckboxInputByName('hasOutputParser');
|
||||
checkParameterCheckboxInputByName('hasOutputParser');
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
|
@ -167,7 +169,7 @@ describe('Langchain Integration', () => {
|
|||
lastNodeExecuted: BASIC_LLM_CHAIN_NODE_NAME,
|
||||
});
|
||||
|
||||
getManualChatDialog().should('contain', outputMessage);
|
||||
getManualChatMessages().should('contain', outputMessage);
|
||||
});
|
||||
|
||||
it('should be able to open and execute Agent node', () => {
|
||||
|
@ -207,7 +209,7 @@ describe('Langchain Integration', () => {
|
|||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
getManualChatDialog().should('contain', outputMessage);
|
||||
getManualChatMessages().should('contain', outputMessage);
|
||||
});
|
||||
|
||||
it('should add and use Manual Chat Trigger node together with Agent node', () => {
|
||||
|
@ -228,99 +230,98 @@ describe('Langchain Integration', () => {
|
|||
|
||||
clickManualChatButton();
|
||||
|
||||
getManualChatModalLogs().should('not.exist');
|
||||
|
||||
const inputMessage = 'Hello!';
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
const runData = [
|
||||
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { input: inputMessage },
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, {
|
||||
jsonData: {
|
||||
ai_languageModel: {
|
||||
response: {
|
||||
generations: [
|
||||
{
|
||||
text: `{
|
||||
"action": "Final Answer",
|
||||
"action_input": "${outputMessage}"
|
||||
}`,
|
||||
message: {
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'AIMessage'],
|
||||
kwargs: {
|
||||
content: `{
|
||||
"action": "Final Answer",
|
||||
"action_input": "${outputMessage}"
|
||||
}`,
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
generationInfo: { finish_reason: 'stop' },
|
||||
},
|
||||
],
|
||||
llmOutput: {
|
||||
tokenUsage: {
|
||||
completionTokens: 26,
|
||||
promptTokens: 519,
|
||||
totalTokens: 545,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
inputOverride: {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
messages: [
|
||||
{
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'SystemMessage'],
|
||||
kwargs: {
|
||||
content:
|
||||
'Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist. However, above all else, all responses must adhere to the format of RESPONSE FORMAT INSTRUCTIONS.',
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'HumanMessage'],
|
||||
kwargs: {
|
||||
content:
|
||||
'TOOLS\n------\nAssistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:\n\n\n\nRESPONSE FORMAT INSTRUCTIONS\n----------------------------\n\nOutput a JSON markdown code snippet containing a valid JSON object in one of two formats:\n\n**Option 1:**\nUse this if you want the human to use a tool.\nMarkdown code snippet formatted in the following schema:\n\n```json\n{\n "action": string, // The action to take. Must be one of []\n "action_input": string // The input to the action. May be a stringified object.\n}\n```\n\n**Option #2:**\nUse this if you want to respond directly and conversationally to the human. Markdown code snippet formatted in the following schema:\n\n```json\n{\n "action": "Final Answer",\n "action_input": string // You should put what you want to return to use here and make sure to use valid json newline characters.\n}\n```\n\nFor both options, remember to always include the surrounding markdown code snippet delimiters (begin with "```json" and end with "```")!\n\n\nUSER\'S INPUT\n--------------------\nHere is the user\'s input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):\n\nHello!',
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
options: { stop: ['Observation:'], promptIndex: 0 },
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { output: 'Hi there! How can I assist you today?' },
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => {
|
||||
sendManualChatMessage(inputMessage);
|
||||
},
|
||||
runData: [
|
||||
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { input: inputMessage },
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, {
|
||||
jsonData: {
|
||||
ai_languageModel: {
|
||||
response: {
|
||||
generations: [
|
||||
{
|
||||
text: `{
|
||||
"action": "Final Answer",
|
||||
"action_input": "${outputMessage}"
|
||||
}`,
|
||||
message: {
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'AIMessage'],
|
||||
kwargs: {
|
||||
content: `{
|
||||
"action": "Final Answer",
|
||||
"action_input": "${outputMessage}"
|
||||
}`,
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
generationInfo: { finish_reason: 'stop' },
|
||||
},
|
||||
],
|
||||
llmOutput: {
|
||||
tokenUsage: {
|
||||
completionTokens: 26,
|
||||
promptTokens: 519,
|
||||
totalTokens: 545,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputOverride: {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
messages: [
|
||||
{
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'SystemMessage'],
|
||||
kwargs: {
|
||||
content:
|
||||
'Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist. However, above all else, all responses must adhere to the format of RESPONSE FORMAT INSTRUCTIONS.',
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
lc: 1,
|
||||
type: 'constructor',
|
||||
id: ['langchain', 'schema', 'HumanMessage'],
|
||||
kwargs: {
|
||||
content:
|
||||
'TOOLS\n------\nAssistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:\n\n\n\nRESPONSE FORMAT INSTRUCTIONS\n----------------------------\n\nOutput a JSON markdown code snippet containing a valid JSON object in one of two formats:\n\n**Option 1:**\nUse this if you want the human to use a tool.\nMarkdown code snippet formatted in the following schema:\n\n```json\n{\n "action": string, // The action to take. Must be one of []\n "action_input": string // The input to the action. May be a stringified object.\n}\n```\n\n**Option #2:**\nUse this if you want to respond directly and conversationally to the human. Markdown code snippet formatted in the following schema:\n\n```json\n{\n "action": "Final Answer",\n "action_input": string // You should put what you want to return to use here and make sure to use valid json newline characters.\n}\n```\n\nFor both options, remember to always include the surrounding markdown code snippet delimiters (begin with "```json" and end with "```")!\n\n\nUSER\'S INPUT\n--------------------\nHere is the user\'s input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):\n\nHello!',
|
||||
additional_kwargs: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
options: { stop: ['Observation:'], promptIndex: 0 },
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { output: 'Hi there! How can I assist you today?' },
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
}),
|
||||
],
|
||||
runData,
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
|
@ -333,6 +334,8 @@ describe('Langchain Integration', () => {
|
|||
getManualChatModalLogsEntries().should('have.length', 1);
|
||||
|
||||
closeManualChatModal();
|
||||
getManualChatModalLogs().should('not.exist');
|
||||
getManualChatModal().should('not.exist');
|
||||
});
|
||||
|
||||
it('should auto-add chat trigger and basic LLM chain when adding LLM node', () => {
|
||||
|
@ -357,4 +360,162 @@ describe('Langchain Integration', () => {
|
|||
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
||||
getNodes().should('have.length', 3);
|
||||
});
|
||||
it('should not auto-add nodes if ChatTrigger is already present', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
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);
|
||||
});
|
||||
it('should render runItems for sub-nodes and allow switching between them', () => {
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
cy.visit(workflowPage.url);
|
||||
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.executeNode('Populate VS');
|
||||
cy.get('[data-label="25 items"]').should('exist');
|
||||
|
||||
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
|
||||
ndv.getters.outputPanel().contains(text).should(assertion);
|
||||
ndv.getters.inputPanel().contains(text).should(assertion);
|
||||
};
|
||||
|
||||
workflowPage.actions.openNode('Character Text Splitter');
|
||||
ndv.getters.outputRunSelector().should('exist');
|
||||
ndv.getters.inputRunSelector().should('exist');
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '3 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '3 of 3');
|
||||
assertInputOutputText('Kyiv', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Prague', 'not.exist');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||
assertInputOutputText('Berlin', 'exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
assertInputOutputText('Prague', 'not.exist');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('1 of 3');
|
||||
assertInputOutputText('Prague', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
|
||||
ndv.actions.toggleInputRunLinking();
|
||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 3');
|
||||
ndv.getters.inputPanel().contains('Prague').should('exist');
|
||||
ndv.getters.inputPanel().contains('Berlin').should('not.exist');
|
||||
|
||||
ndv.getters.outputPanel().contains('Berlin').should('exist');
|
||||
ndv.getters.outputPanel().contains('Prague').should('not.exist');
|
||||
|
||||
ndv.actions.toggleInputRunLinking();
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
assertInputOutputText('Prague', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
});
|
||||
|
||||
it('should show tool info notice if no existing tools were used during execution', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
AGENT_NODE_NAME,
|
||||
true,
|
||||
);
|
||||
|
||||
clickCreateNewCredential();
|
||||
setCredentialValues({
|
||||
apiKey: 'sk_test_123',
|
||||
});
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME);
|
||||
clickGetBackToCanvas();
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
const inputMessage = 'Hello!';
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => sendManualChatMessage(inputMessage),
|
||||
runData: [
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { output: outputMessage },
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
}),
|
||||
],
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
closeManualChatModal();
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
getRunDataInfoCallout().should('exist');
|
||||
});
|
||||
|
||||
it('should not show tool info notice if tools were used during execution', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
getRunDataInfoCallout().should('not.exist');
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
AGENT_NODE_NAME,
|
||||
true,
|
||||
);
|
||||
|
||||
clickCreateNewCredential();
|
||||
setCredentialValues({
|
||||
apiKey: 'sk_test_123',
|
||||
});
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME);
|
||||
clickGetBackToCanvas();
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
getRunDataInfoCallout().should('not.exist');
|
||||
|
||||
const inputMessage = 'Hello!';
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => sendManualChatMessage(inputMessage),
|
||||
runData: [
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { output: outputMessage },
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AI_TOOL_CALCULATOR_NODE_NAME, {}),
|
||||
],
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
closeManualChatModal();
|
||||
openNode(AGENT_NODE_NAME);
|
||||
// This waits to ensure the output panel is rendered
|
||||
getOutputPanelTable();
|
||||
|
||||
getRunDataInfoCallout().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
import workflow from '../fixtures/Manual_wait_set.json';
|
||||
import { getOutputTableRow } from '../composables/ndv';
|
||||
import { getCanvasNodes, openNode } from '../composables/workflow';
|
||||
import SIMPLE_WORKFLOW from '../fixtures/Manual_wait_set.json';
|
||||
import WORKFLOW_WITH_PINNED from '../fixtures/Webhook_set_pinned.json';
|
||||
import { importWorkflow, visitDemoPage } from '../pages/demo';
|
||||
import { errorToast } from '../pages/notifications';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
describe('Demo', () => {
|
||||
beforeEach(() => {
|
||||
cy.overrideSettings({ previewMode: true });
|
||||
cy.signout();
|
||||
});
|
||||
|
||||
it('can import template', () => {
|
||||
visitDemoPage();
|
||||
errorToast().should('not.exist');
|
||||
importWorkflow(workflow);
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
importWorkflow(SIMPLE_WORKFLOW);
|
||||
getCanvasNodes().should('have.length', 3);
|
||||
});
|
||||
|
||||
it('can import workflow with pin data', () => {
|
||||
visitDemoPage();
|
||||
importWorkflow(WORKFLOW_WITH_PINNED);
|
||||
getCanvasNodes().should('have.length', 2);
|
||||
openNode('Webhook');
|
||||
getOutputTableRow(0).should('include.text', 'headers');
|
||||
getOutputTableRow(1).should('include.text', 'dragons');
|
||||
});
|
||||
|
||||
it('can override theme to dark', () => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as formStep from '../composables/setup-template-form-step';
|
|||
import { getSetupWorkflowCredentialsButton } from '../composables/setup-workflow-credentials-button';
|
||||
import TestTemplate1 from '../fixtures/Test_Template_1.json';
|
||||
import TestTemplate2 from '../fixtures/Test_Template_2.json';
|
||||
import { clearNotifications } from '../pages/notifications';
|
||||
import {
|
||||
clickUseWorkflowButtonByTitle,
|
||||
visitTemplateCollectionPage,
|
||||
|
@ -56,10 +57,10 @@ describe('Template credentials setup', () => {
|
|||
it('can be opened from template collection page', () => {
|
||||
visitTemplateCollectionPage(testData.ecommerceStarterPack);
|
||||
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
|
||||
clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram');
|
||||
clickUseWorkflowButtonByTitle('Promote new Shopify products');
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
.title("Set up 'Promote new Shopify products on Twitter and Telegram' template")
|
||||
.title("Set up 'Promote new Shopify products' template")
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
|
@ -67,7 +68,7 @@ describe('Template credentials setup', () => {
|
|||
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
.title("Set up 'Promote new Shopify products on Twitter and Telegram' template")
|
||||
.title("Set up 'Promote new Shopify products' template")
|
||||
.should('be.visible');
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
|
@ -111,16 +112,19 @@ describe('Template credentials setup', () => {
|
|||
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
|
||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');
|
||||
|
||||
clearNotifications();
|
||||
|
||||
templateCredentialsSetupPage.finishCredentialSetup();
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
|
||||
// Focus the canvas so the copy to clipboard works
|
||||
workflowPage.getters.canvasNodes().eq(0).realClick();
|
||||
workflowPage.actions.hitSelectAll();
|
||||
workflowPage.actions.hitCopy();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
// Check workflow JSON by copying it to clipboard
|
||||
cy.readClipboard().then((workflowJSON) => {
|
||||
const workflow = JSON.parse(workflowJSON);
|
||||
|
@ -154,6 +158,8 @@ describe('Template credentials setup', () => {
|
|||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Email (IMAP)');
|
||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Nextcloud');
|
||||
|
||||
clearNotifications();
|
||||
|
||||
templateCredentialsSetupPage.finishCredentialSetup();
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
|
@ -176,6 +182,8 @@ describe('Template credentials setup', () => {
|
|||
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify');
|
||||
|
||||
clearNotifications();
|
||||
|
||||
templateCredentialsSetupPage.finishCredentialSetup();
|
||||
|
||||
getSetupWorkflowCredentialsButton().should('be.visible');
|
||||
|
@ -192,6 +200,8 @@ describe('Template credentials setup', () => {
|
|||
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
|
||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');
|
||||
|
||||
clearNotifications();
|
||||
|
||||
setupCredsModal.closeModalFromContinueButton();
|
||||
setupCredsModal.getWorkflowCredentialsModal().should('not.exist');
|
||||
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import { SettingsPage } from '../pages/settings';
|
||||
|
||||
const settingsPage = new SettingsPage();
|
||||
const url = '/settings';
|
||||
|
||||
describe('Admin user', { disableAutoLogin: true }, () => {
|
||||
it('should see same Settings sub menu items as instance owner', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.visit(settingsPage.url);
|
||||
cy.visit(url);
|
||||
|
||||
let ownerMenuItems = 0;
|
||||
|
||||
settingsPage.getters.menuItems().then(($el) => {
|
||||
cy.getByTestId('menu-item').then(($el) => {
|
||||
ownerMenuItems = $el.length;
|
||||
});
|
||||
|
||||
cy.signout();
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(settingsPage.url);
|
||||
cy.visit(url);
|
||||
|
||||
settingsPage.getters.menuItems().should('have.length', ownerMenuItems);
|
||||
cy.getByTestId('menu-item').should('have.length', ownerMenuItems);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
NDV,
|
||||
MainSidebar,
|
||||
} from '../pages';
|
||||
import { clearNotifications } from '../pages/notifications';
|
||||
import { clearNotifications, successToast } from '../pages/notifications';
|
||||
import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
@ -51,7 +51,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
projects.getHomeButton().click();
|
||||
projects.getProjectTabs().should('have.length', 2);
|
||||
projects.getProjectTabs().should('have.length', 3);
|
||||
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||
|
@ -101,7 +101,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
|
||||
projects.getMenuItems().first().click();
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
projects.getProjectTabs().should('have.length', 3);
|
||||
projects.getProjectTabs().should('have.length', 4);
|
||||
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
|
@ -176,7 +176,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
let menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Home")[class*=active_]').should('exist');
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('exist');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
|
||||
|
@ -186,7 +186,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
|
||||
cy.intercept('GET', '/rest/workflows/*').as('loadWorkflow');
|
||||
workflowsPage.getters.workflowCards().first().click();
|
||||
workflowsPage.getters.workflowCards().first().findChildByTestId('card-content').click();
|
||||
|
||||
cy.wait('@loadWorkflow');
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
@ -222,7 +222,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Home")[class*=active_]').should('exist');
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('exist');
|
||||
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2).first().click();
|
||||
|
||||
|
@ -230,7 +230,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
cy.getByTestId('execute-workflow-button').should('be.visible');
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
menuItems.filter(':contains("Home")[class*=active_]').should('not.exist');
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('not.exist');
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
|
@ -367,7 +367,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -382,7 +382,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -396,7 +396,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -407,7 +407,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -425,7 +425,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -436,7 +436,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
});
|
||||
|
@ -498,7 +498,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.contains('button', 'Move workflow')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -506,7 +506,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(':contains("Project 1")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||
clearNotifications();
|
||||
|
||||
workflowsPage.getters
|
||||
|
@ -524,7 +524,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.contains('button', 'Move workflow')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -532,7 +532,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(':contains("Project 2")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||
|
||||
// Move the workflow from Project 2 to a member user
|
||||
projects.getMenuItems().last().click();
|
||||
|
@ -544,7 +544,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.contains('button', 'Move workflow')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -553,7 +553,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.filter(`:contains("${INSTANCE_MEMBERS[0].email}")`)
|
||||
.click();
|
||||
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
|
||||
// Move the workflow from member user back to Home
|
||||
|
@ -569,7 +569,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.contains('button', 'Move workflow')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -578,7 +578,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
||||
.click();
|
||||
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||
clearNotifications();
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
|
@ -596,7 +596,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.contains('button', 'Move credential')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -604,7 +604,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(':contains("Project 2")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||
clearNotifications();
|
||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||
|
||||
|
@ -619,7 +619,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.contains('button', 'Move credential')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -627,7 +627,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_ADMIN.email}")`)
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
|
||||
// Move the credential from admin user back to instance owner
|
||||
|
@ -641,7 +641,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.contains('button', 'Move credential')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -649,7 +649,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||
|
||||
clearNotifications();
|
||||
|
||||
|
@ -666,7 +666,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.contains('button', 'Move credential')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -674,7 +674,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 5)
|
||||
.filter(':contains("Project 1")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move credential').click();
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
|
@ -697,9 +697,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects.getHomeButton().click();
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
workflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -723,7 +721,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.contains('button', 'Move workflow')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
|
@ -731,7 +729,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('have.length', 4)
|
||||
.filter(':contains("Project 1")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
projects.getResourceMoveModal().contains('button', 'Move workflow').click();
|
||||
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
|
@ -749,7 +747,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
|
||||
// Open the moved workflow
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
workflowsPage.getters.workflowCards().first().click();
|
||||
workflowsPage.getters.workflowCards().first().findChildByTestId('card-content').click();
|
||||
|
||||
// Check if the credential can be changed
|
||||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
|
@ -785,7 +783,8 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password);
|
||||
cy.getByTestId('form-submit-button').click();
|
||||
|
||||
mainSidebar.getters.executions().click();
|
||||
projects.getMenuItems().last().click();
|
||||
projects.getProjectTabExecutions().click();
|
||||
cy.getByTestId('global-execution-list-item').first().find('td:last button').click();
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
|
@ -831,4 +830,23 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.should('not.have.length');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set and update project icon', () => {
|
||||
const DEFAULT_ICON = 'fa-layer-group';
|
||||
const NEW_PROJECT_NAME = 'Test Project';
|
||||
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(workflowsPage.url);
|
||||
projects.createProject(NEW_PROJECT_NAME);
|
||||
// New project should have default icon
|
||||
projects.getIconPickerButton().find('svg').should('have.class', DEFAULT_ICON);
|
||||
// Choose another icon
|
||||
projects.getIconPickerButton().click();
|
||||
projects.getIconPickerTab('Emojis').click();
|
||||
projects.getIconPickerEmojis().first().click();
|
||||
// Project should be updated with new icon
|
||||
successToast().contains('Project icon updated successfully');
|
||||
projects.getIconPickerButton().should('contain', '😀');
|
||||
projects.getMenuItems().contains(NEW_PROJECT_NAME).should('contain', '😀');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { clickGetBackToCanvas } from '../composables/ndv';
|
||||
import {
|
||||
addNodeToCanvas,
|
||||
addRetrieverNodeToParent,
|
||||
addVectorStoreNodeToParent,
|
||||
addVectorStoreToolToParent,
|
||||
getNodeCreatorItems,
|
||||
} from '../composables/workflow';
|
||||
import { IF_NODE_NAME } from '../constants';
|
||||
import { AGENT_NODE_NAME, IF_NODE_NAME, MANUAL_CHAT_TRIGGER_NODE_NAME } from '../constants';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
@ -74,11 +76,21 @@ describe('Node Creator', () => {
|
|||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
WorkflowPage.actions.addNodeToCanvas('Manual', false);
|
||||
|
||||
nodeCreatorFeature.getters.canvasAddButton().should('not.be.visible');
|
||||
nodeCreatorFeature.getters.nodeCreator().should('not.exist');
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().should('not.be.visible');
|
||||
nodeCreatorFeature.getters.nodeCreator().should('not.exist');
|
||||
// TODO: Replace once we have canvas feature utils
|
||||
cy.get('div').contains('Add first step').should('be.hidden');
|
||||
},
|
||||
() => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().should('not.exist');
|
||||
nodeCreatorFeature.getters.nodeCreator().should('not.exist');
|
||||
// TODO: Replace once we have canvas feature utils
|
||||
cy.get('div').contains('Add first step').should('not.exist');
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: Replace once we have canvas feature utils
|
||||
cy.get('div').contains('Add first step').should('be.hidden');
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.nodeCreator().contains('What happens next?').should('be.visible');
|
||||
|
||||
|
@ -344,7 +356,15 @@ describe('Node Creator', () => {
|
|||
|
||||
it('should correctly append a No Op node when Loop Over Items node is added (from connection)', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas('Manual');
|
||||
cy.get('.plus-endpoint').should('be.visible').click();
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
cy.get('.plus-endpoint').click();
|
||||
},
|
||||
() => {
|
||||
cy.getByTestId('canvas-handle-plus').click();
|
||||
},
|
||||
);
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('Loop Over Items');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Loop Over Items').click();
|
||||
|
@ -515,7 +535,7 @@ describe('Node Creator', () => {
|
|||
const actions = [
|
||||
'Get ranked documents from vector store',
|
||||
'Add documents to vector store',
|
||||
'Retrieve documents for AI processing',
|
||||
'Retrieve documents for Chain/Tool as Vector Store',
|
||||
];
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
|
@ -529,14 +549,14 @@ describe('Node Creator', () => {
|
|||
vectorStores.each((_i, vectorStore) => {
|
||||
nodeCreatorFeature.getters.getCreatorItem(vectorStore).click();
|
||||
actions.forEach((action) => {
|
||||
nodeCreatorFeature.getters.getCreatorItem(action).should('be.visible');
|
||||
nodeCreatorFeature.getters.getCreatorItem(action).should('be.visible').realHover();
|
||||
});
|
||||
cy.realPress('ArrowLeft');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should add node directly for sub-connection', () => {
|
||||
it('should add node directly for sub-connection as vector store', () => {
|
||||
addNodeToCanvas('Question and Answer Chain', true);
|
||||
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
||||
cy.realPress('Escape');
|
||||
|
@ -544,4 +564,12 @@ describe('Node Creator', () => {
|
|||
cy.realPress('Escape');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
});
|
||||
|
||||
it('should add node directly for sub-connection as tool', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addVectorStoreToolToParent('In-Memory Vector Store', AGENT_NODE_NAME);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import { WorkflowPage } from '../pages/workflow';
|
|||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
const NOW = 1717771477012;
|
||||
const NOW = Date.now();
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
const THREE_DAYS = ONE_DAY * 3;
|
||||
const SEVEN_DAYS = ONE_DAY * 7;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { getCredentialSaveButton } from '../composables/modals/credential-modal';
|
||||
import { CredentialsPage, CredentialsModal } from '../pages';
|
||||
|
||||
const credentialsPage = new CredentialsPage();
|
||||
|
@ -40,7 +41,7 @@ describe('Credentials', () => {
|
|||
});
|
||||
|
||||
// Check that the credential was saved and connected successfully
|
||||
credentialsModal.getters.saveButton().should('contain.text', 'Saved');
|
||||
getCredentialSaveButton().should('contain.text', 'Saved');
|
||||
credentialsModal.getters.oauthConnectSuccessBanner().should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,13 +4,14 @@ import { clickCreateNewCredential, openCredentialSelect } from '../composables/n
|
|||
import { GMAIL_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
||||
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
|
||||
import { AIAssistant } from '../pages/features/ai-assistant';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
|
||||
const wf = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
const aiAssistant = new AIAssistant();
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
const nodeCreatorFeature = new NodeCreator();
|
||||
|
||||
describe('AI Assistant::disabled', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -222,6 +223,54 @@ describe('AI Assistant::enabled', () => {
|
|||
.should('contain.text', 'item.json.myNewField = 1');
|
||||
});
|
||||
|
||||
it('Should ignore node execution success and error messages after the node run successfully once', () => {
|
||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||||
|
||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||||
|
||||
cy.intercept('POST', '/rest/ai/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/responses/code_diff_suggestion_response.json',
|
||||
}).as('chatRequest');
|
||||
|
||||
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
||||
wf.actions.openNode('Code');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
||||
cy.wait('@chatRequest');
|
||||
|
||||
cy.intercept('POST', '/rest/ai/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/responses/node_execution_succeeded_response.json',
|
||||
}).as('chatRequest2');
|
||||
|
||||
getEditor()
|
||||
.type('{selectall}')
|
||||
.paste(
|
||||
'for (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();',
|
||||
);
|
||||
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
|
||||
getEditor()
|
||||
.type('{selectall}')
|
||||
.paste(
|
||||
'for (const item of $input.all()) {\n item.json.myNewField = 1aaaa!;\n}\n\nreturn $input.all();',
|
||||
);
|
||||
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
|
||||
aiAssistant.getters.chatMessagesAssistant().should('have.length', 3);
|
||||
|
||||
aiAssistant.getters
|
||||
.chatMessagesAssistant()
|
||||
.eq(2)
|
||||
.should(
|
||||
'contain.text',
|
||||
'Code node ran successfully, did my solution help resolve your issue?\nQuick reply 👇Yes, thanksNo, I am still stuck',
|
||||
);
|
||||
});
|
||||
|
||||
it('should end chat session when `end_session` event is received', () => {
|
||||
cy.intercept('POST', '/rest/ai/chat', {
|
||||
statusCode: 200,
|
||||
|
@ -280,6 +329,20 @@ describe('AI Assistant::enabled', () => {
|
|||
wf.getters.isWorkflowSaved();
|
||||
aiAssistant.getters.placeholderMessage().should('not.exist');
|
||||
});
|
||||
|
||||
it('should send message via enter even with global NodeCreator panel opened', () => {
|
||||
cy.intercept('POST', '/rest/ai/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/responses/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
|
||||
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
aiAssistant.actions.openChat();
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
aiAssistant.getters.chatInput().type('Hello{Enter}');
|
||||
|
||||
aiAssistant.getters.placeholderMessage().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AI Assistant Credential Help', () => {
|
||||
|
@ -370,7 +433,7 @@ describe('AI Assistant Credential Help', () => {
|
|||
wf.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
wf.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
|
@ -403,7 +466,7 @@ describe('AI Assistant Credential Help', () => {
|
|||
wf.actions.addNodeToCanvas('Microsoft Outlook', true, true, 'Get a calendar');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
wf.getters.nodeCredentialsCreateOption().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
||||
|
@ -493,6 +556,8 @@ describe('General help', () => {
|
|||
}).as('chatRequest');
|
||||
|
||||
aiAssistant.getters.askAssistantFloatingButton().click();
|
||||
wf.getters.zoomToFitButton().click();
|
||||
|
||||
aiAssistant.actions.sendMessage('What is wrong with this workflow?');
|
||||
cy.wait('@chatRequest');
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('Workflow Selector Parameter', () => {
|
|||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
.should('have.length', 2);
|
||||
.should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should show required parameter warning', () => {
|
||||
|
@ -44,7 +44,8 @@ describe('Workflow Selector Parameter', () => {
|
|||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
.should('have.length', 1)
|
||||
.should('have.length', 2)
|
||||
.eq(1)
|
||||
.click();
|
||||
|
||||
ndv.getters
|
||||
|
@ -57,7 +58,7 @@ describe('Workflow Selector Parameter', () => {
|
|||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').first().click();
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(1).click();
|
||||
|
||||
ndv.getters.resourceLocatorInput('workflowId').find('a').should('exist');
|
||||
cy.getByTestId('radio-button-expression').eq(1).click();
|
||||
|
@ -68,7 +69,7 @@ describe('Workflow Selector Parameter', () => {
|
|||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').first().click();
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(1).click();
|
||||
ndv.getters
|
||||
.resourceLocatorModeSelector('workflowId')
|
||||
.find('input')
|
||||
|
@ -79,4 +80,28 @@ describe('Workflow Selector Parameter', () => {
|
|||
.find('input')
|
||||
.should('have.value', 'By ID');
|
||||
});
|
||||
|
||||
it('should render add resource option and redirect to the correct route when clicked', () => {
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').as('windowOpen');
|
||||
});
|
||||
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).should('exist');
|
||||
getVisiblePopper()
|
||||
.findChildByTestId('rlc-item')
|
||||
.eq(0)
|
||||
.find('span')
|
||||
.should('contain.text', 'Create a'); // Due to some inconsistency we're sometimes in a project and sometimes not, this covers both cases
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).click();
|
||||
|
||||
const SAMPLE_SUBWORKFLOW_TEMPLATE_ID = 'VMiAxXa3lCAizGB5f7dVZQSFfg3FtHkdTKvLuupqBls=';
|
||||
cy.get('@windowOpen').should(
|
||||
'be.calledWith',
|
||||
`/workflows/onboarding/${SAMPLE_SUBWORKFLOW_TEMPLATE_ID}?sampleSubWorkflows=0`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
140
cypress/e2e/47-subworkflow-debugging.cy.ts
Normal file
140
cypress/e2e/47-subworkflow-debugging.cy.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink,
|
||||
getExecutionsSidebar,
|
||||
getWorkflowExecutionPreviewIframe,
|
||||
openExecutionPreviewNode,
|
||||
} from '../composables/executions';
|
||||
import {
|
||||
changeOutputRunSelector,
|
||||
getOutputPanelItemsCount,
|
||||
getOutputPanelRelatedExecutionLink,
|
||||
getOutputRunSelectorInput,
|
||||
getOutputTableHeaders,
|
||||
getOutputTableRows,
|
||||
getOutputTbodyCell,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
clickExecuteWorkflowButton,
|
||||
clickZoomToFit,
|
||||
getCanvasNodes,
|
||||
navigateToNewWorkflowPage,
|
||||
openNode,
|
||||
pasteWorkflow,
|
||||
saveWorkflowOnButtonClick,
|
||||
} from '../composables/workflow';
|
||||
import SUBWORKFLOW_DEBUGGING_EXAMPLE from '../fixtures/Subworkflow-debugging-execute-workflow.json';
|
||||
|
||||
describe('Subworkflow debugging', () => {
|
||||
beforeEach(() => {
|
||||
navigateToNewWorkflowPage();
|
||||
pasteWorkflow(SUBWORKFLOW_DEBUGGING_EXAMPLE);
|
||||
saveWorkflowOnButtonClick();
|
||||
getCanvasNodes().should('have.length', 11);
|
||||
clickZoomToFit();
|
||||
|
||||
clickExecuteWorkflowButton();
|
||||
});
|
||||
|
||||
describe('can inspect sub executed workflow', () => {
|
||||
it('(Run once with all items/ Wait for Sub-workflow completion) (default behavior)', () => {
|
||||
openNode('Execute Workflow with param');
|
||||
|
||||
getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('contain.text', 'View sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
|
||||
|
||||
// ensure workflow executed and waited on output
|
||||
getOutputTableHeaders().should('have.length', 2);
|
||||
getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore');
|
||||
});
|
||||
|
||||
it('(Run once for each item/ Wait for Sub-workflow completion)', () => {
|
||||
openNode('Execute Workflow with param1');
|
||||
|
||||
getOutputPanelItemsCount().should('contain.text', '2 items, 2 sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('not.exist');
|
||||
|
||||
// ensure workflow executed and waited on output
|
||||
getOutputTableHeaders().should('have.length', 3);
|
||||
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
|
||||
getOutputTbodyCell(1, 1).should('have.text', 'world Natalie Moore');
|
||||
});
|
||||
|
||||
it('(Run once with all items/ Wait for Sub-workflow completion)', () => {
|
||||
openNode('Execute Workflow with param2');
|
||||
|
||||
getOutputPanelItemsCount().should('not.exist');
|
||||
getOutputPanelRelatedExecutionLink().should('contain.text', 'View sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
|
||||
|
||||
// ensure workflow executed but returned same data as input
|
||||
getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 1 sub-execution)');
|
||||
getOutputTableHeaders().should('have.length', 6);
|
||||
getOutputTableHeaders().eq(0).should('have.text', 'uid');
|
||||
getOutputTableRows().should('have.length', 4);
|
||||
getOutputTbodyCell(1, 1).should('include.text', 'Jon_Ebert@yahoo.com');
|
||||
|
||||
changeOutputRunSelector('1 of 2 (2 items, 1 sub-execution)');
|
||||
getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 1 sub-execution)');
|
||||
getOutputTableHeaders().should('have.length', 6);
|
||||
getOutputTableHeaders().eq(0).should('have.text', 'uid');
|
||||
getOutputTableRows().should('have.length', 3);
|
||||
getOutputTbodyCell(1, 1).should('include.text', 'Terry.Dach@hotmail.com');
|
||||
});
|
||||
|
||||
it('(Run once for each item/ Wait for Sub-workflow completion)', () => {
|
||||
openNode('Execute Workflow with param3');
|
||||
|
||||
// ensure workflow executed but returned same data as input
|
||||
getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 3 sub-executions)');
|
||||
getOutputTableHeaders().should('have.length', 7);
|
||||
getOutputTableHeaders().eq(1).should('have.text', 'uid');
|
||||
getOutputTableRows().should('have.length', 4);
|
||||
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
|
||||
getOutputTbodyCell(1, 2).should('include.text', 'Jon_Ebert@yahoo.com');
|
||||
|
||||
changeOutputRunSelector('1 of 2 (2 items, 2 sub-executions)');
|
||||
getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 2 sub-executions)');
|
||||
getOutputTableHeaders().should('have.length', 7);
|
||||
getOutputTableHeaders().eq(1).should('have.text', 'uid');
|
||||
getOutputTableRows().should('have.length', 3);
|
||||
|
||||
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
|
||||
getOutputTbodyCell(1, 2).should('include.text', 'Terry.Dach@hotmail.com');
|
||||
});
|
||||
});
|
||||
|
||||
it('can inspect parent executions', () => {
|
||||
cy.url().then((workflowUrl) => {
|
||||
openNode('Execute Workflow with param');
|
||||
|
||||
getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('contain.text', 'View sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
|
||||
|
||||
// ensure workflow executed and waited on output
|
||||
getOutputTableHeaders().should('have.length', 2);
|
||||
getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore');
|
||||
|
||||
// cypress cannot handle new tabs so removing it
|
||||
getOutputPanelRelatedExecutionLink().invoke('removeAttr', 'target').click();
|
||||
|
||||
getExecutionsSidebar().should('be.visible');
|
||||
getWorkflowExecutionPreviewIframe().should('be.visible');
|
||||
openExecutionPreviewNode('Execute Workflow Trigger');
|
||||
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink().should(
|
||||
'include.text',
|
||||
'View parent execution',
|
||||
);
|
||||
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink()
|
||||
.invoke('removeAttr', 'target')
|
||||
.click({ force: true });
|
||||
|
||||
cy.url().then((currentUrl) => {
|
||||
expect(currentUrl === workflowUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
242
cypress/e2e/48-subworkflow-inputs.cy.ts
Normal file
242
cypress/e2e/48-subworkflow-inputs.cy.ts
Normal file
|
@ -0,0 +1,242 @@
|
|||
import {
|
||||
addItemToFixedCollection,
|
||||
assertNodeOutputHintExists,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
getExecuteNodeButton,
|
||||
getOutputTableHeaders,
|
||||
getParameterInputByName,
|
||||
populateFixedCollection,
|
||||
selectResourceLocatorItem,
|
||||
typeIntoFixedCollectionItem,
|
||||
clickWorkflowCardContent,
|
||||
assertOutputTableContent,
|
||||
populateMapperFields,
|
||||
getNodeRunInfoStale,
|
||||
assertNodeOutputErrorMessageExists,
|
||||
checkParameterCheckboxInputByName,
|
||||
uncheckParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
clickExecuteWorkflowButton,
|
||||
clickZoomToFit,
|
||||
navigateToNewWorkflowPage,
|
||||
openNode,
|
||||
pasteWorkflow,
|
||||
saveWorkflowOnButtonClick,
|
||||
} from '../composables/workflow';
|
||||
import { visitWorkflowsPage } from '../composables/workflowsPage';
|
||||
import SUB_WORKFLOW_INPUTS from '../fixtures/Test_Subworkflow-Inputs.json';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
import { getVisiblePopper } from '../utils';
|
||||
|
||||
const DEFAULT_WORKFLOW_NAME = 'My workflow';
|
||||
const DEFAULT_SUBWORKFLOW_NAME_1 = 'My Sub-Workflow 1';
|
||||
const DEFAULT_SUBWORKFLOW_NAME_2 = 'My Sub-Workflow 2';
|
||||
|
||||
const EXAMPLE_FIELDS = [
|
||||
['aNumber', 'Number'],
|
||||
['aString', 'String'],
|
||||
['aArray', 'Array'],
|
||||
['aObject', 'Object'],
|
||||
['aAny', 'Allow Any Type'],
|
||||
// bool last because it's a switch instead of a normal inputField so we'll skip it for some cases
|
||||
['aBool', 'Boolean'],
|
||||
] as const;
|
||||
|
||||
type TypeField = 'Allow Any Type' | 'String' | 'Number' | 'Boolean' | 'Array' | 'Object';
|
||||
|
||||
describe('Sub-workflow creation and typed usage', () => {
|
||||
beforeEach(() => {
|
||||
navigateToNewWorkflowPage();
|
||||
pasteWorkflow(SUB_WORKFLOW_INPUTS);
|
||||
saveWorkflowOnButtonClick();
|
||||
clickZoomToFit();
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Prevent sub-workflow from opening in new window
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||
// **************************
|
||||
// NAVIGATE TO CHILD WORKFLOW
|
||||
// **************************
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
});
|
||||
|
||||
it('works with type-checked values', () => {
|
||||
populateFixedCollection(EXAMPLE_FIELDS, 'workflowInputs', 1);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
const values = [
|
||||
'-1', // number fields don't support `=` switch to expression, so let's test the Fixed case with it
|
||||
...EXAMPLE_FIELDS.slice(1).map((x) => `={{}{{} $json.a${x[0]}`), // the `}}` at the end are added automatically
|
||||
];
|
||||
|
||||
// this matches with the pinned data provided in the fixture
|
||||
populateMapperFields(values.map((x, i) => [EXAMPLE_FIELDS[i][0], x]));
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
const expected = [
|
||||
['-1', 'A String', '0:11:true2:3', 'aKey:-1', '[empty object]', 'false'],
|
||||
['-1', 'Another String', '[empty array]', 'aDifferentKey:-1', '[empty array]', 'false'],
|
||||
];
|
||||
assertOutputTableContent(expected);
|
||||
|
||||
// Test the type-checking options
|
||||
populateMapperFields([['aString', '{selectAll}{backspace}{{}{{} 5']]);
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
assertNodeOutputErrorMessageExists();
|
||||
|
||||
// attemptToConvertTypes enabled
|
||||
checkParameterCheckboxInputByName('attemptToConvertTypes');
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
const expected2 = [
|
||||
['-1', '5', '0:11:true2:3', 'aKey:-1', '[empty object]', 'false'],
|
||||
['-1', '5', '[empty array]', 'aDifferentKey:-1', '[empty array]', 'false'],
|
||||
];
|
||||
|
||||
assertOutputTableContent(expected2);
|
||||
|
||||
// disabled again
|
||||
uncheckParameterCheckboxInputByName('attemptToConvertTypes');
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
assertNodeOutputErrorMessageExists();
|
||||
});
|
||||
|
||||
it('works with Fields input source, and can then be changed to JSON input source', () => {
|
||||
assertNodeOutputHintExists();
|
||||
|
||||
populateFixedCollection(EXAMPLE_FIELDS, 'workflowInputs', 1);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
|
||||
getParameterInputByName('inputSource').click();
|
||||
|
||||
getVisiblePopper()
|
||||
.getByTestId('parameter-input')
|
||||
.eq(0)
|
||||
.type('Using JSON Example{downArrow}{enter}');
|
||||
|
||||
const exampleJson =
|
||||
'{{}' + EXAMPLE_FIELDS.map((x) => `"${x[0]}": ${makeExample(x[1])}`).join(',') + '}';
|
||||
getParameterInputByName('jsonExample')
|
||||
.find('.cm-line')
|
||||
.eq(0)
|
||||
.type(`{selectAll}{backspace}${exampleJson}{enter}`);
|
||||
|
||||
// first one doesn't work for some reason, might need to wait for something?
|
||||
clickExecuteNode();
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_2,
|
||||
2,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
assertOutputTableContent([
|
||||
['[null]', '[null]', '[null]', '[null]', '[null]', 'false'],
|
||||
['[null]', '[null]', '[null]', '[null]', '[null]', 'false'],
|
||||
]);
|
||||
|
||||
clickExecuteNode();
|
||||
});
|
||||
|
||||
it('should show node issue when no fields are defined in manual mode', () => {
|
||||
getExecuteNodeButton().should('be.disabled');
|
||||
clickGetBackToCanvas();
|
||||
// Executing the workflow should show an error toast
|
||||
clickExecuteWorkflowButton();
|
||||
errorToast().should('contain', 'The workflow has issues');
|
||||
openNode('Workflow Input Trigger');
|
||||
// Add a field to the workflowInputs fixedCollection
|
||||
addItemToFixedCollection('workflowInputs');
|
||||
typeIntoFixedCollectionItem('workflowInputs', 0, 'test');
|
||||
// Executing the workflow should not show error now
|
||||
clickGetBackToCanvas();
|
||||
clickExecuteWorkflowButton();
|
||||
successToast().should('contain', 'Workflow executed successfully');
|
||||
});
|
||||
});
|
||||
|
||||
// This function starts off in the Child Workflow Input Trigger, assuming we just defined the input fields
|
||||
// It then navigates back to the parent and validates the outputPanel matches our changes
|
||||
function validateAndReturnToParent(targetChild: string, offset: number, fields: string[]) {
|
||||
clickExecuteNode();
|
||||
|
||||
// + 1 to account for formatting-only column
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
|
||||
clickGetBackToCanvas();
|
||||
saveWorkflowOnButtonClick();
|
||||
|
||||
visitWorkflowsPage();
|
||||
|
||||
clickWorkflowCardContent(DEFAULT_WORKFLOW_NAME);
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Note that outside of e2e tests this will be pre-selected correctly.
|
||||
// Due to our workaround to remain in the same tab we need to select the correct tab manually
|
||||
selectResourceLocatorItem('workflowId', offset, targetChild);
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
}
|
||||
|
||||
function makeExample(type: TypeField) {
|
||||
switch (type) {
|
||||
case 'String':
|
||||
return '"example"';
|
||||
case 'Number':
|
||||
return '42';
|
||||
case 'Boolean':
|
||||
return 'true';
|
||||
case 'Array':
|
||||
return '["example", 123, null]';
|
||||
case 'Object':
|
||||
return '{{}"example": [123]}';
|
||||
case 'Allow Any Type':
|
||||
return 'null';
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import { clickCreateNewCredential } from '../composables/ndv';
|
||||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, NOTION_NODE_NAME } from '../constants';
|
||||
import { clickCreateNewCredential, setParameterSelectByContent } from '../composables/ndv';
|
||||
import {
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
NOTION_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
|
||||
|
@ -65,26 +69,6 @@ describe('NDV', () => {
|
|||
cy.shouldNotHaveConsoleErrors();
|
||||
});
|
||||
|
||||
it('should disconect Switch outputs if rules order was changed', () => {
|
||||
cy.createFixtureWorkflow('NDV-test-switch_reorder.json', 'NDV test switch reorder');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.executeWorkflow();
|
||||
workflowPage.actions.openNode('Merge');
|
||||
ndv.getters.outputPanel().contains('2 items').should('exist');
|
||||
cy.contains('span', 'first').should('exist');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
workflowPage.actions.openNode('Switch');
|
||||
cy.get('.cm-line').realMouseMove(100, 100);
|
||||
cy.get('.fa-angle-down').click();
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
workflowPage.actions.openNode('Merge');
|
||||
ndv.getters.outputPanel().contains('1 item').should('exist');
|
||||
cy.contains('span', 'zero').should('exist');
|
||||
});
|
||||
|
||||
it('should show correct validation state for resource locator params', () => {
|
||||
workflowPage.actions.addNodeToCanvas('Typeform', true, true);
|
||||
ndv.getters.container().should('be.visible');
|
||||
|
@ -111,6 +95,7 @@ describe('NDV', () => {
|
|||
cy.get('[class*=hasIssues]').should('have.length', 1);
|
||||
});
|
||||
|
||||
// Correctly failing in V2 - node issues are only shows after execution
|
||||
it('should show all validation errors when opening pasted node', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_ndv_errors.json', 'Validation errors');
|
||||
workflowPage.getters.canvasNodes().should('have.have.length', 1);
|
||||
|
@ -125,7 +110,10 @@ describe('NDV', () => {
|
|||
ndv.actions.execute();
|
||||
ndv.getters
|
||||
.nodeRunErrorMessage()
|
||||
.should('have.text', 'Info for expression missing from previous node');
|
||||
.should(
|
||||
'have.text',
|
||||
"Using the item method doesn't work with pinned data in this scenario. Please unpin 'Break pairedItem chain' and try again.",
|
||||
);
|
||||
ndv.getters
|
||||
.nodeRunErrorDescription()
|
||||
.should(
|
||||
|
@ -204,7 +192,7 @@ describe('NDV', () => {
|
|||
.contains(key)
|
||||
.should('be.visible');
|
||||
});
|
||||
getObjectValueItem().find('label').click({ force: true });
|
||||
getObjectValueItem().find('.toggle').click({ force: true });
|
||||
expandedObjectProps.forEach((key) => {
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
|
@ -213,9 +201,11 @@ describe('NDV', () => {
|
|||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display pagination for schema', () => {
|
||||
setupSchemaWorkflow();
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.deselectAll();
|
||||
workflowPage.getters.canvasNodeByName('Set').click();
|
||||
workflowPage.actions.addNodeToCanvas(
|
||||
'Customer Datastore (n8n training)',
|
||||
|
@ -245,8 +235,8 @@ describe('NDV', () => {
|
|||
ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
.find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]')
|
||||
.should('have.length', 20);
|
||||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('have.length.above', 10);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -376,15 +366,71 @@ describe('NDV', () => {
|
|||
ndv.getters.nodeExecuteButton().should('be.visible');
|
||||
});
|
||||
|
||||
it('should allow editing code in fullscreen in the Code node', () => {
|
||||
it('should allow editing code in fullscreen in the code editors', () => {
|
||||
// Code (JavaScript)
|
||||
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
|
||||
ndv.actions.openCodeEditorFullscreen();
|
||||
|
||||
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
|
||||
ndv.getters.codeEditorFullscreen().should('contain.text', 'foo()');
|
||||
cy.wait(200);
|
||||
cy.wait(200); // allow change to emit before closing modal
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters.parameterInput('jsCode').get('.cm-content').should('contain.text', 'foo()');
|
||||
ndv.actions.close();
|
||||
|
||||
// SQL
|
||||
workflowPage.actions.addNodeToCanvas('Postgres', true, true, 'Execute a SQL query');
|
||||
ndv.actions.openCodeEditorFullscreen();
|
||||
|
||||
ndv.getters
|
||||
.codeEditorFullscreen()
|
||||
.type('{selectall}')
|
||||
.type('{backspace}')
|
||||
.type('SELECT * FROM workflows');
|
||||
ndv.getters.codeEditorFullscreen().should('contain.text', 'SELECT * FROM workflows');
|
||||
cy.wait(200);
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters
|
||||
.parameterInput('query')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', 'SELECT * FROM workflows');
|
||||
ndv.actions.close();
|
||||
|
||||
// HTML
|
||||
workflowPage.actions.addNodeToCanvas('HTML', true, true, 'Generate HTML template');
|
||||
ndv.actions.openCodeEditorFullscreen();
|
||||
|
||||
ndv.getters
|
||||
.codeEditorFullscreen()
|
||||
.type('{selectall}')
|
||||
.type('{backspace}')
|
||||
.type('<div>Hello World');
|
||||
ndv.getters.codeEditorFullscreen().should('contain.text', '<div>Hello World</div>');
|
||||
cy.wait(200);
|
||||
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters
|
||||
.parameterInput('html')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', '<div>Hello World</div>');
|
||||
ndv.actions.close();
|
||||
|
||||
// JSON
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||
setParameterSelectByContent('mode', 'JSON');
|
||||
ndv.actions.openCodeEditorFullscreen();
|
||||
ndv.getters
|
||||
.codeEditorFullscreen()
|
||||
.type('{selectall}')
|
||||
.type('{backspace}')
|
||||
.type('{ "key": "value" }', { parseSpecialCharSequences: false });
|
||||
ndv.getters.codeEditorFullscreen().should('contain.text', '{ "key": "value" }');
|
||||
cy.wait(200);
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters
|
||||
.parameterInput('jsonOutput')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', '{ "key": "value" }');
|
||||
});
|
||||
|
||||
it('should not retrieve remote options when a parameter value changes', () => {
|
||||
|
@ -407,8 +453,18 @@ describe('NDV', () => {
|
|||
return cy.get(`[data-node-placement=${position}]`);
|
||||
}
|
||||
|
||||
// Correctly failing in V2 - due to floating navigation not updating the selected node
|
||||
it('should traverse floating nodes with mouse', () => {
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => {},
|
||||
() => {
|
||||
// Needed in V2 as all nodes remain selected when clicking on a selected node
|
||||
workflowPage.actions.deselectAll();
|
||||
},
|
||||
);
|
||||
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
|
@ -419,6 +475,7 @@ describe('NDV', () => {
|
|||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
ndv.actions.close();
|
||||
// These two lines are broken in V2
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
|
@ -426,10 +483,8 @@ describe('NDV', () => {
|
|||
.should('contain', `Node ${i + 1}`);
|
||||
workflowPage.getters.selectedNodes().first().dblclick();
|
||||
});
|
||||
|
||||
getFloatingNodeByPosition('outputMain').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Chain');
|
||||
|
||||
// Traverse 4 connected node backwards
|
||||
Array.from(Array(4).keys()).forEach((i) => {
|
||||
getFloatingNodeByPosition('inputMain').click({ force: true });
|
||||
|
@ -453,8 +508,17 @@ describe('NDV', () => {
|
|||
.should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
});
|
||||
|
||||
// Correctly failing in V2 - due to floating navigation not updating the selected node
|
||||
it('should traverse floating nodes with keyboard', () => {
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
|
||||
cy.ifCanvasVersion(
|
||||
() => {},
|
||||
() => {
|
||||
// Needed in V2 as all nodes remain selected when clicking on a selected node
|
||||
workflowPage.actions.deselectAll();
|
||||
},
|
||||
);
|
||||
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
|
@ -465,6 +529,7 @@ describe('NDV', () => {
|
|||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
ndv.actions.close();
|
||||
// These two lines are broken in V2
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
|
@ -492,6 +557,7 @@ describe('NDV', () => {
|
|||
getFloatingNodeByPosition('inputSub').should('not.exist');
|
||||
getFloatingNodeByPosition('outputSub').should('not.exist');
|
||||
ndv.actions.close();
|
||||
// These two lines are broken in V2
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
|
@ -717,6 +783,7 @@ describe('NDV', () => {
|
|||
.should('have.value', 'Error fetching options from Notion');
|
||||
});
|
||||
|
||||
// Correctly failing in V2 - NodeCreator is not opened after clicking on the link
|
||||
it('Should open appropriate node creator after clicking on connection hint link', () => {
|
||||
const nodeCreator = new NodeCreator();
|
||||
const hintMapper = {
|
||||
|
@ -734,6 +801,7 @@ describe('NDV', () => {
|
|||
|
||||
Object.entries(hintMapper).forEach(([node, group]) => {
|
||||
workflowPage.actions.openNode(node);
|
||||
// This fails to open the NodeCreator
|
||||
cy.get('[data-action=openSelectiveNodeCreator]').contains('Insert one').click();
|
||||
nodeCreator.getters.activeSubcategory().should('contain', group);
|
||||
cy.realPress('Escape');
|
||||
|
@ -795,4 +863,60 @@ describe('NDV', () => {
|
|||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('contain.text', 'onlyOnItem3');
|
||||
});
|
||||
|
||||
it('should keep search expanded after Test step node run', () => {
|
||||
cy.createFixtureWorkflow('Test_ndv_search.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
workflowPage.actions.openNode('Edit Fields');
|
||||
ndv.getters.outputPanel().should('be.visible');
|
||||
ndv.getters.outputPanel().findChildByTestId('ndv-search').click().type('US');
|
||||
ndv.getters.outputTableRow(1).find('mark').should('have.text', 'US');
|
||||
|
||||
ndv.actions.execute();
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
.findChildByTestId('ndv-search')
|
||||
.should('be.visible')
|
||||
.should('have.value', 'US');
|
||||
});
|
||||
|
||||
it('should not show items count when seaching in schema view', () => {
|
||||
cy.createFixtureWorkflow('Test_ndv_search.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.openNode('Edit Fields');
|
||||
ndv.getters.outputPanel().should('be.visible');
|
||||
ndv.actions.execute();
|
||||
ndv.actions.switchOutputMode('Schema');
|
||||
ndv.getters.outputPanel().find('[data-test-id=ndv-search]').click().type('US');
|
||||
ndv.getters.outputPanel().find('[data-test-id=ndv-items-count]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should show additional tooltip when seaching in schema view if no matches', () => {
|
||||
cy.createFixtureWorkflow('Test_ndv_search.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.openNode('Edit Fields');
|
||||
ndv.getters.outputPanel().should('be.visible');
|
||||
ndv.actions.execute();
|
||||
ndv.actions.switchOutputMode('Schema');
|
||||
ndv.getters.outputPanel().find('[data-test-id=ndv-search]').click().type('foo');
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
.contains('To search field contents rather than just names, use Table or JSON view')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('ADO-2931 - should handle multiple branches of the same input with the first branch empty correctly', () => {
|
||||
cy.createFixtureWorkflow('Test_ndv_two_branches_of_same_parent_false_populated.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.openNode('DebugHelper');
|
||||
ndv.getters.inputPanel().should('be.visible');
|
||||
ndv.getters.outputPanel().should('be.visible');
|
||||
ndv.actions.execute();
|
||||
// This ensures we rendered the inputPanel
|
||||
ndv.getters
|
||||
.inputPanel()
|
||||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('contain.text', 'a1');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@ for (const item of $input.all()) {
|
|||
|
||||
return
|
||||
`);
|
||||
getParameter().get('.cm-lint-marker-error').should('have.length', 6);
|
||||
getParameter().get('.cm-lintRange-error').should('have.length', 6);
|
||||
getParameter().contains('itemMatching').realHover();
|
||||
cy.get('.cm-tooltip-lint').should(
|
||||
'have.text',
|
||||
|
@ -81,7 +81,7 @@ $input.item()
|
|||
return []
|
||||
`);
|
||||
|
||||
getParameter().get('.cm-lint-marker-error').should('have.length', 5);
|
||||
getParameter().get('.cm-lintRange-error').should('have.length', 5);
|
||||
getParameter().contains('all').realHover();
|
||||
cy.get('.cm-tooltip-lint').should(
|
||||
'have.text',
|
||||
|
|
|
@ -171,9 +171,16 @@ describe('Workflow Actions', () => {
|
|||
cy.get('#node-creator').should('not.exist');
|
||||
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
cy.get('.jtk-drag-selected').should('have.length', 2);
|
||||
WorkflowPage.actions.hitCopy();
|
||||
successToast().should('exist');
|
||||
// Both nodes should be copied
|
||||
cy.window()
|
||||
.its('navigator.clipboard')
|
||||
.then((clip) => clip.readText())
|
||||
.then((text) => {
|
||||
const copiedWorkflow = JSON.parse(text);
|
||||
expect(copiedWorkflow.nodes).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should paste nodes (both current and old node versions)', () => {
|
||||
|
@ -345,7 +352,15 @@ describe('Workflow Actions', () => {
|
|||
WorkflowPage.actions.hitDeleteAllNodes();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 0);
|
||||
// Button should be disabled
|
||||
WorkflowPage.getters.executeWorkflowButton().should('be.disabled');
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
WorkflowPage.getters.executeWorkflowButton().should('be.disabled');
|
||||
},
|
||||
() => {
|
||||
// In new canvas, button does not exist when there are no nodes
|
||||
WorkflowPage.getters.executeWorkflowButton().should('not.exist');
|
||||
},
|
||||
);
|
||||
// Keyboard shortcut should not work
|
||||
WorkflowPage.actions.hitExecuteWorkflow();
|
||||
successToast().should('not.exist');
|
||||
|
|
File diff suppressed because one or more lines are too long
347
cypress/fixtures/In_memory_vector_store_fake_embeddings.json
Normal file
347
cypress/fixtures/In_memory_vector_store_fake_embeddings.json
Normal file
File diff suppressed because one or more lines are too long
354
cypress/fixtures/Subworkflow-debugging-execute-workflow.json
Normal file
354
cypress/fixtures/Subworkflow-debugging-execute-workflow.json
Normal file
|
@ -0,0 +1,354 @@
|
|||
{
|
||||
"meta": {
|
||||
"instanceId": "08ce71ad998aeaade0abedb8dd96153d8eaa03fcb84cfccc1530095bf9ee478e"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "4535ce3e-280e-49b0-8854-373472ec86d1",
|
||||
"name": "When clicking ‘Test workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [80, 860]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"category": "randomData",
|
||||
"randomDataSeed": "0",
|
||||
"randomDataCount": 2
|
||||
},
|
||||
"id": "d7fba18a-d51f-4509-af45-68cd9425ac6b",
|
||||
"name": "DebugHelper1",
|
||||
"type": "n8n-nodes-base.debugHelper",
|
||||
"typeVersion": 1,
|
||||
"position": [280, 860]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"source": "parameter",
|
||||
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
|
||||
"mode": "each",
|
||||
"options": {
|
||||
"waitForSubWorkflow": false
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.1,
|
||||
"position": [680, 1540],
|
||||
"id": "f90a25da-dd89-4bf8-8f5b-bf8ee1de0b70",
|
||||
"name": "Execute Workflow with param3"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "c93f26bd-3489-467b-909e-6462e1463707",
|
||||
"name": "uid",
|
||||
"value": "={{ $json.uid }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
|
||||
"name": "email",
|
||||
"value": "={{ $json.email }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [900, 1540],
|
||||
"id": "3be57648-3be8-4b0f-abfa-8fdcafee804d",
|
||||
"name": "Edit Fields8"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"source": "parameter",
|
||||
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
|
||||
"options": {
|
||||
"waitForSubWorkflow": false
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.1,
|
||||
"position": [620, 1220],
|
||||
"id": "dabc2356-3660-4d17-b305-936a002029ba",
|
||||
"name": "Execute Workflow with param2"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "c93f26bd-3489-467b-909e-6462e1463707",
|
||||
"name": "uid",
|
||||
"value": "={{ $json.uid }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
|
||||
"name": "email",
|
||||
"value": "={{ $json.email }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [840, 1220],
|
||||
"id": "9d2a9dda-e2a1-43e8-a66f-a8a555692e5f",
|
||||
"name": "Edit Fields7"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"source": "parameter",
|
||||
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
|
||||
"mode": "each",
|
||||
"options": {
|
||||
"waitForSubWorkflow": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.1,
|
||||
"position": [560, 900],
|
||||
"id": "07e47f60-622a-484c-ab24-35f6f2280595",
|
||||
"name": "Execute Workflow with param1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "c93f26bd-3489-467b-909e-6462e1463707",
|
||||
"name": "uid",
|
||||
"value": "={{ $json.uid }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
|
||||
"name": "email",
|
||||
"value": "={{ $json.email }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [760, 900],
|
||||
"id": "80563d0a-0bab-444f-a04c-4041a505d78b",
|
||||
"name": "Edit Fields6"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"source": "parameter",
|
||||
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
|
||||
"options": {
|
||||
"waitForSubWorkflow": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.1,
|
||||
"position": [560, 580],
|
||||
"id": "f04af481-f4d9-4d91-a60a-a377580e8393",
|
||||
"name": "Execute Workflow with param"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "c93f26bd-3489-467b-909e-6462e1463707",
|
||||
"name": "uid",
|
||||
"value": "={{ $json.uid }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
|
||||
"name": "email",
|
||||
"value": "={{ $json.email }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [760, 580],
|
||||
"id": "80c10607-a0ac-4090-86a1-890da0a2aa52",
|
||||
"name": "Edit Fields2"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Execute Workflow (Run once with all items/ DONT Wait for Sub-workflow completion)",
|
||||
"height": 254.84308966329985,
|
||||
"width": 457.58120569815793
|
||||
},
|
||||
"id": "534ef523-3453-4a16-9ff0-8ac9f025d47d",
|
||||
"name": "Sticky Note5",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [500, 1080]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Execute Workflow (Run once with for each item/ DONT Wait for Sub-workflow completion) ",
|
||||
"height": 284.59778445962905,
|
||||
"width": 457.58120569815793
|
||||
},
|
||||
"id": "838f0fa3-5ee4-4d1a-afb8-42e009f1aa9e",
|
||||
"name": "Sticky Note4",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 1400]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"category": "randomData",
|
||||
"randomDataSeed": "1",
|
||||
"randomDataCount": 3
|
||||
},
|
||||
"id": "86699a49-2aa7-488e-8ea9-828404c98f08",
|
||||
"name": "DebugHelper",
|
||||
"type": "n8n-nodes-base.debugHelper",
|
||||
"typeVersion": 1,
|
||||
"position": [320, 1120]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Execute Workflow (Run once with for each item/ Wait for Sub-workflow completion) ",
|
||||
"height": 284.59778445962905,
|
||||
"width": 457.58120569815793
|
||||
},
|
||||
"id": "885d35f0-8ae6-45ec-821b-a82c27e7577a",
|
||||
"name": "Sticky Note3",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [480, 760]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Execute Workflow (Run once with all items/ Wait for Sub-workflow completion) (default behavior)",
|
||||
"height": 254.84308966329985,
|
||||
"width": 457.58120569815793
|
||||
},
|
||||
"id": "505bd7f2-767e-41b8-9325-77300aed5883",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [460, 460]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "DebugHelper1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "DebugHelper",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"DebugHelper1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Workflow with param3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Execute Workflow with param2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Execute Workflow with param1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Execute Workflow with param",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow with param3": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields8",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow with param2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields7",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow with param1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields6",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow with param": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"DebugHelper": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Workflow with param2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Execute Workflow with param3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
85
cypress/fixtures/Switch_node_with_null_connection.json
Normal file
85
cypress/fixtures/Switch_node_with_null_connection.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "418350b8-b402-4d3b-93ba-3794d36c1ad5",
|
||||
"name": "When clicking \"Test workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [440, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "b67ad46f-6b0d-4ff4-b2d2-dfbde44e287c",
|
||||
"name": "Switch",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [660, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "24731c11-e2a4-4854-81a6-277ce72e8a93",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.3,
|
||||
"position": [840, 480]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking \"Test workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Switch",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Switch": {
|
||||
"main": [
|
||||
null,
|
||||
null,
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
69
cypress/fixtures/Test_Subworkflow-Inputs.json
Normal file
69
cypress/fixtures/Test_Subworkflow-Inputs.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"meta": {
|
||||
"instanceId": "4d0676b62208d810ef035130bbfc9fd3afdc78d963ea8ccb9514dc89066efc94"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "bb7f8bb3-840a-464c-a7de-d3a80538c2be",
|
||||
"name": "When clicking ‘Test workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.2,
|
||||
"position": [500, 240],
|
||||
"id": "6b6e2e34-c6ab-4083-b8e3-6b0d56be5453",
|
||||
"name": "Execute Workflow"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Workflow",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"When clicking ‘Test workflow’": [
|
||||
{
|
||||
"aaString": "A String",
|
||||
"aaNumber": 1,
|
||||
"aaArray": [1, true, "3"],
|
||||
"aaObject": {
|
||||
"aKey": -1
|
||||
},
|
||||
"aaAny": {}
|
||||
},
|
||||
{
|
||||
"aaString": "Another String",
|
||||
"aaNumber": 2,
|
||||
"aaArray": [],
|
||||
"aaObject": {
|
||||
"aDifferentKey": -1
|
||||
},
|
||||
"aaAny": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"workflow": {
|
||||
"id": 1205,
|
||||
"name": "Promote new Shopify products on Twitter and Telegram",
|
||||
"name": "Promote new Shopify products",
|
||||
"views": 478,
|
||||
"recentViews": 9880,
|
||||
"totalViews": 478,
|
||||
|
|
135
cypress/fixtures/Test_ndv_search.json
Normal file
135
cypress/fixtures/Test_ndv_search.json
Normal file
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"name": "NDV search bugs (introduced by schema view?)",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "55635c7b-92ee-4d2d-a0c0-baff9ab071da",
|
||||
"name": "When clicking ‘Test workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"position": [
|
||||
800,
|
||||
380
|
||||
],
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAllPeople"
|
||||
},
|
||||
"id": "4737af43-e49b-4c92-b76f-32605c047114",
|
||||
"name": "Customer Datastore (n8n training)",
|
||||
"type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1020,
|
||||
380
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": []
|
||||
},
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"id": "8cc9b374-1856-4f3f-9315-08e6e27840d8",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
1240,
|
||||
380
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Customer Datastore (n8n training)": [
|
||||
{
|
||||
"json": {
|
||||
"id": "23423532",
|
||||
"name": "Jay Gatsby",
|
||||
"email": "gatsby@west-egg.com",
|
||||
"notes": "Keeps asking about a green light??",
|
||||
"country": "US",
|
||||
"created": "1925-04-10"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "23423533",
|
||||
"name": "José Arcadio Buendía",
|
||||
"email": "jab@macondo.co",
|
||||
"notes": "Lots of people named after him. Very confusing",
|
||||
"country": "CO",
|
||||
"created": "1967-05-05"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "23423534",
|
||||
"name": "Max Sendak",
|
||||
"email": "info@in-and-out-of-weeks.org",
|
||||
"notes": "Keeps rolling his terrible eyes",
|
||||
"country": "US",
|
||||
"created": "1963-04-09"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "23423535",
|
||||
"name": "Zaphod Beeblebrox",
|
||||
"email": "captain@heartofgold.com",
|
||||
"notes": "Felt like I was talking to more than one person",
|
||||
"country": null,
|
||||
"created": "1979-10-12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "23423536",
|
||||
"name": "Edmund Pevensie",
|
||||
"email": "edmund@narnia.gov",
|
||||
"notes": "Passionate sailor",
|
||||
"country": "UK",
|
||||
"created": "1950-10-16"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Customer Datastore (n8n training)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Customer Datastore (n8n training)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "20178044-fb64-4443-88dd-e941517520d0",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
|
||||
},
|
||||
"id": "aBVnTRON9Y2cSmse",
|
||||
"tags": []
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "6f0cf983-824b-4339-a5de-6b374a23b4b0",
|
||||
"leftValue": "={{ $json.a }}",
|
||||
"rightValue": 3,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [220, 0],
|
||||
"id": "1755282a-ec4a-4d02-a833-0316ca413cc4",
|
||||
"name": "If"
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "de1e7acf-12d8-4e56-ba42-709ffb397db2",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"category": "randomData"
|
||||
},
|
||||
"type": "n8n-nodes-base.debugHelper",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 0],
|
||||
"id": "86440d33-f833-453c-bcaa-fff7e0083501",
|
||||
"name": "DebugHelper",
|
||||
"alwaysOutputData": true
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "DebugHelper",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "DebugHelper",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"When clicking ‘Test workflow’": [
|
||||
{
|
||||
"a": 1
|
||||
},
|
||||
{
|
||||
"a": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "**Code** node ran successfully, did my solution help resolve your issue?",
|
||||
"quickReplies": [
|
||||
{
|
||||
"text": "Yes, thanks",
|
||||
"type": "all-good",
|
||||
"isFeedback": true
|
||||
},
|
||||
{
|
||||
"text": "No, I am still stuck",
|
||||
"type": "still-stuck",
|
||||
"isFeedback": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1202,7 +1202,7 @@
|
|||
},
|
||||
{
|
||||
"id": 1205,
|
||||
"name": "Promote New Shopify Products on Social Media (Twitter and Telegram)",
|
||||
"name": "Promote New Shopify Products",
|
||||
"totalViews": 219,
|
||||
"recentViews": 0,
|
||||
"user": {
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
"cypress:install": "cypress install",
|
||||
"test:e2e:ui": "scripts/run-e2e.js ui",
|
||||
"test:e2e:dev": "scripts/run-e2e.js dev",
|
||||
"test:e2e:dev:v2": "scripts/run-e2e.js dev:v2",
|
||||
"test:e2e:all": "scripts/run-e2e.js all",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
"lint": "eslint . --quiet",
|
||||
"lintfix": "eslint . --fix",
|
||||
"develop": "cd ..; pnpm dev",
|
||||
"develop": "cd ..; pnpm dev:e2e:server",
|
||||
"start": "cd ..; pnpm start"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -26,6 +27,7 @@
|
|||
"cypress": "^13.14.2",
|
||||
"cypress-otp": "^1.0.3",
|
||||
"cypress-real-events": "^1.13.0",
|
||||
"flatted": "catalog:",
|
||||
"lodash": "catalog:",
|
||||
"nanoid": "catalog:",
|
||||
"start-server-and-test": "^2.0.8"
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
export class BannerStack extends BasePage {
|
||||
getters = {
|
||||
banner: () => cy.getByTestId('banner-stack'),
|
||||
};
|
||||
|
||||
actions = {};
|
||||
}
|
|
@ -1,5 +1,13 @@
|
|||
import type { IE2ETestPage } from '../types';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class BasePage implements IE2ETestPage {
|
||||
getters = {};
|
||||
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class CredentialsPage extends BasePage {
|
||||
url = '/home/credentials';
|
||||
|
||||
getters = {
|
||||
emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'),
|
||||
createCredentialButton: () => cy.getByTestId('resources-list-add'),
|
||||
createCredentialButton: () => {
|
||||
cy.getByTestId('add-resource').should('be.visible').click();
|
||||
cy.getByTestId('add-resource').getByTestId('action-credential').should('be.visible');
|
||||
return cy.getByTestId('add-resource').getByTestId('action-credential');
|
||||
},
|
||||
searchInput: () => cy.getByTestId('resources-list-search'),
|
||||
emptyList: () => cy.getByTestId('resources-list-empty'),
|
||||
credentialCards: () => cy.getByTestId('resources-list-item'),
|
||||
|
|
|
@ -8,6 +8,14 @@ const AI_ASSISTANT_FEATURE = {
|
|||
disabledFor: 'control',
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class AIAssistant extends BasePage {
|
||||
url = '/workflows/new';
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from '../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class NodeCreator extends BasePage {
|
||||
url = '/workflow/new';
|
||||
|
||||
|
|
|
@ -7,9 +7,7 @@ export * from './settings-users';
|
|||
export * from './settings-log-streaming';
|
||||
export * from './sidebar';
|
||||
export * from './ndv';
|
||||
export * from './bannerStack';
|
||||
export * from './workflow-executions-tab';
|
||||
export * from './signin';
|
||||
export * from './workflow-history';
|
||||
export * from './workerView';
|
||||
export * from './settings-public-api';
|
||||
|
|
|
@ -3,23 +3,31 @@ import { SigninPage } from './signin';
|
|||
import { WorkflowsPage } from './workflows';
|
||||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class MfaLoginPage extends BasePage {
|
||||
url = '/mfa';
|
||||
|
||||
getters = {
|
||||
form: () => cy.getByTestId('mfa-login-form'),
|
||||
token: () => cy.getByTestId('token'),
|
||||
recoveryCode: () => cy.getByTestId('recoveryCode'),
|
||||
mfaCode: () => cy.getByTestId('mfaCode'),
|
||||
mfaRecoveryCode: () => cy.getByTestId('mfaRecoveryCode'),
|
||||
enterRecoveryCodeButton: () => cy.getByTestId('mfa-enter-recovery-code-button'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
loginWithMfaToken: (email: string, password: string, mfaToken: string) => {
|
||||
loginWithMfaCode: (email: string, password: string, mfaCode: string) => {
|
||||
const signinPage = new SigninPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
cy.session(
|
||||
[mfaToken],
|
||||
[mfaCode],
|
||||
() => {
|
||||
cy.visit(signinPage.url);
|
||||
|
||||
|
@ -30,7 +38,7 @@ export class MfaLoginPage extends BasePage {
|
|||
});
|
||||
|
||||
this.getters.form().within(() => {
|
||||
this.getters.token().type(mfaToken);
|
||||
this.getters.mfaCode().type(mfaCode);
|
||||
});
|
||||
|
||||
// we should be redirected to /workflows
|
||||
|
@ -43,12 +51,12 @@ export class MfaLoginPage extends BasePage {
|
|||
},
|
||||
);
|
||||
},
|
||||
loginWithRecoveryCode: (email: string, password: string, recoveryCode: string) => {
|
||||
loginWithMfaRecoveryCode: (email: string, password: string, mfaRecoveryCode: string) => {
|
||||
const signinPage = new SigninPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
cy.session(
|
||||
[recoveryCode],
|
||||
[mfaRecoveryCode],
|
||||
() => {
|
||||
cy.visit(signinPage.url);
|
||||
|
||||
|
@ -61,7 +69,7 @@ export class MfaLoginPage extends BasePage {
|
|||
this.getters.enterRecoveryCodeButton().click();
|
||||
|
||||
this.getters.form().within(() => {
|
||||
this.getters.recoveryCode().type(recoveryCode);
|
||||
this.getters.mfaRecoveryCode().type(mfaRecoveryCode);
|
||||
});
|
||||
|
||||
// we should be redirected to /workflows
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from './../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class ChangePasswordModal extends BasePage {
|
||||
getters = {
|
||||
modalContainer: () => cy.getByTestId('changePassword-modal').last(),
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import { getCredentialSaveButton, saveCredential } from '../../composables/modals/credential-modal';
|
||||
import { getVisibleSelect } from '../../utils';
|
||||
import { BasePage } from '../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class CredentialsModal extends BasePage {
|
||||
getters = {
|
||||
newCredentialModal: () => cy.getByTestId('selectCredential-modal', { timeout: 5000 }),
|
||||
|
@ -13,8 +22,6 @@ export class CredentialsModal extends BasePage {
|
|||
this.getters.credentialInputs().find(`:contains('${fieldName}') .n8n-input input`),
|
||||
name: () => cy.getByTestId('credential-name'),
|
||||
nameInput: () => cy.getByTestId('credential-name').find('input'),
|
||||
// Saving of the credentials takes a while on the CI so we need to increase the timeout
|
||||
saveButton: () => cy.getByTestId('credential-save-button', { timeout: 5000 }),
|
||||
deleteButton: () => cy.getByTestId('credential-delete-button'),
|
||||
closeButton: () => this.getters.editCredentialModal().find('.el-dialog__close').first(),
|
||||
oauthConnectButton: () => cy.getByTestId('oauth-connect-button'),
|
||||
|
@ -41,17 +48,17 @@ export class CredentialsModal extends BasePage {
|
|||
},
|
||||
save: (test = false) => {
|
||||
cy.intercept('POST', '/rest/credentials').as('saveCredential');
|
||||
this.getters.saveButton().click({ force: true });
|
||||
saveCredential();
|
||||
|
||||
cy.wait('@saveCredential');
|
||||
if (test) cy.wait('@testCredential');
|
||||
this.getters.saveButton().should('contain.text', 'Saved');
|
||||
getCredentialSaveButton().should('contain.text', 'Saved');
|
||||
},
|
||||
saveSharing: () => {
|
||||
cy.intercept('PUT', '/rest/credentials/*/share').as('shareCredential');
|
||||
this.getters.saveButton().click({ force: true });
|
||||
saveCredential();
|
||||
cy.wait('@shareCredential');
|
||||
this.getters.saveButton().should('contain.text', 'Saved');
|
||||
getCredentialSaveButton().should('contain.text', 'Saved');
|
||||
},
|
||||
close: () => {
|
||||
this.getters.closeButton().click();
|
||||
|
@ -62,10 +69,11 @@ export class CredentialsModal extends BasePage {
|
|||
this.getters
|
||||
.credentialInputs()
|
||||
.find('input[type=text], input[type=password]')
|
||||
.filter(':not([readonly])')
|
||||
.each(($el) => {
|
||||
cy.wrap($el).type('test');
|
||||
});
|
||||
this.getters.saveButton().click();
|
||||
saveCredential();
|
||||
if (closeModal) {
|
||||
this.getters.closeButton().click();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from '../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class MessageBox extends BasePage {
|
||||
getters = {
|
||||
modal: () => cy.get('.el-message-box', { withinSubject: null }),
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from './../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class MfaSetupModal extends BasePage {
|
||||
getters = {
|
||||
modalContainer: () => cy.getByTestId('changePassword-modal').last(),
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from '../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class WorkflowSharingModal extends BasePage {
|
||||
getters = {
|
||||
modal: () => cy.getByTestId('workflowShare-modal', { timeout: 5000 }),
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { BasePage } from './base';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class NDV extends BasePage {
|
||||
getters = {
|
||||
container: () => cy.getByTestId('ndv'),
|
||||
|
@ -227,9 +235,6 @@ export class NDV extends BasePage {
|
|||
this.getters.inputSelect().find('.el-select').click();
|
||||
this.getters.inputOption().contains(nodeName).click();
|
||||
},
|
||||
expandSchemaViewNode: (nodeName: string) => {
|
||||
this.getters.schemaViewNodeName().contains(nodeName).click();
|
||||
},
|
||||
addDefaultPinnedData: () => {
|
||||
this.actions.editPinnedData();
|
||||
this.actions.savePinnedData();
|
||||
|
@ -323,6 +328,17 @@ export class NDV extends BasePage {
|
|||
addItemToFixedCollection: (paramName: string) => {
|
||||
this.getters.fixedCollectionParameter(paramName).getByTestId('fixed-collection-add').click();
|
||||
},
|
||||
typeIntoFixedCollectionItem: (fixedCollectionName: string, index: number, content: string) => {
|
||||
this.getters.fixedCollectionParameter(fixedCollectionName).within(() => {
|
||||
cy.getByTestId('parameter-input').eq(index).type(content);
|
||||
});
|
||||
},
|
||||
dragMainPanelToLeft: () => {
|
||||
cy.drag('[data-test-id=panel-drag-button]', [-1000, 0], { moveTwice: true });
|
||||
},
|
||||
dragMainPanelToRight: () => {
|
||||
cy.drag('[data-test-id=panel-drag-button]', [1000, 0], { moveTwice: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,5 +13,10 @@ export const infoToast = () => cy.get('.el-notification:has(.el-notification--in
|
|||
* Actions
|
||||
*/
|
||||
export const clearNotifications = () => {
|
||||
successToast().find('.el-notification__closeBtn').click({ multiple: true });
|
||||
const buttons = successToast().find('.el-notification__closeBtn');
|
||||
buttons.then(($buttons) => {
|
||||
if ($buttons.length) {
|
||||
buttons.click({ multiple: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { BasePage } from './base';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class SettingsLogStreamingPage extends BasePage {
|
||||
url = '/settings/log-streaming';
|
||||
|
||||
|
|
|
@ -7,6 +7,14 @@ import { MfaSetupModal } from './modals/mfa-setup-modal';
|
|||
const changePasswordModal = new ChangePasswordModal();
|
||||
const mfaSetupModal = new MfaSetupModal();
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class PersonalSettingsPage extends BasePage {
|
||||
url = '/settings/personal';
|
||||
|
||||
|
@ -22,6 +30,8 @@ export class PersonalSettingsPage extends BasePage {
|
|||
saveSettingsButton: () => cy.getByTestId('save-settings-button'),
|
||||
enableMfaButton: () => cy.getByTestId('enable-mfa-button'),
|
||||
disableMfaButton: () => cy.getByTestId('disable-mfa-button'),
|
||||
mfaCodeOrMfaRecoveryCodeInput: () => cy.getByTestId('mfa-code-or-recovery-code-input'),
|
||||
mfaSaveButton: () => cy.getByTestId('mfa-save-button'),
|
||||
themeSelector: () => cy.getByTestId('theme-select'),
|
||||
selectOptionsVisible: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'),
|
||||
};
|
||||
|
@ -83,9 +93,11 @@ export class PersonalSettingsPage extends BasePage {
|
|||
mfaSetupModal.getters.saveButton().click();
|
||||
});
|
||||
},
|
||||
disableMfa: () => {
|
||||
disableMfa: (mfaCodeOrRecoveryCode: string) => {
|
||||
cy.visit(this.url);
|
||||
this.getters.disableMfaButton().click();
|
||||
this.getters.mfaCodeOrMfaRecoveryCodeInput().type(mfaCodeOrRecoveryCode);
|
||||
this.getters.mfaSaveButton().click();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
export class SettingsUsagePage extends BasePage {
|
||||
url = '/settings/usage';
|
||||
|
||||
getters = {};
|
||||
|
||||
actions = {};
|
||||
}
|
|
@ -9,6 +9,14 @@ const workflowsPage = new WorkflowsPage();
|
|||
const mainSidebar = new MainSidebar();
|
||||
const settingsSidebar = new SettingsSidebar();
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class SettingsUsersPage extends BasePage {
|
||||
url = '/settings/users';
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
export class SettingsPage extends BasePage {
|
||||
url = '/settings';
|
||||
|
||||
getters = {
|
||||
menuItems: () => cy.getByTestId('menu-item'),
|
||||
};
|
||||
|
||||
actions = {};
|
||||
}
|
|
@ -1,6 +1,14 @@
|
|||
import { BasePage } from '../base';
|
||||
import { WorkflowsPage } from '../workflows';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class MainSidebar extends BasePage {
|
||||
getters = {
|
||||
menuItem: (id: string) => cy.getByTestId('menu-item').get('#' + id),
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from '../base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class SettingsSidebar extends BasePage {
|
||||
getters = {
|
||||
menuItem: (id: string) => cy.getByTestId('menu-item').get('#' + id),
|
||||
|
|
|
@ -2,6 +2,14 @@ import { BasePage } from './base';
|
|||
import { WorkflowsPage } from './workflows';
|
||||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class SigninPage extends BasePage {
|
||||
url = '/signin';
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
/**
|
||||
* @deprecated Use functional composables from @composables instead.
|
||||
* If a composable doesn't exist for your use case, please create a new one in:
|
||||
* cypress/composables
|
||||
*
|
||||
* This class-based approach is being phased out in favor of more modular functional composables.
|
||||
* Each getter and action in this class should be moved to individual composable functions.
|
||||
*/
|
||||
export class TemplatesPage extends BasePage {
|
||||
url = '/templates';
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue