mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
Revert "Merge remote-tracking branch 'origin/master' into seatable_node_rework2"
This reverts commit d0b1f62d96
.
This commit is contained in:
parent
d0b1f62d96
commit
83396fce1e
|
@ -9,7 +9,7 @@
|
|||
"type=bind,source=${localEnv:HOME}/.n8n,target=/home/node/.n8n,consistency=cached"
|
||||
],
|
||||
"forwardPorts": [8080, 5678],
|
||||
"postCreateCommand": "corepack prepare --activate && pnpm install",
|
||||
"postCreateCommand": "corepack prepare --activate && pnpm install ",
|
||||
"postAttachCommand": "pnpm build",
|
||||
"customizations": {
|
||||
"codespaces": {
|
||||
|
|
|
@ -19,6 +19,5 @@ services:
|
|||
- ..:/workspaces:cached
|
||||
command: sleep infinity
|
||||
environment:
|
||||
DB_POSTGRESDB_HOST: postgres
|
||||
DB_TYPE: postgresdb
|
||||
DB_POSTGRESDB_PASSWORD: password
|
||||
|
|
|
@ -10,7 +10,6 @@ packages/**/.turbo
|
|||
packages/**/*.test.*
|
||||
.git
|
||||
.github
|
||||
!.github/scripts
|
||||
*.tsbuildinfo
|
||||
packages/cli/dist/**/e2e.*
|
||||
docker/compose
|
||||
|
|
72
.github/pull_request_title_conventions.md
vendored
72
.github/pull_request_title_conventions.md
vendored
|
@ -4,79 +4,75 @@ We have very precise rules over how Pull Requests (to the `master` branch) must
|
|||
|
||||
A PR title consists of these elements:
|
||||
|
||||
```text
|
||||
```
|
||||
<type>(<scope>): <summary>
|
||||
│ │ │
|
||||
│ │ └─⫸ Summary: In imperative present tense.
|
||||
| | Capitalized
|
||||
| | No period at the end.
|
||||
│ │
|
||||
│ └─⫸ Scope: API | benchmark | core | editor | * Node
|
||||
│ └─⫸ Scope: API|core|editor|* Node
|
||||
│
|
||||
└─⫸ Type: build | ci | chore | docs | feat | fix | perf | refactor | test
|
||||
└─⫸ Type: build|ci|docs|feat|fix|perf|refactor|test
|
||||
```
|
||||
|
||||
- PR title
|
||||
- type
|
||||
- scope (_optional_)
|
||||
- summary
|
||||
- type
|
||||
- scope (*optional*)
|
||||
- summary
|
||||
- PR description
|
||||
- body (optional)
|
||||
- blank line
|
||||
- footer (optional)
|
||||
- body (optional)
|
||||
- blank line
|
||||
- footer (optional)
|
||||
|
||||
The structure looks like this:
|
||||
|
||||
## Type
|
||||
### **Type**
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
| type | description | appears in changelog |
|
||||
| --- | --- | --- |
|
||||
| `feat` | A new feature | ✅ |
|
||||
| `fix` | A bug fix | ✅ |
|
||||
| `perf` | A code change that improves performance | ✅ |
|
||||
| `test` | Adding missing tests or correcting existing tests | ❌ |
|
||||
| `docs` | Documentation only changes | ❌ |
|
||||
| `refactor` | A behavior-neutral code change that neither fixes a bug nor adds a feature | ❌ |
|
||||
| `build` | Changes that affect the build system or external dependencies (TypeScript, Jest, pnpm, etc.) | ❌ |
|
||||
| `ci` | Changes to CI configuration files and scripts (e.g. Github actions) | ❌ |
|
||||
| `chore` | Routine tasks, maintenance, and minor updates not covered by other types | ❌ |
|
||||
- `feat` - A new feature
|
||||
- `fix` - A bug fix
|
||||
- `perf` - A code change that improves performance
|
||||
- `test` - Adding missing tests or correcting existing tests
|
||||
- `docs` - Documentation only changes
|
||||
- `refactor` - A code change that neither fixes a bug nor adds a feature
|
||||
- `build` - Changes that affect the build system or external dependencies (example scopes: broccoli, npm)
|
||||
- `ci` - Changes to our CI configuration files and scripts (e.g. Github actions)
|
||||
|
||||
> BREAKING CHANGES (see Footer section below), will **always** appear in the changelog unless suffixed with `no-changelog`.
|
||||
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However if there is any BREAKING CHANGE (see Footer section below), the commit will always appear in the changelog.
|
||||
|
||||
## Scope (optional)
|
||||
### **Scope (optional)**
|
||||
|
||||
The scope should specify the place of the commit change as long as the commit clearly addresses one of the following supported scopes. (Otherwise, omit the scope!)
|
||||
|
||||
- `API` - changes to the _public_ API
|
||||
- `benchmark` - changes to the benchmark cli
|
||||
- `API` - changes to the *public* API
|
||||
- `core` - changes to the core / private API / backend of n8n
|
||||
- `editor` - changes to the Editor UI
|
||||
- `* Node` - changes to a specific node or trigger node (”`*`” to be replaced with the node name, not its display name), e.g.
|
||||
- mattermost → Mattermost Node
|
||||
- microsoftToDo → Microsoft To Do Node
|
||||
- n8n → n8n Node
|
||||
- mattermost → Mattermost Node
|
||||
- microsoftToDo → Microsoft To Do Node
|
||||
- n8n → n8n Node
|
||||
|
||||
## Summary
|
||||
### **Summary**
|
||||
|
||||
The summary contains succinct description of the change:
|
||||
|
||||
- use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- capitalize the first letter
|
||||
- _no_ dot (.) at the end
|
||||
- do _not_ include Linear ticket IDs etc. (e.g. N8N-1234)
|
||||
- *no* dot (.) at the end
|
||||
- do *not* include Linear ticket IDs etc. (e.g. N8N-1234)
|
||||
- suffix with “(no-changelog)” for commits / PRs that should not get mentioned in the changelog.
|
||||
|
||||
## Body (optional)
|
||||
### **Body (optional)**
|
||||
|
||||
Just as in the **summary**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
## Footer (optional)
|
||||
### **Footer (optional)**
|
||||
|
||||
The footer can contain information about breaking changes and deprecations and is also the place to [reference GitHub issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), Linear tickets, and other PRs that this commit closes or is related to. For example:
|
||||
|
||||
```text
|
||||
```
|
||||
BREAKING CHANGE: <breaking change summary>
|
||||
<BLANK LINE>
|
||||
<breaking change description + migration instructions>
|
||||
|
@ -87,7 +83,7 @@ Fixes #<issue number>
|
|||
|
||||
or
|
||||
|
||||
```text
|
||||
```
|
||||
DEPRECATED: <what is deprecated>
|
||||
<BLANK LINE>
|
||||
<deprecation description + recommended update path>
|
||||
|
@ -99,18 +95,18 @@ Closes #<pr number>
|
|||
A Breaking Change section should start with the phrase "`BREAKING CHANGE:` " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.
|
||||
|
||||
> 💡 A breaking change can additionally also be marked by adding a “`!`” to the header, right before the “`:`”, e.g. `feat(editor)!: Remove support for dark mode`
|
||||
>
|
||||
>
|
||||
> This makes locating breaking changes easier when just skimming through commit messages.
|
||||
|
||||
> 💡 The breaking changes must also be added to the [packages/cli/BREAKING-CHANGES.md](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) file located in the n8n repository.
|
||||
|
||||
Similarly, a Deprecation section should start with "`DEPRECATED:` " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
|
||||
|
||||
### Revert commits
|
||||
### **Revert commits**
|
||||
|
||||
If the commit reverts a previous commit, it should begin with `revert:` , followed by the header of the reverted commit.
|
||||
|
||||
The content of the commit message body should contain:
|
||||
|
||||
- information about the SHA of the commit being reverted in the following format: `This reverts commit <SHA>`,
|
||||
- a clear description of the reason for reverting the commit message.
|
||||
- a clear description of the reason for reverting the commit message.
|
44
.github/scripts/ensure-provenance-fields.mjs
vendored
44
.github/scripts/ensure-provenance-fields.mjs
vendored
|
@ -1,44 +0,0 @@
|
|||
import { writeFile, readFile, copyFile } from 'fs/promises';
|
||||
import { resolve, dirname } from 'path';
|
||||
import child_process from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
const commonFiles = ['LICENSE.md', 'LICENSE_EE.md'];
|
||||
|
||||
const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const packages = JSON.parse((await exec('pnpm ls -r --only-projects --json')).stdout);
|
||||
|
||||
for (let { name, path, version, private: isPrivate } of packages) {
|
||||
if (isPrivate) continue;
|
||||
|
||||
const packageFile = resolve(path, 'package.json');
|
||||
const packageJson = {
|
||||
...JSON.parse(await readFile(packageFile, 'utf-8')),
|
||||
// Add these fields to all published package.json files to ensure provenance checks pass
|
||||
license: 'SEE LICENSE IN LICENSE.md',
|
||||
homepage: 'https://n8n.io',
|
||||
author: {
|
||||
name: 'Jan Oberhauser',
|
||||
email: 'jan@n8n.io',
|
||||
},
|
||||
repository: {
|
||||
type: 'git',
|
||||
url: 'git+https://github.com/n8n-io/n8n.git',
|
||||
},
|
||||
};
|
||||
|
||||
// Copy over LICENSE.md and LICENSE_EE.md into every published package, and ensure they get included in the published package
|
||||
await Promise.all(
|
||||
commonFiles.map(async (file) => {
|
||||
await copyFile(resolve(baseDir, file), resolve(path, file));
|
||||
if (packageJson.files && !packageJson.files.includes(file)) {
|
||||
packageJson.files.push(file);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
}
|
2
.github/scripts/package.json
vendored
2
.github/scripts/package.json
vendored
|
@ -5,7 +5,7 @@
|
|||
"debug": "4.3.4",
|
||||
"glob": "10.3.10",
|
||||
"p-limit": "3.1.0",
|
||||
"picocolors": "1.0.1",
|
||||
"picocolors": "1.0.0",
|
||||
"semver": "7.5.4",
|
||||
"tempfile": "5.0.0",
|
||||
"typescript": "*"
|
||||
|
|
6
.github/scripts/update-changelog.mjs
vendored
6
.github/scripts/update-changelog.mjs
vendored
|
@ -16,11 +16,7 @@ const changelogStream = conventionalChangelog({
|
|||
releaseCount: 1,
|
||||
tagPrefix: 'n8n@',
|
||||
transform: (commit, callback) => {
|
||||
const hasNoChangelogInHeader = commit.header.includes('(no-changelog)');
|
||||
const isBenchmarkScope = commit.scope === 'benchmark';
|
||||
|
||||
// Ignore commits that have 'benchmark' scope or '(no-changelog)' in the header
|
||||
callback(null, hasNoChangelogInHeader || isBenchmarkScope ? undefined : commit);
|
||||
callback(null, commit.header.includes('(no-changelog)') ? undefined : commit);
|
||||
},
|
||||
}).on('error', (err) => {
|
||||
console.error(err.stack);
|
||||
|
|
43
.github/workflows/benchmark-destroy-nightly.yml
vendored
43
.github/workflows/benchmark-destroy-nightly.yml
vendored
|
@ -1,43 +0,0 @@
|
|||
name: Destroy Benchmark Env
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: benchmark
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: benchmarking
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v2.1.1
|
||||
with:
|
||||
client-id: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.BENCHMARK_ARM_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }}
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Destroy cloud env
|
||||
run: pnpm destroy-cloud-env
|
||||
working-directory: packages/@n8n/benchmark
|
101
.github/workflows/benchmark-nightly.yml
vendored
101
.github/workflows/benchmark-nightly.yml
vendored
|
@ -1,101 +0,0 @@
|
|||
name: Run Nightly Benchmark
|
||||
run-name: Benchmark ${{ inputs.n8n_tag || 'nightly' }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1,2,3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug:
|
||||
description: 'Use debug logging'
|
||||
required: true
|
||||
default: 'false'
|
||||
n8n_tag:
|
||||
description: 'Name of the n8n docker tag to run the benchmark against.'
|
||||
required: true
|
||||
default: 'nightly'
|
||||
benchmark_tag:
|
||||
description: 'Name of the benchmark cli docker tag to run the benchmark with.'
|
||||
required: true
|
||||
default: 'latest'
|
||||
|
||||
env:
|
||||
ARM_CLIENT_ID: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.BENCHMARK_ARM_TENANT_ID }}
|
||||
N8N_TAG: ${{ inputs.n8n_tag || 'nightly' }}
|
||||
N8N_BENCHMARK_TAG: ${{ inputs.benchmark_tag || 'latest' }}
|
||||
DEBUG: ${{ inputs.debug == 'true' && '--debug' || '' }}
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: benchmark
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: benchmarking
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_version: '1.8.5'
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v2.1.1
|
||||
with:
|
||||
client-id: ${{ env.ARM_CLIENT_ID }}
|
||||
tenant-id: ${{ env.ARM_TENANT_ID }}
|
||||
subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Destroy any existing environment
|
||||
run: pnpm destroy-cloud-env
|
||||
working-directory: packages/@n8n/benchmark
|
||||
|
||||
- name: Provision the environment
|
||||
run: pnpm provision-cloud-env ${{ env.DEBUG }}
|
||||
working-directory: packages/@n8n/benchmark
|
||||
|
||||
- name: Run the benchmark
|
||||
env:
|
||||
BENCHMARK_RESULT_WEBHOOK_URL: ${{ secrets.BENCHMARK_RESULT_WEBHOOK_URL }}
|
||||
BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: ${{ secrets.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER }}
|
||||
N8N_LICENSE_CERT: ${{ secrets.N8N_BENCHMARK_LICENSE_CERT }}
|
||||
run: |
|
||||
pnpm benchmark-in-cloud \
|
||||
--vus 5 \
|
||||
--duration 1m \
|
||||
--n8nTag ${{ env.N8N_TAG }} \
|
||||
--benchmarkTag ${{ env.N8N_BENCHMARK_TAG }} \
|
||||
${{ env.DEBUG }}
|
||||
working-directory: packages/@n8n/benchmark
|
||||
|
||||
# We need to login again because the access token expires
|
||||
- name: Azure login
|
||||
if: always()
|
||||
uses: azure/login@v2.1.1
|
||||
with:
|
||||
client-id: ${{ env.ARM_CLIENT_ID }}
|
||||
tenant-id: ${{ env.ARM_TENANT_ID }}
|
||||
subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Destroy the environment
|
||||
if: always()
|
||||
run: pnpm destroy-cloud-env ${{ env.DEBUG }}
|
||||
working-directory: packages/@n8n/benchmark
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build relevant packages
|
||||
run: pnpm build:nodes
|
||||
run: pnpm --filter @n8n/client-oauth2 --filter @n8n/imap --filter n8n-workflow --filter n8n-core --filter n8n-nodes-base --filter @n8n/n8n-nodes-langchain build
|
||||
|
||||
- run: npm install --prefix=.github/scripts --no-package-lock
|
||||
|
||||
|
|
5
.github/workflows/check-pr-title.yml
vendored
5
.github/workflows/check-pr-title.yml
vendored
|
@ -7,7 +7,8 @@ on:
|
|||
- edited
|
||||
- synchronize
|
||||
branches:
|
||||
- 'master'
|
||||
- '**'
|
||||
- '!release/*'
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
|
@ -28,6 +29,6 @@ jobs:
|
|||
|
||||
- name: Validate PR title
|
||||
id: validate_pr_title
|
||||
uses: n8n-io/validate-n8n-pull-request-title@v2.2.0
|
||||
uses: n8n-io/validate-n8n-pull-request-title@v2.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
42
.github/workflows/chromatic.yml
vendored
42
.github/workflows/chromatic.yml
vendored
|
@ -4,49 +4,19 @@ on:
|
|||
workflow_dispatch:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
branch:
|
||||
- 'master'
|
||||
paths:
|
||||
- packages/design-system/**
|
||||
- .github/workflows/chromatic.yml
|
||||
|
||||
concurrency:
|
||||
group: chromatic-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
get-metadata:
|
||||
name: Get Metadata
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out current commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Determine changed files
|
||||
uses: tomi/paths-filter-action@v3.0.2
|
||||
id: changed
|
||||
if: github.event_name == 'pull_request_review'
|
||||
with:
|
||||
filters: |
|
||||
design_system:
|
||||
- packages/design-system/**
|
||||
- .github/workflows/chromatic.yml
|
||||
|
||||
outputs:
|
||||
design_system_files_changed: ${{ steps.changed.outputs.design_system == 'true' }}
|
||||
is_community_pr: ${{ contains(github.event.pull_request.labels.*.name, 'community') }}
|
||||
is_pr_target_master: ${{ github.event.pull_request.base.ref == 'master' }}
|
||||
is_dispatch: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
is_pr_approved: ${{ github.event.review.state == 'approved' }}
|
||||
|
||||
chromatic:
|
||||
needs: [get-metadata]
|
||||
if: |
|
||||
needs.get-metadata.outputs.is_dispatch == 'true' ||
|
||||
(
|
||||
needs.get-metadata.outputs.design_system_files_changed == 'true' &&
|
||||
needs.get-metadata.outputs.is_community_pr == 'false' &&
|
||||
needs.get-metadata.outputs.is_pr_target_master == 'true' &&
|
||||
needs.get-metadata.outputs.is_pr_approved == 'true'
|
||||
)
|
||||
if: ${{ github.event.review.state == 'approved' && !contains(github.event.pull_request.labels.*.name, 'community') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
|
9
.github/workflows/ci-master.yml
vendored
9
.github/workflows/ci-master.yml
vendored
|
@ -23,9 +23,6 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
|
@ -41,14 +38,12 @@ jobs:
|
|||
needs: install-and-build
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x, 22.4]
|
||||
node-version: [18.x, 20.x, 22.x]
|
||||
with:
|
||||
ref: ${{ inputs.branch }}
|
||||
nodeVersion: ${{ matrix.node-version }}
|
||||
cacheKey: ${{ github.sha }}-base:build
|
||||
collectCoverage: ${{ matrix.node-version == '20.x' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
collectCoverage: true
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
|
|
17
.github/workflows/ci-postgres-mysql.yml
vendored
17
.github/workflows/ci-postgres-mysql.yml
vendored
|
@ -8,8 +8,6 @@ on:
|
|||
paths:
|
||||
- packages/cli/src/databases/**
|
||||
- .github/workflows/ci-postgres-mysql.yml
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
concurrency:
|
||||
group: db-${{ github.event.pull_request.number || github.ref }}
|
||||
|
@ -19,7 +17,6 @@ jobs:
|
|||
build:
|
||||
name: Install & Build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request_review' || startsWith(github.event.pull_request.base.ref, 'release/')
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- run: corepack enable
|
||||
|
@ -29,9 +26,6 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build Backend
|
||||
run: pnpm build:backend
|
||||
|
||||
|
@ -58,9 +52,6 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Restore cached build artifacts
|
||||
uses: actions/cache/restore@v4.0.0
|
||||
with:
|
||||
|
@ -69,7 +60,7 @@ jobs:
|
|||
|
||||
- name: Test SQLite Pooled
|
||||
working-directory: packages/cli
|
||||
run: pnpm jest
|
||||
run: pnpm jest --coverage
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
|
@ -87,9 +78,6 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Restore cached build artifacts
|
||||
uses: actions/cache/restore@v4.0.0
|
||||
with:
|
||||
|
@ -124,9 +112,6 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Restore cached build artifacts
|
||||
uses: actions/cache/restore@v4.0.0
|
||||
with:
|
||||
|
|
13
.github/workflows/ci-pull-requests.yml
vendored
13
.github/workflows/ci-pull-requests.yml
vendored
|
@ -1,10 +1,6 @@
|
|||
name: Build, unit test and lint branch
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
- '!release/*'
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
install-and-build:
|
||||
|
@ -13,6 +9,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: n8n-io/n8n
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
|
||||
- run: corepack enable
|
||||
|
@ -24,15 +21,9 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Run formatcheck
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Run typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
|
|
43
.github/workflows/docker-images-benchmark.yml
vendored
43
.github/workflows/docker-images-benchmark.yml
vendored
|
@ -1,43 +0,0 @@
|
|||
name: Benchmark Docker Image CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'packages/@n8n/benchmark/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.github/workflows/docker-images-benchmark.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/@n8n/benchmark/Dockerfile
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/n8n-benchmark:latest
|
27
.github/workflows/docker-images-nightly.yml
vendored
27
.github/workflows/docker-images-nightly.yml
vendored
|
@ -6,6 +6,10 @@ on:
|
|||
- cron: '0 1 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
repository:
|
||||
description: 'GitHub repository to create image off.'
|
||||
required: true
|
||||
default: 'n8n-io/n8n'
|
||||
branch:
|
||||
description: 'GitHub branch to create image off.'
|
||||
required: true
|
||||
|
@ -32,9 +36,6 @@ on:
|
|||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
N8N_TAG: ${{ inputs.tag || 'nightly' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -48,6 +49,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: ${{ github.event.inputs.repository || 'n8n-io/n8n' }}
|
||||
ref: ${{ github.event.inputs.branch || 'master' }}
|
||||
|
||||
- name: Set up QEMU
|
||||
|
@ -67,7 +69,7 @@ jobs:
|
|||
[[ "${{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
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
|
@ -79,22 +81,7 @@ 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 \
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:nightly
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/n8n:${{ github.event.inputs.tag || 'nightly' }}
|
||||
|
||||
- name: Call Success URL - optionally
|
||||
run: |
|
||||
|
|
48
.github/workflows/docker-images.yml
vendored
Normal file
48
.github/workflows/docker-images.yml
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Get the version
|
||||
id: vars
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:14})
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: ./docker/images/n8n
|
||||
build-args: |
|
||||
N8N_VERSION=${{ steps.vars.outputs.tag }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ steps.vars.outputs.tag }}
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ steps.vars.outputs.tag }}
|
13
.github/workflows/e2e-reusable.yml
vendored
13
.github/workflows/e2e-reusable.yml
vendored
|
@ -22,6 +22,11 @@ on:
|
|||
required: false
|
||||
default: 'browsers:node18.12.0-chrome107'
|
||||
type: string
|
||||
cache-key:
|
||||
description: 'Cache key for modules and build artifacts.'
|
||||
required: false
|
||||
default: ${{ github.sha }}-${{ inputs.run-env }}-e2e-modules
|
||||
type: string
|
||||
record:
|
||||
description: 'Record test run.'
|
||||
required: false
|
||||
|
@ -73,6 +78,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: n8n-io/n8n
|
||||
ref: ${{ inputs.branch }}
|
||||
|
||||
- name: Checkout PR
|
||||
|
@ -93,6 +99,8 @@ jobs:
|
|||
runTests: false
|
||||
install: false
|
||||
build: pnpm build
|
||||
env:
|
||||
VUE_APP_MAX_PINNED_DATA_SIZE: 16384
|
||||
|
||||
- name: Cypress install
|
||||
working-directory: cypress
|
||||
|
@ -105,7 +113,7 @@ jobs:
|
|||
/github/home/.cache
|
||||
/github/home/.pnpm-store
|
||||
./packages/**/dist
|
||||
key: ${{ github.sha }}-e2e
|
||||
key: ${{ inputs.cache-key }}
|
||||
|
||||
testing:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -122,6 +130,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: n8n-io/n8n
|
||||
ref: ${{ inputs.branch }}
|
||||
|
||||
- name: Checkout PR
|
||||
|
@ -139,7 +148,7 @@ jobs:
|
|||
/github/home/.cache
|
||||
/github/home/.pnpm-store
|
||||
./packages/**/dist
|
||||
key: ${{ github.sha }}-e2e
|
||||
key: ${{ inputs.cache-key }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
|
44
.github/workflows/e2e-tests-pr.yml
vendored
44
.github/workflows/e2e-tests-pr.yml
vendored
|
@ -3,65 +3,33 @@ name: PR E2E
|
|||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
branch:
|
||||
- 'master'
|
||||
|
||||
concurrency:
|
||||
group: e2e-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
get-metadata:
|
||||
name: Get Metadata
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out current commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Determine changed files
|
||||
uses: tomi/paths-filter-action@v3.0.2
|
||||
id: changed
|
||||
with:
|
||||
filters: |
|
||||
not_ignored:
|
||||
- '!.devcontainer/**'
|
||||
- '!.github/*'
|
||||
- '!.github/scripts/*'
|
||||
- '!.github/workflows/benchmark-*'
|
||||
- '!.github/workflows/check-*'
|
||||
- '!.vscode/**'
|
||||
- '!docker/**'
|
||||
- '!packages/@n8n/benchmark/**'
|
||||
- '!**/*.md'
|
||||
predicate-quantifier: 'every'
|
||||
|
||||
outputs:
|
||||
# The workflow should run when:
|
||||
# - It has changes to files that are not ignored
|
||||
# - It is not a community PR
|
||||
# - It is targeting master or a release branch
|
||||
should_run: ${{ steps.changed.outputs.not_ignored == 'true' && !contains(github.event.pull_request.labels.*.name, 'community') && (github.event.pull_request.base.ref == 'master' || startsWith(github.event.pull_request.base.ref, 'release/')) }}
|
||||
|
||||
run-e2e-tests:
|
||||
name: E2E [Electron/Node 18]
|
||||
uses: ./.github/workflows/e2e-reusable.yml
|
||||
needs: [get-metadata]
|
||||
if: ${{ github.event.review.state == 'approved' && needs.get-metadata.outputs.should_run == 'true' }}
|
||||
if: ${{ github.event.review.state == 'approved' && !contains(github.event.pull_request.labels.*.name, 'community') }}
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
user: ${{ github.event.pull_request.user.login || 'PR User' }}
|
||||
spec: 'e2e/*'
|
||||
secrets:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
post-e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: E2E [Electron/Node 18] - Checks
|
||||
needs: [get-metadata, run-e2e-tests]
|
||||
needs: [run-e2e-tests]
|
||||
if: always()
|
||||
steps:
|
||||
- name: E2E success comment
|
||||
if: ${{ needs.get-metadata.outputs.should_run == 'true' && needs.run-e2e-tests.outputs.tests_passed == 'true' }}
|
||||
if: ${{!contains(github.event.pull_request.labels.*.name, 'community') && needs.run-e2e-tests.outputs.tests_passed == 'true' }}
|
||||
uses: peter-evans/create-or-update-comment@v4.0.0
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
|
4
.github/workflows/linting-reusable.yml
vendored
4
.github/workflows/linting-reusable.yml
vendored
|
@ -21,6 +21,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: n8n-io/n8n
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- run: corepack enable
|
||||
|
@ -32,9 +33,6 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.cacheKey == '' }}
|
||||
run: pnpm build
|
||||
|
|
1
.github/workflows/notify-pr-status.yml
vendored
1
.github/workflows/notify-pr-status.yml
vendored
|
@ -16,7 +16,6 @@ jobs:
|
|||
(github.event_name == 'pull_request' && github.event.pull_request.merged == false && github.event.action == 'closed')
|
||||
steps:
|
||||
- uses: fjogeleit/http-request-action@dea46570591713c7de04a5b556bf2ff7bdf0aa9c # v1
|
||||
if: ${{!contains(github.event.pull_request.labels.*.name, 'community')}}
|
||||
name: Notify
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
|
|
6
.github/workflows/release-create-pr.yml
vendored
6
.github/workflows/release-create-pr.yml
vendored
|
@ -56,12 +56,12 @@ jobs:
|
|||
git push -f origin refs/remotes/origin/${{ github.event.inputs.base-branch }}:refs/heads/release/${{ env.NEXT_RELEASE }}
|
||||
|
||||
- name: Push the release branch, and Create the PR
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
base: 'release/${{ env.NEXT_RELEASE }}'
|
||||
branch: 'release-pr/${{ env.NEXT_RELEASE }}'
|
||||
branch: '${{ env.NEXT_RELEASE }}-pr'
|
||||
commit-message: ':rocket: Release ${{ env.NEXT_RELEASE }}'
|
||||
delete-branch: true
|
||||
labels: release,release:${{ github.event.inputs.release-type }}
|
||||
labels: 'release'
|
||||
title: ':rocket: Release ${{ env.NEXT_RELEASE }}'
|
||||
body-path: 'CHANGELOG-${{ env.NEXT_RELEASE }}.md'
|
||||
|
|
112
.github/workflows/release-publish.yml
vendored
112
.github/workflows/release-publish.yml
vendored
|
@ -8,17 +8,18 @@ on:
|
|||
- 'release/*'
|
||||
|
||||
jobs:
|
||||
publish-to-npm:
|
||||
name: Publish to NPM
|
||||
runs-on: ubuntu-latest
|
||||
publish-release:
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
outputs:
|
||||
release: ${{ steps.set-release.outputs.release }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
@ -44,103 +45,30 @@ jobs:
|
|||
- name: Publish to NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
node .github/scripts/trim-fe-packageJson.js
|
||||
node .github/scripts/ensure-provenance-fields.mjs
|
||||
node scripts/trim-fe-packageJson.js
|
||||
sed -i "s/default: 'dev'/default: 'stable'/g" packages/cli/dist/config/schema.js
|
||||
pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public --tag rc --no-git-checks
|
||||
npm dist-tag rm n8n rc
|
||||
|
||||
- id: set-release
|
||||
run: echo "release=${{ env.RELEASE }}" >> $GITHUB_OUTPUT
|
||||
|
||||
publish-to-docker-hub:
|
||||
name: Publish to DockerHub
|
||||
needs: [publish-to-npm]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: ./docker/images/n8n
|
||||
build-args: |
|
||||
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
||||
|
||||
create-github-release:
|
||||
name: Create a GitHub Release
|
||||
needs: [publish-to-npm, publish-to-docker-hub]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 5
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Create a Release on GitHub
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
commit: ${{github.event.pull_request.base.ref}}
|
||||
tag: 'n8n@${{ needs.publish-to-npm.outputs.release }}'
|
||||
tag: 'n8n@${{env.RELEASE}}'
|
||||
prerelease: true
|
||||
makeLatest: false
|
||||
body: ${{github.event.pull_request.body}}
|
||||
|
||||
trigger-release-note:
|
||||
name: Trigger a release note
|
||||
needs: [publish-to-npm, create-github-release]
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger a release note
|
||||
run: curl -u docsWorkflows:${{ secrets.N8N_WEBHOOK_DOCS_PASSWORD }} --request GET 'https://internal.users.n8n.cloud/webhook/trigger-release-note' --header 'Content-Type:application/json' --data '{"version":"${{ needs.publish-to-npm.outputs.release }}"}'
|
||||
continue-on-error: true
|
||||
run: curl -u docsWorkflows:${{ secrets.N8N_WEBHOOK_DOCS_PASSWORD }} --request GET 'https://internal.users.n8n.cloud/webhook/trigger-release-note' --header 'Content-Type:application/json' --data '{"version":"${{env.RELEASE}}"}'
|
||||
|
||||
# merge-back-into-master:
|
||||
# name: Merge back into master
|
||||
# needs: [publish-to-npm, create-github-release]
|
||||
# if: ${{ github.event.pull_request.merged == true && !contains(github.event.pull_request.labels.*.name, 'release:patch') }}
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.1
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - run: |
|
||||
# git checkout --track origin/master
|
||||
# git config user.name "github-actions[bot]"
|
||||
# git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
# git merge --ff n8n@${{ needs.publish-to-npm.outputs.release }}
|
||||
# git push origin master
|
||||
# git push origin :${{github.event.pull_request.base.ref}}
|
||||
# - name: Merge Release into 'master'
|
||||
# run: |
|
||||
# git fetch origin
|
||||
# git checkout --track origin/master
|
||||
# git config user.name "Jan Oberhauser"
|
||||
# git config user.email jan.oberhauser@gmail.com
|
||||
# git merge --ff n8n@${{env.RELEASE}}
|
||||
# git push origin master
|
||||
# git push origin :${{github.event.pull_request.base.ref}}
|
||||
|
|
1
.github/workflows/test-workflows.yml
vendored
1
.github/workflows/test-workflows.yml
vendored
|
@ -73,7 +73,6 @@ jobs:
|
|||
env:
|
||||
N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}}
|
||||
SKIP_STATISTICS_EVENTS: true
|
||||
DB_SQLITE_POOL_SIZE: 4
|
||||
# -
|
||||
# name: Export credentials
|
||||
# if: always()
|
||||
|
|
13
.github/workflows/units-tests-reusable.yml
vendored
13
.github/workflows/units-tests-reusable.yml
vendored
|
@ -22,10 +22,6 @@ on:
|
|||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: 'Codecov upload token.'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
|
@ -36,6 +32,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: n8n-io/n8n
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- run: corepack enable
|
||||
|
@ -48,10 +45,6 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup build cache
|
||||
if: inputs.collectCoverage != true
|
||||
uses: rharkor/caching-for-turbo@v1.5
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.cacheKey == '' }}
|
||||
run: pnpm build
|
||||
|
@ -74,6 +67,6 @@ jobs:
|
|||
|
||||
- name: Upload coverage to Codecov
|
||||
if: inputs.collectCoverage
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: packages/@n8n/chat/coverage/cobertura-coverage.xml,packages/@n8n/nodes-langchain/coverage/cobertura-coverage.xml,packages/@n8n/permissions/coverage/cobertura-coverage.xml,packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/@n8n/codemirror-lang/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml
|
||||
|
|
|
@ -7,11 +7,3 @@ packages/nodes-base/nodes/**/test
|
|||
packages/cli/templates/form-trigger.handlebars
|
||||
cypress/fixtures
|
||||
CHANGELOG.md
|
||||
.github/pull_request_template.md
|
||||
# Ignored for now
|
||||
**/*.md
|
||||
# Handled by biome
|
||||
**/*.ts
|
||||
**/*.js
|
||||
**/*.json
|
||||
**/*.jsonc
|
||||
|
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
|
@ -1,12 +1,10 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"biomejs.biome",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"dangmai.workspace-default-settings",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"mjmlio.vscode-mjml",
|
||||
"Vue.volar"
|
||||
]
|
||||
}
|
||||
|
|
16
.vscode/settings.default.json
vendored
16
.vscode/settings.default.json
vendored
|
@ -1,22 +1,6 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"quickfix.biome": "explicit",
|
||||
"source.organizeImports.biome": "never"
|
||||
},
|
||||
"search.exclude": {
|
||||
"node_modules": true,
|
||||
"dist": true,
|
||||
|
|
507
CHANGELOG.md
507
CHANGELOG.md
|
@ -1,510 +1,3 @@
|
|||
# [1.61.0](https://github.com/n8n-io/n8n/compare/n8n@1.60.0...n8n@1.61.0) (2024-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Add executionData to expressions in pagination code ([#10926](https://github.com/n8n-io/n8n/issues/10926)) ([eac103e](https://github.com/n8n-io/n8n/commit/eac103e367d59a532b9ba12db78a0dd10aee62fb))
|
||||
* **core:** Fix webhook binary data max size configuration ([#10897](https://github.com/n8n-io/n8n/issues/10897)) ([693fb7e](https://github.com/n8n-io/n8n/commit/693fb7e580b7e030c86977bff6d319bbee4fcd62))
|
||||
* **core:** Remove subworkflow license check ([#10893](https://github.com/n8n-io/n8n/issues/10893)) ([0290e38](https://github.com/n8n-io/n8n/commit/0290e38f990275074eb7e7ccd0b41f1ae0215dd2))
|
||||
* **editor:** Credentials scopes and n8n scopes mix up ([#10930](https://github.com/n8n-io/n8n/issues/10930)) ([e069608](https://github.com/n8n-io/n8n/commit/e0696080227aee7ccb50d51a82873e8a1ba4667d))
|
||||
* **editor:** Fix design system form component sizing ([#10961](https://github.com/n8n-io/n8n/issues/10961)) ([cf153ea](https://github.com/n8n-io/n8n/commit/cf153ea085165115ee523fbb1bd32080dde47eda))
|
||||
* **editor:** Fix modal overflow when AI is enabled in code node ([#10887](https://github.com/n8n-io/n8n/issues/10887)) ([f9f303f](https://github.com/n8n-io/n8n/commit/f9f303f562084db8c8956da267680b1f935aa2df))
|
||||
* **editor:** Fix source control push modal checkboxes ([#10910](https://github.com/n8n-io/n8n/issues/10910)) ([8db8817](https://github.com/n8n-io/n8n/commit/8db88178511749b19a5878816ef062092fd9f2be))
|
||||
* **editor:** Fix styling and typography in AI Assistant chat ([#10895](https://github.com/n8n-io/n8n/issues/10895)) ([57ff3cc](https://github.com/n8n-io/n8n/commit/57ff3cc27b9470bfbe2486c3c1831c57f5a4075f))
|
||||
* **editor:** Prevent clipboard xss injection ([#10894](https://github.com/n8n-io/n8n/issues/10894)) ([e20ab59](https://github.com/n8n-io/n8n/commit/e20ab59c1dcf9da19a30268ce19930bfa7e38992))
|
||||
* **editor:** Prevent node name input in NDV to expand unnecessarily ([#10922](https://github.com/n8n-io/n8n/issues/10922)) ([a2237d1](https://github.com/n8n-io/n8n/commit/a2237d128ff6a4d65cd30325b6b9d9b765ca7be6))
|
||||
* **editor:** Update gird size when opening credentials support chat ([#10882](https://github.com/n8n-io/n8n/issues/10882)) ([b86fd80](https://github.com/n8n-io/n8n/commit/b86fd80fc9fe06011367ca04a75e4b52533db1fe))
|
||||
* **editor:** Use `:focus-visible` instead for `:focus` for buttons ([#10921](https://github.com/n8n-io/n8n/issues/10921)) ([bf28d09](https://github.com/n8n-io/n8n/commit/bf28d0965c46620a106c87037bafd2cf936f1050))
|
||||
* **editor:** Use correct output for connected nodes in schema view ([#10928](https://github.com/n8n-io/n8n/issues/10928)) ([ad60d49](https://github.com/n8n-io/n8n/commit/ad60d49b4251138a7c69cb5e9f00c3ef875486e0))
|
||||
* Enable Assistant on other credential views ([#10931](https://github.com/n8n-io/n8n/issues/10931)) ([557db9c](https://github.com/n8n-io/n8n/commit/557db9c170a89447ec9cc14aa1af51e5fd11dd92))
|
||||
* Ensure user id for early track events ([#10885](https://github.com/n8n-io/n8n/issues/10885)) ([23c09ea](https://github.com/n8n-io/n8n/commit/23c09eae4223545c717270a5cd305d2e57e1ad5b))
|
||||
* **Google Sheets Node:** Insert data if sheet is empty instead of error ([#10942](https://github.com/n8n-io/n8n/issues/10942)) ([c75990e](https://github.com/n8n-io/n8n/commit/c75990e0632c581384542610a886ef89621a9403))
|
||||
* Hide assistant button when showing Click to connect ([#10932](https://github.com/n8n-io/n8n/issues/10932)) ([d74cff2](https://github.com/n8n-io/n8n/commit/d74cff20301f285588f93207f29660d25fdbc8da))
|
||||
* **HTTP Request Node:** Do not modify request object when sanitizing message for UI ([#10923](https://github.com/n8n-io/n8n/issues/10923)) ([8cc10cc](https://github.com/n8n-io/n8n/commit/8cc10cc2c1869b9abcafd157e41be65ce2b6f499))
|
||||
* **MQTT Node:** Close connection if connection attempt fails ([#10873](https://github.com/n8n-io/n8n/issues/10873)) ([ee7147c](https://github.com/n8n-io/n8n/commit/ee7147c6b3b053ac8fc317319ab257204e599f16))
|
||||
* **MySQL Node:** Fix "Maximum call stack size exceeded" error when handling a large number of rows ([#10965](https://github.com/n8n-io/n8n/issues/10965)) ([62159bd](https://github.com/n8n-io/n8n/commit/62159bd71c9a0303b597a68113e0ac50473ee8d4))
|
||||
* **Notion Node:** Allow UUID v8 in notion id checks ([#10938](https://github.com/n8n-io/n8n/issues/10938)) ([46beda0](https://github.com/n8n-io/n8n/commit/46beda05f6771c31bcf0b6a781976d8261079a66))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Brandfetch Node:** Update to use new API ([#10877](https://github.com/n8n-io/n8n/issues/10877)) ([08ba9a3](https://github.com/n8n-io/n8n/commit/08ba9a36a43b6c84f69bb04fa4d6419a7a4adddf))
|
||||
* **editor:** Setup Sentry integration ([#10945](https://github.com/n8n-io/n8n/issues/10945)) ([6de4dff](https://github.com/n8n-io/n8n/commit/6de4dfff87e4da888567081a9928d9682bdea11d))
|
||||
* **editor:** Show a notice before deleting annotated executions ([#10934](https://github.com/n8n-io/n8n/issues/10934)) ([dcc1c72](https://github.com/n8n-io/n8n/commit/dcc1c72fc4b56c3252183541b22da801804d4f79))
|
||||
* Page size 1 option ([#10957](https://github.com/n8n-io/n8n/issues/10957)) ([bdc0622](https://github.com/n8n-io/n8n/commit/bdc0622f59e98c9e6c542f5cb59a2dbd9008ba96))
|
||||
* **Slack Node:** Add option to hide workflow link on message update ([#10927](https://github.com/n8n-io/n8n/issues/10927)) ([422c946](https://github.com/n8n-io/n8n/commit/422c9463c8d931a728615a1fe5a10f05a96ecaa2))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **editor:** Use virtual scrolling in `RunDataJson.vue` ([#10838](https://github.com/n8n-io/n8n/issues/10838)) ([f5474ff](https://github.com/n8n-io/n8n/commit/f5474ff79198a2f5a145d0a9df1bb651ea677ec5))
|
||||
|
||||
|
||||
|
||||
# [1.60.0](https://github.com/n8n-io/n8n/compare/n8n@1.59.0...n8n@1.60.0) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Azure OpenAI Chat Model Node:** Add response format option ([#10851](https://github.com/n8n-io/n8n/issues/10851)) ([0b5299a](https://github.com/n8n-io/n8n/commit/0b5299a248fdd451ceabb98ff6a2b38e818d02f8))
|
||||
* **Contentful Node:** Add missing additional fields to entry > get ([#10830](https://github.com/n8n-io/n8n/issues/10830)) ([c43aef1](https://github.com/n8n-io/n8n/commit/c43aef1a266cc6ccf8f778c290f8cb8ba2ee28cf))
|
||||
* **core:** Prevent shutdown error in regular mode ([#10844](https://github.com/n8n-io/n8n/issues/10844)) ([acb4194](https://github.com/n8n-io/n8n/commit/acb4194fa1a1d0497dd1f48045f069e1db28c432))
|
||||
* **core:** Restore queue listeners for `webhook` process ([#10781](https://github.com/n8n-io/n8n/issues/10781)) ([86f4877](https://github.com/n8n-io/n8n/commit/86f4877bab978a1ec2f53df23b6c515507cd8f72))
|
||||
* **editor:** Add missing node parameter values to AI Assistant request ([#10788](https://github.com/n8n-io/n8n/issues/10788)) ([d65ade4](https://github.com/n8n-io/n8n/commit/d65ade4e92eed3cfc47854d493fac6885a1a852b))
|
||||
* **editor:** Address edge toolbar rendering glitches ([#10839](https://github.com/n8n-io/n8n/issues/10839)) ([e0c0dde](https://github.com/n8n-io/n8n/commit/e0c0ddee59e889f50dd5033d0a933bad60fb7e3a))
|
||||
* **editor:** Allow custom git repo urls in source control settings ([#10849](https://github.com/n8n-io/n8n/issues/10849)) ([a63a9b5](https://github.com/n8n-io/n8n/commit/a63a9b53f06d3a00e8e76c0ab9f2571604c01922))
|
||||
* **editor:** Fix completion on $input.item. in Code node ([#10800](https://github.com/n8n-io/n8n/issues/10800)) ([45dccf3](https://github.com/n8n-io/n8n/commit/45dccf3d0c8282987833962a8e3f3a77d256ea37))
|
||||
* **editor:** Make expression edit modal read-only in executions view ([#10806](https://github.com/n8n-io/n8n/issues/10806)) ([394ef88](https://github.com/n8n-io/n8n/commit/394ef888433b1d48593531ab9eea93a3c3ae6040))
|
||||
* **editor:** Make schema view search copy more clear ([#10807](https://github.com/n8n-io/n8n/issues/10807)) ([7f1c131](https://github.com/n8n-io/n8n/commit/7f1c131b72ad1b98b4a8c976b8a0ef5d963d5f1f))
|
||||
* **editor:** Minimap Show nodes outside viewport ([#10843](https://github.com/n8n-io/n8n/issues/10843)) ([9c95db8](https://github.com/n8n-io/n8n/commit/9c95db8282c9f3cef5568aa9793ca977d4d8a347))
|
||||
* **editor:** Prevent clipboard XSS injection ([#10805](https://github.com/n8n-io/n8n/issues/10805)) ([db846d3](https://github.com/n8n-io/n8n/commit/db846d3235a360b4b729312b6ffe0d75be08fd45))
|
||||
* **editor:** Render image binary-data using img tags ([#10829](https://github.com/n8n-io/n8n/issues/10829)) ([7c23101](https://github.com/n8n-io/n8n/commit/7c23101ab8c12b735a17deb35637f3f12c00aeb0))
|
||||
* **editor:** Replace v-html with custom directive to sanitize html ([#10804](https://github.com/n8n-io/n8n/issues/10804)) ([44e5fb9](https://github.com/n8n-io/n8n/commit/44e5fb9b06c794033204ef1744b54b3b87160082))
|
||||
* **editor:** Restore V1 keybinding, Space Key to toggle panning ([#10841](https://github.com/n8n-io/n8n/issues/10841)) ([5a1db6d](https://github.com/n8n-io/n8n/commit/5a1db6db1adad43887e839181719818474bc66b0))
|
||||
* Fix telemetry causing console error ([#10828](https://github.com/n8n-io/n8n/issues/10828)) ([3be31e2](https://github.com/n8n-io/n8n/commit/3be31e27edc6e71400bde23f992ba98b2365bcff))
|
||||
* **Google Vertex Chat Model Node:** Clean service account private key ([#10770](https://github.com/n8n-io/n8n/issues/10770)) ([e6d84db](https://github.com/n8n-io/n8n/commit/e6d84db89930afc16f4a08fae87d8af4a059e6d7))
|
||||
* **HTTP Request Tool Node:** Fix subsequent tool calls reusung the same options ([#10808](https://github.com/n8n-io/n8n/issues/10808)) ([d647ef4](https://github.com/n8n-io/n8n/commit/d647ef41acf672177ea5e8ce0e99d78c565e34b2))
|
||||
* **OpenAI Node, Basic LLM Chain Node, Tool Agent Node:** Better OpenAI API rate limit errors ([#10797](https://github.com/n8n-io/n8n/issues/10797)) ([ab83c4b](https://github.com/n8n-io/n8n/commit/ab83c4b4166d5ad5f4ca46a636f83c8802fe3ec0))
|
||||
* Prevent copying workflow when copying outside of canvas ([#10813](https://github.com/n8n-io/n8n/issues/10813)) ([22c1890](https://github.com/n8n-io/n8n/commit/22c1890139c89e74df67b9673a1d0c85d647eb9d))
|
||||
* **RSS Feed Trigger Node:** Handle empty items gracefully ([#10855](https://github.com/n8n-io/n8n/issues/10855)) ([c55df63](https://github.com/n8n-io/n8n/commit/c55df63abc234ace6ac8e54ed094d10797671264))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Allow customizing max file size in form-data payloads for webhooks ([#10857](https://github.com/n8n-io/n8n/issues/10857)) ([a3335e0](https://github.com/n8n-io/n8n/commit/a3335e0ecd3796c874985d3c6fbbaabc35dc3490))
|
||||
* **core:** Introduce worker metrics ([#10850](https://github.com/n8n-io/n8n/issues/10850)) ([08ebe1e](https://github.com/n8n-io/n8n/commit/08ebe1e4807b3d7b4a4840887cbb30f547a5c89a))
|
||||
* **editor:** Add truncate directive ([#10842](https://github.com/n8n-io/n8n/issues/10842)) ([57836cc](https://github.com/n8n-io/n8n/commit/57836cc17a57c790d2ffb2463abb16a03321eb59))
|
||||
* **editor:** Show Collaboration pane only when there are multiple active users ([#10772](https://github.com/n8n-io/n8n/issues/10772)) ([a0af1d9](https://github.com/n8n-io/n8n/commit/a0af1d9a06c78d29f215dc010332ea7c8f28717d))
|
||||
* **Invoice Ninja Node:** Add actions for bank transactions ([#10389](https://github.com/n8n-io/n8n/issues/10389)) ([5a2c7e0](https://github.com/n8n-io/n8n/commit/5a2c7e00a0ca1a151a7fec56da5f99b086c25b1f))
|
||||
* **OpenAI Node:** Include O1 models in the models select ([#10801](https://github.com/n8n-io/n8n/issues/10801)) ([b2b1abc](https://github.com/n8n-io/n8n/commit/b2b1abc5319bdbf2bc855649ea27359b22aba009))
|
||||
|
||||
|
||||
|
||||
# [1.59.0](https://github.com/n8n-io/n8n/compare/n8n@1.58.0...n8n@1.59.0) (2024-09-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Chat Trigger Node:** Fix auth in "Embedded Chat" mode ([#10734](https://github.com/n8n-io/n8n/issues/10734)) ([96db501](https://github.com/n8n-io/n8n/commit/96db501a615ff7ec91bb66ea49532a2c6ca2a172))
|
||||
* **core:** Allow license:clear command to be used for licenses that failed renewal ([#10665](https://github.com/n8n-io/n8n/issues/10665)) ([a422c5a](https://github.com/n8n-io/n8n/commit/a422c5ac7b8f609eeab891230d9660f71bf225c5))
|
||||
* **core:** Update subworkflow execution status correctly ([#10764](https://github.com/n8n-io/n8n/issues/10764)) ([4f94319](https://github.com/n8n-io/n8n/commit/4f94319cd93885ebe830fa1f0e6b757de80f7356))
|
||||
* **editor:** Add arrow end to connection line ([#10704](https://github.com/n8n-io/n8n/issues/10704)) ([43713dc](https://github.com/n8n-io/n8n/commit/43713dcd89fcb98ea7e24d27127861fc4b0d7872))
|
||||
* **editor:** Add sticky note readonly state in new canvas ([#10678](https://github.com/n8n-io/n8n/issues/10678)) ([c5bc8e6](https://github.com/n8n-io/n8n/commit/c5bc8e6eb9eadadf44f763e5e5aac4b35d03cc31))
|
||||
* **editor:** Auto-focus expression input when switching from "fixed" mode ([#10686](https://github.com/n8n-io/n8n/issues/10686)) ([54ab2b1](https://github.com/n8n-io/n8n/commit/54ab2b14e41fe84a455c7e7d5c73d7347844d2fb))
|
||||
* **editor:** Don't render pinned icon for disabled nodes ([#10712](https://github.com/n8n-io/n8n/issues/10712)) ([879b837](https://github.com/n8n-io/n8n/commit/879b8375812106b3f6909b7de27858175ba5575d))
|
||||
* **editor:** Fix error rendering and indexing of LLM sub-node outputs ([#10688](https://github.com/n8n-io/n8n/issues/10688)) ([50459ba](https://github.com/n8n-io/n8n/commit/50459bacab517bacb97d2884fda69f8412c9960c))
|
||||
* **editor:** Fix xss issues in toast usages ([#10733](https://github.com/n8n-io/n8n/issues/10733)) ([6df6f5f](https://github.com/n8n-io/n8n/commit/6df6f5f8df9a8fc0899524a1b69859815eeb341f))
|
||||
* **editor:** Follow up fixes and improvements to viewer role ([#10684](https://github.com/n8n-io/n8n/issues/10684)) ([63548e6](https://github.com/n8n-io/n8n/commit/63548e6ead5c122732628b5feb1515f492d5e033))
|
||||
* **editor:** Increase connector snap radius ([#10757](https://github.com/n8n-io/n8n/issues/10757)) ([297b668](https://github.com/n8n-io/n8n/commit/297b668f32f9ecfc82c1205ea4e915408cab482e))
|
||||
* **editor:** Plus node button should not be visible on readonly mode ([#10692](https://github.com/n8n-io/n8n/issues/10692)) ([62cb189](https://github.com/n8n-io/n8n/commit/62cb189985035c447ad31c275337b3fb24089265))
|
||||
* **editor:** Prevent action's panel flickering while dragging a node ([#10739](https://github.com/n8n-io/n8n/issues/10739)) ([efa5573](https://github.com/n8n-io/n8n/commit/efa5573278a60d55d5b509aac48cc112c79334d2))
|
||||
* **editor:** Restrict when the collision avoidance algorithm is used ([#10755](https://github.com/n8n-io/n8n/issues/10755)) ([bf43d67](https://github.com/n8n-io/n8n/commit/bf43d673571b2fc18fe5d660171f0da165909dfc))
|
||||
* **editor:** Show docs link in credential modal when docs sidebar is hidden ([#10750](https://github.com/n8n-io/n8n/issues/10750)) ([87333cb](https://github.com/n8n-io/n8n/commit/87333cbefebe652256fa1d60ba7a4b946fdfe17d))
|
||||
* **Email Trigger (IMAP) Node:** Ensure connection close does not block deactivation ([#10689](https://github.com/n8n-io/n8n/issues/10689)) ([156eb72](https://github.com/n8n-io/n8n/commit/156eb72ebefa1d963ff46eff6652e2c947ef031b))
|
||||
* Fix the issue in Trigger Nodes where poll time was not loaded ([#10695](https://github.com/n8n-io/n8n/issues/10695)) ([1dea8f4](https://github.com/n8n-io/n8n/commit/1dea8f4c7da2a04434c274faf8e0a9a7a693f5a4))
|
||||
* **Gmail Trigger Node:** Change Gmail Trigger dedupe logic ([#10717](https://github.com/n8n-io/n8n/issues/10717)) ([9f3e03d](https://github.com/n8n-io/n8n/commit/9f3e03d728d8acda5ae4166c5837b00cb1311e96))
|
||||
* Google Contacts node warm up request, Google Calendar node events>getAll fields option ([#10700](https://github.com/n8n-io/n8n/issues/10700)) ([22c70d5](https://github.com/n8n-io/n8n/commit/22c70d50697023cf448a379d7778695abb718ce9))
|
||||
* **If Node:** Update copy for type conversion parameter ([#10769](https://github.com/n8n-io/n8n/issues/10769)) ([ee5fbc5](https://github.com/n8n-io/n8n/commit/ee5fbc543ce1d33a56cf118dbd048d6693a15875))
|
||||
* **n8n Form Trigger Node:** Do not rerun trigger when it has run data ([#10687](https://github.com/n8n-io/n8n/issues/10687)) ([3adbcab](https://github.com/n8n-io/n8n/commit/3adbcab27de34ea5a2c7a88b2ad0d80e3f6d4a0b))
|
||||
* **OpenAI Chat Model Node:** Prevent filtering of fine-tuned models in model selector ([#10662](https://github.com/n8n-io/n8n/issues/10662)) ([4e89912](https://github.com/n8n-io/n8n/commit/4e899125884bdd97c97446d90e89668688fe7573))
|
||||
* Prevent AI assistant session reset when workflow is saved ([#10707](https://github.com/n8n-io/n8n/issues/10707)) ([91d9be2](https://github.com/n8n-io/n8n/commit/91d9be20667c20599f64a24fa99386c78476d425))
|
||||
* Show a more user friendly error message if initial Db connection times out ([#10682](https://github.com/n8n-io/n8n/issues/10682)) ([4efcbc5](https://github.com/n8n-io/n8n/commit/4efcbc593685286837022e5600d81e67f3e0131c))
|
||||
* **Webflow Node:** Update scopes to include forms ([#10554](https://github.com/n8n-io/n8n/issues/10554)) ([d3861b3](https://github.com/n8n-io/n8n/commit/d3861b31ceef16f566c525c7651453a1b84ed2a4))
|
||||
* **YouTube Node:** Fix Date filters ([#10725](https://github.com/n8n-io/n8n/issues/10725)) ([21936c8](https://github.com/n8n-io/n8n/commit/21936c88a84b8c03a8d02391cb7112b0e4d9f1f9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Code Tool Node:** Option to specify input schema ([#10693](https://github.com/n8n-io/n8n/issues/10693)) ([421aa71](https://github.com/n8n-io/n8n/commit/421aa712515d9beeae7c0201b173cb7324473f69))
|
||||
* **editor:** Add lint for $('Node').item in runOnceForAllItems mode ([#10743](https://github.com/n8n-io/n8n/issues/10743)) ([1b04be1](https://github.com/n8n-io/n8n/commit/1b04be1240ec29151e79162680907710c71c6488))
|
||||
* **editor:** Logs markdown block improvements ([#10681](https://github.com/n8n-io/n8n/issues/10681)) ([db6e832](https://github.com/n8n-io/n8n/commit/db6e8326c7119d90fa6a51f82099026f50587202))
|
||||
* Filter parameter: Improve loose type validation for booleans ([#10702](https://github.com/n8n-io/n8n/issues/10702)) ([e9b8d99](https://github.com/n8n-io/n8n/commit/e9b8d99084f0ea2063a1d691928025e534980b4e))
|
||||
* **Lemlist Node:** Add V2 to support more API operations ([#10615](https://github.com/n8n-io/n8n/issues/10615)) ([20b1cf2](https://github.com/n8n-io/n8n/commit/20b1cf2b7597c78e28f522945b8cbad2ee535cd7))
|
||||
* **OpenAI Node:** Add Max Tools Iteration parameter and prevent tool calling after execution is aborted ([#10735](https://github.com/n8n-io/n8n/issues/10735)) ([5c47a5f](https://github.com/n8n-io/n8n/commit/5c47a5f691d42dae84a9df8a32a5ea600d83f6dd))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **editor:** Fix WorkflowDetails excessive re-rendering ([#10767](https://github.com/n8n-io/n8n/issues/10767)) ([00013a2](https://github.com/n8n-io/n8n/commit/00013a2069fff5e5d9398c5921c90d34dc384299))
|
||||
|
||||
|
||||
|
||||
# [1.58.0](https://github.com/n8n-io/n8n/compare/n8n@1.57.0...n8n@1.58.0) (2024-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AI Agent Node:** Fix tools agent when using memory and Anthropic models ([#10513](https://github.com/n8n-io/n8n/issues/10513)) ([746e7b8](https://github.com/n8n-io/n8n/commit/746e7b89f7e9b99126fb69110773548dfe91b74f))
|
||||
* **API:** Update express-openapi-validator to resolve AIKIDO-2024-10229 ([#10612](https://github.com/n8n-io/n8n/issues/10612)) ([1dcb814](https://github.com/n8n-io/n8n/commit/1dcb814ced7cfbc80eddbb4bc03108341a9f27f5))
|
||||
* **core:** Declutter webhook insertion errors ([#10650](https://github.com/n8n-io/n8n/issues/10650)) ([36177b0](https://github.com/n8n-io/n8n/commit/36177b0943cf72bae3b0075453498dd1e41684d0))
|
||||
* **core:** Flush responses for ai streaming endpoints ([#10633](https://github.com/n8n-io/n8n/issues/10633)) ([6bb6a5c](https://github.com/n8n-io/n8n/commit/6bb6a5c6cd1da3503a1a2b35bcf4c685cd3f964f))
|
||||
* **core:** Tighten check for company size survey answer ([#10646](https://github.com/n8n-io/n8n/issues/10646)) ([e5aba60](https://github.com/n8n-io/n8n/commit/e5aba60afff93364d91f17c00ea18d38d9dbc970))
|
||||
* **editor:** Add confirmation toast when changing user role ([#10592](https://github.com/n8n-io/n8n/issues/10592)) ([95da4d4](https://github.com/n8n-io/n8n/commit/95da4d4797e800c04b2b17c23c941c785dd62393))
|
||||
* **editor:** Add pinned data only to manual executions in execution view ([#10605](https://github.com/n8n-io/n8n/issues/10605)) ([a12e9ed](https://github.com/n8n-io/n8n/commit/a12e9edac042957939c63f0a5c35572930632352))
|
||||
* **editor:** Add tooltips to workflow history button ([#10570](https://github.com/n8n-io/n8n/issues/10570)) ([4a125f5](https://github.com/n8n-io/n8n/commit/4a125f511c5537977652900b7712a2ad908140e7))
|
||||
* **editor:** Allow disabling SSO when config request fails ([#10635](https://github.com/n8n-io/n8n/issues/10635)) ([ce39933](https://github.com/n8n-io/n8n/commit/ce39933766fa18107f4082de0cba0b6702cbbbfa))
|
||||
* **editor:** Fix notification rendering HTML as text ([#10642](https://github.com/n8n-io/n8n/issues/10642)) ([5eba534](https://github.com/n8n-io/n8n/commit/5eba5343191665cd4639632ba303464176c279c4))
|
||||
* **editor:** Fix opening executions tab from a new, unsaved workflow ([#10652](https://github.com/n8n-io/n8n/issues/10652)) ([cd0891e](https://github.com/n8n-io/n8n/commit/cd0891e4f1cfdc90b2090958a39564ba99534627))
|
||||
* **Gmail Trigger Node:** Don't return date instances, but date strings instead ([#10582](https://github.com/n8n-io/n8n/issues/10582)) ([9e1dac0](https://github.com/n8n-io/n8n/commit/9e1dac04655a20c5c7b99552742312fd9237604b))
|
||||
* **HTTP Request Node:** Sanitize authorization headers ([#10607](https://github.com/n8n-io/n8n/issues/10607)) ([405c55a](https://github.com/n8n-io/n8n/commit/405c55a1f7cf34e7b6e46a86031ef9a41956ca78))
|
||||
* **Wait Node:** Append n8n attribution option ([#10585](https://github.com/n8n-io/n8n/issues/10585)) ([81f4322](https://github.com/n8n-io/n8n/commit/81f4322d456773281aec4b47447465bdffd311fe))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Execution curation ([#10342](https://github.com/n8n-io/n8n/issues/10342)) ([022ddcb](https://github.com/n8n-io/n8n/commit/022ddcbef9f1ac1b89bcfd5f7759d67325b07392))
|
||||
* **core:** Implement wrapping of regular nodes as AI Tools ([#10641](https://github.com/n8n-io/n8n/issues/10641)) ([da44fe4](https://github.com/n8n-io/n8n/commit/da44fe4b8967055b7b1f849750e1fafa0ba67218))
|
||||
* **core:** Introduce DB health check ([#10661](https://github.com/n8n-io/n8n/issues/10661)) ([a8e80d0](https://github.com/n8n-io/n8n/commit/a8e80d0c4b7531fe32be1d4057656885359f42fc))
|
||||
* **core:** Make Postgres connection timeout configurable ([#10670](https://github.com/n8n-io/n8n/issues/10670)) ([8154031](https://github.com/n8n-io/n8n/commit/81540318b4c55f3a09c9776e23d2211abdbd36f7))
|
||||
* **core:** Switch to MJML for email templates ([#10518](https://github.com/n8n-io/n8n/issues/10518)) ([dbc10fe](https://github.com/n8n-io/n8n/commit/dbc10fe9f522f31eb06add6f3f6863ce24510547))
|
||||
* **editor:** Add A/B testing feature flag for credential docs modal ([#10664](https://github.com/n8n-io/n8n/issues/10664)) ([899b0a1](https://github.com/n8n-io/n8n/commit/899b0a19efc49c1c087f78bbb1a59d726a510965))
|
||||
* **editor:** Add AI Assistant support chat ([#10656](https://github.com/n8n-io/n8n/issues/10656)) ([3a80780](https://github.com/n8n-io/n8n/commit/3a8078068e5c0b01dfd34ff838fe1b30d604abc6))
|
||||
* **editor:** Implement new app layout ([#10548](https://github.com/n8n-io/n8n/issues/10548)) ([95a9cd2](https://github.com/n8n-io/n8n/commit/95a9cd2c739cf4f817eb8df6509a9112ac24a3b1))
|
||||
* **editor:** Make highlighted data pane floating ([#10638](https://github.com/n8n-io/n8n/issues/10638)) ([8b5c333](https://github.com/n8n-io/n8n/commit/8b5c333d3dca03ba51a5873b75451fbfafc5ae15))
|
||||
* More hints to nodes ([#10565](https://github.com/n8n-io/n8n/issues/10565)) ([66ddb4a](https://github.com/n8n-io/n8n/commit/66ddb4a6f367602c9aaad1bfb0cc6fac3facd15e))
|
||||
* **Postgres PGVector Store Node:** Add PGVector vector store node ([#10517](https://github.com/n8n-io/n8n/issues/10517)) ([650389d](https://github.com/n8n-io/n8n/commit/650389d90763a45c037e74a1a1193c3cbe103a16))
|
||||
* Reintroduce collaboration feature ([#10602](https://github.com/n8n-io/n8n/issues/10602)) ([2ea2bfe](https://github.com/n8n-io/n8n/commit/2ea2bfe762c02047e522f28dd97f197735b3fb46))
|
||||
* **Text Classifier Node:** Add output fixing parser ([#10667](https://github.com/n8n-io/n8n/issues/10667)) ([aa37c32](https://github.com/n8n-io/n8n/commit/aa37c32f266ffff93cd903888b1c15caa0468830))
|
||||
|
||||
|
||||
|
||||
# [1.57.0](https://github.com/n8n-io/n8n/compare/n8n@1.56.0...n8n@1.57.0) (2024-08-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AI Agent Node:** Allow AWS Bedrock Chat to be used with conversational agent ([#10489](https://github.com/n8n-io/n8n/issues/10489)) ([bdcc657](https://github.com/n8n-io/n8n/commit/bdcc657965af5f604aac1eaff7d937f69a08ce1c))
|
||||
* **core:** Make boolean config value parsing backward-compatible ([#10560](https://github.com/n8n-io/n8n/issues/10560)) ([70b410f](https://github.com/n8n-io/n8n/commit/70b410f4b00dd599fcd4249aa105098aa262da66))
|
||||
* **core:** Restore Redis cache key ([#10520](https://github.com/n8n-io/n8n/issues/10520)) ([873056a](https://github.com/n8n-io/n8n/commit/873056a92e52cc629d2873c960656d5f06d4728e))
|
||||
* **core:** Scheduler tasks should not trigger on follower instances ([#10507](https://github.com/n8n-io/n8n/issues/10507)) ([3428f28](https://github.com/n8n-io/n8n/commit/3428f28a732f79e067b3cb515cc59d835de246a6))
|
||||
* **core:** Stop explicit redis client disconnect on shutdown ([#10551](https://github.com/n8n-io/n8n/issues/10551)) ([f712812](https://github.com/n8n-io/n8n/commit/f71281221efb79d65d8d7610c292bc90cef13d7a))
|
||||
* **editor:** Ensure `Datatable` component renders `All` option ([#10525](https://github.com/n8n-io/n8n/issues/10525)) ([bc27beb](https://github.com/n8n-io/n8n/commit/bc27beb6629883003a8991d7e840ffaa066d41ac))
|
||||
* **editor:** Prevent Safari users from accessing the frontend over insecure contexts ([#10510](https://github.com/n8n-io/n8n/issues/10510)) ([a73b9a3](https://github.com/n8n-io/n8n/commit/a73b9a38d6c48e2f78593328e7d9933f2493dbb6))
|
||||
* **editor:** Scale output item selector input width with value ([#10555](https://github.com/n8n-io/n8n/issues/10555)) ([52c574d](https://github.com/n8n-io/n8n/commit/52c574d83f344f03b0e39984bbc3ac0402e50791))
|
||||
* **Google Sheets Trigger Node:** Show sheet name is too long error ([#10542](https://github.com/n8n-io/n8n/issues/10542)) ([4e15007](https://github.com/n8n-io/n8n/commit/4e1500757700ec984cdad8b9cfcd76ee00ae127e))
|
||||
* **Wait Node:** Prevent waiting until invalid date ([#10523](https://github.com/n8n-io/n8n/issues/10523)) ([c0e7620](https://github.com/n8n-io/n8n/commit/c0e7620036738f8d0b382d0d0610b981dcbc29e0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add new credentials for the HTTP Request node ([#9833](https://github.com/n8n-io/n8n/issues/9833)) ([26f1af3](https://github.com/n8n-io/n8n/commit/26f1af397b2b25e3394fc2dae91a5c281bf33d66))
|
||||
* **AI Agent Node:** Add tutorial link to agent node ([#10493](https://github.com/n8n-io/n8n/issues/10493)) ([5c7cc36](https://github.com/n8n-io/n8n/commit/5c7cc36c23e58a47a1e71911e7303a1bd54f167e))
|
||||
* **core:** Expose queue metrics for Prometheus ([#10559](https://github.com/n8n-io/n8n/issues/10559)) ([008c510](https://github.com/n8n-io/n8n/commit/008c510b7623fefb8c60730c7eac54dd9bb2e3fc))
|
||||
* **editor:** Implement workflowSelector parameter type ([#10482](https://github.com/n8n-io/n8n/issues/10482)) ([84e54be](https://github.com/n8n-io/n8n/commit/84e54beac763f25399c9687f695f1e658e3ce434))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **core:** Make execution queries faster ([#9817](https://github.com/n8n-io/n8n/issues/9817)) ([dc7dc99](https://github.com/n8n-io/n8n/commit/dc7dc995d5e2ea8fbd0dcb54cfa8aa93ecb437c9))
|
||||
|
||||
### Other
|
||||
* **Add user journey link to [n8n.io](https://n8n.io)** ([#10331](https://github.com/n8n-io/n8n/pull/10331))
|
||||
|
||||
|
||||
# [1.56.0](https://github.com/n8n-io/n8n/compare/n8n@1.55.0...n8n@1.56.0) (2024-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Better errors in Switch, If and Filter nodes ([#10457](https://github.com/n8n-io/n8n/issues/10457)) ([aea82cb](https://github.com/n8n-io/n8n/commit/aea82cb74421d516919742127daf669808b57604))
|
||||
* **Calendly Trigger Node:** Fix issue with webhook url matching ([#10378](https://github.com/n8n-io/n8n/issues/10378)) ([09c3a8b](https://github.com/n8n-io/n8n/commit/09c3a8b36733a9634ef5948922d6aa7a19bbb592))
|
||||
* **core:** Fix payload property in `workflow-post-execute` event ([#10413](https://github.com/n8n-io/n8n/issues/10413)) ([d98e29e](https://github.com/n8n-io/n8n/commit/d98e29e3d53de87aec276260615fa60473a2692f))
|
||||
* **core:** Fix XSS validation and separate URL validation ([#10424](https://github.com/n8n-io/n8n/issues/10424)) ([91467ab](https://github.com/n8n-io/n8n/commit/91467ab325e4c71c20c522f3143246d270101626))
|
||||
* **core:** Replace `sanitize-html` with `xss` in XSS validator constraint ([#10479](https://github.com/n8n-io/n8n/issues/10479)) ([5dea51a](https://github.com/n8n-io/n8n/commit/5dea51aad7d9e7ffc676d16f4bbbdecce5876f0b))
|
||||
* **core:** Use class-validator with XSS check for survey answers ([#10490](https://github.com/n8n-io/n8n/issues/10490)) ([547a606](https://github.com/n8n-io/n8n/commit/547a60642ce9e54819d4e600c822d87dabd59b2e))
|
||||
* **core:** Use explicit types in configs to ensure valid decorator metadata ([#10433](https://github.com/n8n-io/n8n/issues/10433)) ([2043daa](https://github.com/n8n-io/n8n/commit/2043daa2570bc04b0b8d41f277901a8cc8a7b98f))
|
||||
* **editor:** Add workflow scopes when initializing workflow ([#10455](https://github.com/n8n-io/n8n/issues/10455)) ([b857c2c](https://github.com/n8n-io/n8n/commit/b857c2cda0a9e4386a540d5e1e741570d9453588))
|
||||
* **editor:** Buffer json chunks in stream response ([#10439](https://github.com/n8n-io/n8n/issues/10439)) ([37797f3](https://github.com/n8n-io/n8n/commit/37797f38d81b12d030ba85034baeb49192ea575c))
|
||||
* **editor:** Fix flaky mapping tests ([#10453](https://github.com/n8n-io/n8n/issues/10453)) ([fc6d413](https://github.com/n8n-io/n8n/commit/fc6d4138d58282f676b32f1a6011b1b6d0184bf2))
|
||||
* **editor:** Fix overflow in AI Assistant chat messages ([#10491](https://github.com/n8n-io/n8n/issues/10491)) ([4a6ca63](https://github.com/n8n-io/n8n/commit/4a6ca632100731f85875c639f2164bf1ef415009))
|
||||
* **editor:** Highlight matching type in filter component ([#10425](https://github.com/n8n-io/n8n/issues/10425)) ([6bca879](https://github.com/n8n-io/n8n/commit/6bca879d4ae30c7f9a35e8d6672de42cf93be727))
|
||||
* **editor:** Show item count in output panel schema view ([#10426](https://github.com/n8n-io/n8n/issues/10426)) ([4dee7cc](https://github.com/n8n-io/n8n/commit/4dee7cc36e5f7768d0b71095b194bf357c92e941))
|
||||
* **editor:** Truncate long data pill labels in schema view ([#10427](https://github.com/n8n-io/n8n/issues/10427)) ([1bf2f4f](https://github.com/n8n-io/n8n/commit/1bf2f4f6171d666391bb3a3a312468bc083446e3))
|
||||
* Filter component - improve errors ([#10456](https://github.com/n8n-io/n8n/issues/10456)) ([61ac0c7](https://github.com/n8n-io/n8n/commit/61ac0c77755210f570b887951fe6bbec1a323811))
|
||||
* **Google Sheets Node:** Better error when column to match on is empty ([#10442](https://github.com/n8n-io/n8n/issues/10442)) ([ce46bf5](https://github.com/n8n-io/n8n/commit/ce46bf516a86d9779f37dd75b0c680d26d88e15d))
|
||||
* **Google Sheets Node:** Update name and hint for useAppend option ([#10443](https://github.com/n8n-io/n8n/issues/10443)) ([c5a0c04](https://github.com/n8n-io/n8n/commit/c5a0c049eaf44419c690d151de42fb0c10bd406e))
|
||||
* **Google Sheets Node:** Update to returnAllMatches option ([#10440](https://github.com/n8n-io/n8n/issues/10440)) ([f7fb02e](https://github.com/n8n-io/n8n/commit/f7fb02e92a756781f8e35bbbfc25d71c12cb70af))
|
||||
* **Invoice Ninja Node:** Fix payment types ([#10462](https://github.com/n8n-io/n8n/issues/10462)) ([129245d](https://github.com/n8n-io/n8n/commit/129245da10be1d645f61e929e40b128bd7813f17))
|
||||
* **n8n Form Trigger Node:** Show basic authentication modal on wrong credentials ([#10423](https://github.com/n8n-io/n8n/issues/10423)) ([0dc3e99](https://github.com/n8n-io/n8n/commit/0dc3e99b26bec45e747d83f383cfe5169d89e6b7))
|
||||
* **OpenAI Node:** Throw node operations error in case of openAi client error ([#10448](https://github.com/n8n-io/n8n/issues/10448)) ([0d3ed46](https://github.com/n8n-io/n8n/commit/0d3ed461996bbad06015c455f133baab6506437f))
|
||||
* Project Viewer always seeing a connection error when testing credentials ([#10417](https://github.com/n8n-io/n8n/issues/10417)) ([613cdd2](https://github.com/n8n-io/n8n/commit/613cdd2ba2c0f224c4857a5fc3eea36dbd683049))
|
||||
* Remove unimplemented Postgres credentials options ([#10461](https://github.com/n8n-io/n8n/issues/10461)) ([17ac784](https://github.com/n8n-io/n8n/commit/17ac7844f29d819b91dfaf90b9fe386d98060c42))
|
||||
* Rename Assistant back ([#10481](https://github.com/n8n-io/n8n/issues/10481)) ([c410aed](https://github.com/n8n-io/n8n/commit/c410aed4c22182943dc80ede63acda00b7898e10))
|
||||
* Require mfa code to change email ([#10354](https://github.com/n8n-io/n8n/issues/10354)) ([39c8e50](https://github.com/n8n-io/n8n/commit/39c8e50ad0513649f5a8cef911b7d6cdd61c2372))
|
||||
* **Respond to Webhook Node:** Fix issue preventing the chat trigger from working ([#9886](https://github.com/n8n-io/n8n/issues/9886)) ([9d6ad88](https://github.com/n8n-io/n8n/commit/9d6ad88c14a88fd0dfcb4f9981e38d19cf5f3067))
|
||||
* Show input names when node has multiple inputs ([#10434](https://github.com/n8n-io/n8n/issues/10434)) ([973956c](https://github.com/n8n-io/n8n/commit/973956cc26c78c329ff6eb6934d4f0a24060c87c))
|
||||
* **Toggl Trigger Node:** Update API version ([#10207](https://github.com/n8n-io/n8n/issues/10207)) ([9bdb1d6](https://github.com/n8n-io/n8n/commit/9bdb1d6dca43fe491c5eb96f093b7eec4509eaff))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Support bidirectional communication between specific mains and specific workers ([#10377](https://github.com/n8n-io/n8n/issues/10377)) ([d0fc9de](https://github.com/n8n-io/n8n/commit/d0fc9dee0e17211c1ed130b19286e9573c9ebfbd))
|
||||
* **Facebook Graph API Node:** Update node to support API v18 - v20 ([#10419](https://github.com/n8n-io/n8n/issues/10419)) ([e7ee10f](https://github.com/n8n-io/n8n/commit/e7ee10f243663d899d32e61bc6264b4df444e2af))
|
||||
|
||||
|
||||
|
||||
# [1.55.0](https://github.com/n8n-io/n8n/compare/n8n@1.54.0...n8n@1.55.0) (2024-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add better error handling for chat errors ([#10408](https://github.com/n8n-io/n8n/issues/10408)) ([f82b6e4](https://github.com/n8n-io/n8n/commit/f82b6e4ba9bf527b3a4c17872162d9ae124ead0d))
|
||||
* **AI Agent Node:** Fix issues with some tools not populating ([#10406](https://github.com/n8n-io/n8n/issues/10406)) ([51a1edd](https://github.com/n8n-io/n8n/commit/51a1eddbf00393f3881c340cf37cfcca59566c99))
|
||||
* **core:** Account for cancelling an execution with no workers available ([#10343](https://github.com/n8n-io/n8n/issues/10343)) ([b044e78](https://github.com/n8n-io/n8n/commit/b044e783e73a499dbd7532a5d489a782d3d021da))
|
||||
* **core:** Account for owner when filtering by project ID in `GET /workflows` in Public API ([#10379](https://github.com/n8n-io/n8n/issues/10379)) ([5ac65b3](https://github.com/n8n-io/n8n/commit/5ac65b36bcb1351c6233b951f064f60862f790a5))
|
||||
* **core:** Enforce shutdown timer and sequence on `SIGINT` for main ([#10346](https://github.com/n8n-io/n8n/issues/10346)) ([5255793](https://github.com/n8n-io/n8n/commit/5255793afee5653d8356b8e4d2e1009d5cf36164))
|
||||
* **core:** Filter out prototype and constructor lookups in expressions ([#10382](https://github.com/n8n-io/n8n/issues/10382)) ([8e7d29a](https://github.com/n8n-io/n8n/commit/8e7d29ad3c4872b1cc147dfcfe9a864ba916692f))
|
||||
* **core:** Fix duplicate Redis publisher ([#10392](https://github.com/n8n-io/n8n/issues/10392)) ([45813de](https://github.com/n8n-io/n8n/commit/45813debc963096f63cc0aabe82d9d9f853a39d7))
|
||||
* **core:** Fix worker shutdown errors when active executions ([#10353](https://github.com/n8n-io/n8n/issues/10353)) ([e071b73](https://github.com/n8n-io/n8n/commit/e071b73bab34edd4b3e6aef6497514acc504cdc6))
|
||||
* **core:** Prevent XSS in user update endpoints ([#10338](https://github.com/n8n-io/n8n/issues/10338)) ([7898498](https://github.com/n8n-io/n8n/commit/78984986a6b4add89df9743b94c113046f1d5ee8))
|
||||
* **core:** Prevent XSS via static cache dir ([#10339](https://github.com/n8n-io/n8n/issues/10339)) ([4f392b5](https://github.com/n8n-io/n8n/commit/4f392b5e3e0ee166e85a2e060b3ec7fcf145229b))
|
||||
* **core:** Rate limit MFA activation and verification endpoints ([#10330](https://github.com/n8n-io/n8n/issues/10330)) ([b6c47c0](https://github.com/n8n-io/n8n/commit/b6c47c0e3214878d42980d5c9535df52b3984b3c))
|
||||
* **editor:** Connect up new project viewer role to the FE ([#9913](https://github.com/n8n-io/n8n/issues/9913)) ([117e2d9](https://github.com/n8n-io/n8n/commit/117e2d968fcc535f32c583ac8f2dc8a84e8cd6bd))
|
||||
* **editor:** Enable credential sharing between all types of projects ([#10233](https://github.com/n8n-io/n8n/issues/10233)) ([1cf48cc](https://github.com/n8n-io/n8n/commit/1cf48cc3019c1cf27e2f3c9affd18426237e9064))
|
||||
* **editor:** Fix rendering of SVG icons in public chat on iOS ([#10381](https://github.com/n8n-io/n8n/issues/10381)) ([7ab3811](https://github.com/n8n-io/n8n/commit/7ab38114dbf3881afba39287a061446ec4bf0431))
|
||||
* **editor:** Fixing XSS vulnerability in toast messages ([#10329](https://github.com/n8n-io/n8n/issues/10329)) ([38bdd9f](https://github.com/n8n-io/n8n/commit/38bdd9f5d0d9ca06fab1a7e1a3e7a4a648a6a89a))
|
||||
* **editor:** Revert change that hid swagger docs in the ui ([#10350](https://github.com/n8n-io/n8n/issues/10350)) ([bae49d3](https://github.com/n8n-io/n8n/commit/bae49d3198d4bcc27e7996cd4f7be3132becc98e))
|
||||
* **n8n Form Trigger Node:** Fix issue preventing v1 node from working ([#10364](https://github.com/n8n-io/n8n/issues/10364)) ([9b647a9](https://github.com/n8n-io/n8n/commit/9b647a9837434e8b75e3ad754ff5136bb5ac760d))
|
||||
* Require mfa code for password change if its enabled ([#10341](https://github.com/n8n-io/n8n/issues/10341)) ([9d7caac](https://github.com/n8n-io/n8n/commit/9d7caacc699f10962783393925a980ec6f1ca975))
|
||||
* Require mfa code to disable mfa ([#10345](https://github.com/n8n-io/n8n/issues/10345)) ([3384f52](https://github.com/n8n-io/n8n/commit/3384f52a35b835ba1d8633dc94bab0ad6e7023b3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add Ask assistant behind feature flag ([#9995](https://github.com/n8n-io/n8n/issues/9995)) ([5ed2a77](https://github.com/n8n-io/n8n/commit/5ed2a77740db1f02b27c571f4dfdfa206ebdb19c))
|
||||
* **AI Transform Node:** New node ([#10405](https://github.com/n8n-io/n8n/issues/10405)) ([4d222ac](https://github.com/n8n-io/n8n/commit/4d222ac19d943b69fd9f87abe5e5c5f5141eed8d))
|
||||
* **AI Transform Node:** New node ([#9990](https://github.com/n8n-io/n8n/issues/9990)) ([0de9d56](https://github.com/n8n-io/n8n/commit/0de9d56619ed1c055407353046b8a9ebe78da527))
|
||||
* **core:** Allow overriding npm registry for community packages ([#10325](https://github.com/n8n-io/n8n/issues/10325)) ([33a2703](https://github.com/n8n-io/n8n/commit/33a2703429d9eaa41f72d2e7d2da5be60b6c620f))
|
||||
* **editor:** Add schema view to expression modal ([#9976](https://github.com/n8n-io/n8n/issues/9976)) ([71b6c67](https://github.com/n8n-io/n8n/commit/71b6c671797024d7b516352fa9b7ecda101ce3b2))
|
||||
* **MySQL Node:** Return decimal types as numbers ([#10313](https://github.com/n8n-io/n8n/issues/10313)) ([f744d7c](https://github.com/n8n-io/n8n/commit/f744d7c100be68669d9a3efd0033dd371a3cfaf7))
|
||||
* **Okta Node:** Add Okta Node ([#10278](https://github.com/n8n-io/n8n/issues/10278)) ([5cac0f3](https://github.com/n8n-io/n8n/commit/5cac0f339d649cfe5857d33738210cbc1599370b))
|
||||
|
||||
|
||||
|
||||
# [1.54.0](https://github.com/n8n-io/n8n/compare/n8n@1.53.0...n8n@1.54.0) (2024-08-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Ensure OAuth token data is not stubbed in source control ([#10302](https://github.com/n8n-io/n8n/issues/10302)) ([98115e9](https://github.com/n8n-io/n8n/commit/98115e95df8289a8ec400a570a7f256382f8e286))
|
||||
* **core:** Fix expressions in webhook nodes(Form, Webhook) to access previous node's data ([#10247](https://github.com/n8n-io/n8n/issues/10247)) ([88a1701](https://github.com/n8n-io/n8n/commit/88a170176a3447e7f847e9cf145aeb867b1c5fcf))
|
||||
* **core:** Fix user telemetry bugs ([#10293](https://github.com/n8n-io/n8n/issues/10293)) ([42a0b59](https://github.com/n8n-io/n8n/commit/42a0b594d6ea2527c55a2aa9976c904cf70ecf92))
|
||||
* **core:** Make execution and its data creation atomic ([#10276](https://github.com/n8n-io/n8n/issues/10276)) ([ae50bb9](https://github.com/n8n-io/n8n/commit/ae50bb95a8e5bf1cdbf9483da54b84094b82e260))
|
||||
* **core:** Make OAuth1/OAuth2 callback not require auth ([#10263](https://github.com/n8n-io/n8n/issues/10263)) ([a8e2774](https://github.com/n8n-io/n8n/commit/a8e2774f5382e202556b5506c7788265786aa973))
|
||||
* **core:** Revert transactions until we remove the legacy sqlite driver ([#10299](https://github.com/n8n-io/n8n/issues/10299)) ([1eba7c3](https://github.com/n8n-io/n8n/commit/1eba7c3c763ac5b6b28c1c6fc43fc8c215249292))
|
||||
* **core:** Surface enterprise trial error message ([#10267](https://github.com/n8n-io/n8n/issues/10267)) ([432ac1d](https://github.com/n8n-io/n8n/commit/432ac1da59e173ce4c0f2abbc416743d9953ba70))
|
||||
* **core:** Upgrade tournament to address some XSS vulnerabilities ([#10277](https://github.com/n8n-io/n8n/issues/10277)) ([43ae159](https://github.com/n8n-io/n8n/commit/43ae159ea40c574f8e41bdfd221ab2bf3268eee7))
|
||||
* **core:** VM2 sandbox should not throw on `new Promise` ([#10298](https://github.com/n8n-io/n8n/issues/10298)) ([7e95f9e](https://github.com/n8n-io/n8n/commit/7e95f9e2e40a99871f1b6abcdacb39ac5f857332))
|
||||
* **core:** Webhook and form baseUrl missing ([#10290](https://github.com/n8n-io/n8n/issues/10290)) ([8131d66](https://github.com/n8n-io/n8n/commit/8131d66f8ca1b1da00597a12859ee4372148a0c9))
|
||||
* **editor:** Enable moving resources only if team projects are available by the license ([#10271](https://github.com/n8n-io/n8n/issues/10271)) ([42ba884](https://github.com/n8n-io/n8n/commit/42ba8841c401126c77158a53dc8fcbb45dfce8fd))
|
||||
* **editor:** Fix execution retry button ([#10275](https://github.com/n8n-io/n8n/issues/10275)) ([55f2ffe](https://github.com/n8n-io/n8n/commit/55f2ffe256c91a028cee95c3bbb37a093a1c0f81))
|
||||
* **editor:** Update design system Avatar component to show initials also when only firstName or lastName is given ([#10308](https://github.com/n8n-io/n8n/issues/10308)) ([46bbf09](https://github.com/n8n-io/n8n/commit/46bbf09beacad12472d91786b91d845fe2afb26d))
|
||||
* **editor:** Update tags filter/editor to not show non existing tag as a selectable option ([#10297](https://github.com/n8n-io/n8n/issues/10297)) ([557a76e](https://github.com/n8n-io/n8n/commit/557a76ec2326de72fb7a8b46fc4353f8fd9b591d))
|
||||
* **Invoice Ninja Node:** Fix payment types ([#10196](https://github.com/n8n-io/n8n/issues/10196)) ([c5acbb7](https://github.com/n8n-io/n8n/commit/c5acbb7ec0d24ec9b30c221fa3b2fb615fb9ec7f))
|
||||
* Loop node no input data shown ([#10224](https://github.com/n8n-io/n8n/issues/10224)) ([c8ee852](https://github.com/n8n-io/n8n/commit/c8ee852159207be0cfe2c3e0ee8e7b29d838aa35))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Allow filtering executions and users by project in Public API ([#10250](https://github.com/n8n-io/n8n/issues/10250)) ([7056e50](https://github.com/n8n-io/n8n/commit/7056e50b006bda665f64ce6234c5c1967891c415))
|
||||
* **core:** Allow transferring credentials in Public API ([#10259](https://github.com/n8n-io/n8n/issues/10259)) ([07d7b24](https://github.com/n8n-io/n8n/commit/07d7b247f02a9d7185beca7817deb779a3d665dd))
|
||||
* **core:** Show sub-node error on the logs pane. Open logs pane on sub-node error ([#10248](https://github.com/n8n-io/n8n/issues/10248)) ([57d1c9a](https://github.com/n8n-io/n8n/commit/57d1c9a99e97308f2f1b8ae05ac3861a835e8e5a))
|
||||
* **core:** Support community packages in scaling-mode ([#10228](https://github.com/n8n-io/n8n/issues/10228)) ([88086a4](https://github.com/n8n-io/n8n/commit/88086a41ff5b804b35aa9d9503dc2d48836fe4ec))
|
||||
* **core:** Support create, delete, edit role for users in Public API ([#10279](https://github.com/n8n-io/n8n/issues/10279)) ([84efbd9](https://github.com/n8n-io/n8n/commit/84efbd9b9c51f536b21a4f969ab607d277bef692))
|
||||
* **core:** Support create, read, update, delete projects in Public API ([#10269](https://github.com/n8n-io/n8n/issues/10269)) ([489ce10](https://github.com/n8n-io/n8n/commit/489ce100634c3af678fb300e9a39d273042542e6))
|
||||
* **editor:** Auto-add LLM chain for new LLM nodes on empty canvas ([#10245](https://github.com/n8n-io/n8n/issues/10245)) ([06419d9](https://github.com/n8n-io/n8n/commit/06419d9483ae916e79aace6d8c17e265b419b15d))
|
||||
* **Elasticsearch Node:** Add bulk operations for Elasticsearch ([#9940](https://github.com/n8n-io/n8n/issues/9940)) ([bf8f848](https://github.com/n8n-io/n8n/commit/bf8f848645dfd31527713a55bd1fc93865327017))
|
||||
* **Lemlist Trigger Node:** Update Trigger events ([#10311](https://github.com/n8n-io/n8n/issues/10311)) ([15f10ec](https://github.com/n8n-io/n8n/commit/15f10ec325cb5eda0f952bed3a5f171dd91bc639))
|
||||
* **MongoDB Node:** Add projection to query options on Find ([#9972](https://github.com/n8n-io/n8n/issues/9972)) ([0a84e0d](https://github.com/n8n-io/n8n/commit/0a84e0d8b047669f5cf023c21383d01c929c5b4f))
|
||||
* **Postgres Chat Memory, Redis Chat Memory, Xata:** Add support for context window length ([#10203](https://github.com/n8n-io/n8n/issues/10203)) ([e3edeaa](https://github.com/n8n-io/n8n/commit/e3edeaa03526f041d15d1099ea91869e38a0decc))
|
||||
* **Stripe Trigger Node:** Add Stripe webhook descriptions based on the workflow ID and name ([#9956](https://github.com/n8n-io/n8n/issues/9956)) ([3433465](https://github.com/n8n-io/n8n/commit/34334651e0e6874736a437a894176bed4590e5a7))
|
||||
* **Webflow Node:** Update to use the v2 API ([#9996](https://github.com/n8n-io/n8n/issues/9996)) ([6d8323f](https://github.com/n8n-io/n8n/commit/6d8323fadea8af04483eb1a873df0cf3ccc2a891))
|
||||
|
||||
|
||||
|
||||
# [1.53.0](https://github.com/n8n-io/n8n/compare/n8n@1.52.0...n8n@1.53.0) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Better error message when calling data transformation functions on a null value ([#10210](https://github.com/n8n-io/n8n/issues/10210)) ([1718125](https://github.com/n8n-io/n8n/commit/1718125c6d8589cf24dc8d34f6808dd6f1802691))
|
||||
* **core:** Fix missing successful items on continueErrorOutput with multiple outputs ([#10218](https://github.com/n8n-io/n8n/issues/10218)) ([1a7713e](https://github.com/n8n-io/n8n/commit/1a7713ef263680da43f08b6c8a15aee7a0341493))
|
||||
* **core:** Flush instance stopped event immediately ([#10238](https://github.com/n8n-io/n8n/issues/10238)) ([d6770b5](https://github.com/n8n-io/n8n/commit/d6770b5fcaec6438d677b918aaeb1669ad7424c2))
|
||||
* **core:** Restore log event `n8n.workflow.failed` ([#10253](https://github.com/n8n-io/n8n/issues/10253)) ([3e96b29](https://github.com/n8n-io/n8n/commit/3e96b293329525c9d4b2fcef87b3803e458c8e7f))
|
||||
* **core:** Upgrade @n8n/vm2 to address CVE‑2023‑37466 ([#10265](https://github.com/n8n-io/n8n/issues/10265)) ([2a09a03](https://github.com/n8n-io/n8n/commit/2a09a036d2e916acff7ee50904f1d011a93758e1))
|
||||
* **editor:** Defer `User saved credentials` telemetry event for OAuth credentials ([#10215](https://github.com/n8n-io/n8n/issues/10215)) ([40a5226](https://github.com/n8n-io/n8n/commit/40a5226e24448a4428143e69d80ebc78238365a1))
|
||||
* **editor:** Fix custom API call notice ([#10227](https://github.com/n8n-io/n8n/issues/10227)) ([5b47c8b](https://github.com/n8n-io/n8n/commit/5b47c8b57b25528cd2d6f97bc6d98707d47f35bc))
|
||||
* **editor:** Fix issue with existing credential not opening in HTTP agent tool ([#10167](https://github.com/n8n-io/n8n/issues/10167)) ([906b4c3](https://github.com/n8n-io/n8n/commit/906b4c3c7b2919111cf23eaa12b3c4d507969179))
|
||||
* **editor:** Fix parameter input glitch when there was an error loading remote options ([#10209](https://github.com/n8n-io/n8n/issues/10209)) ([c0e3743](https://github.com/n8n-io/n8n/commit/c0e37439a87105a0e66c8ebced42c06dab30dc5e))
|
||||
* **editor:** Fix workflow execution list scrolling after filter change ([#10226](https://github.com/n8n-io/n8n/issues/10226)) ([7e64358](https://github.com/n8n-io/n8n/commit/7e643589c67adc0218216ec4b89a95f0edfedbee))
|
||||
* **Google BigQuery Node:** Send timeoutMs in query, pagination support ([#10205](https://github.com/n8n-io/n8n/issues/10205)) ([f5722e8](https://github.com/n8n-io/n8n/commit/f5722e8823ccd2bc2b5f43ba3c849797d5690a93))
|
||||
* **Google Sheets Node:** Add column names row if sheet is empty ([#10200](https://github.com/n8n-io/n8n/issues/10200)) ([82eba9f](https://github.com/n8n-io/n8n/commit/82eba9fc5ff49b8e2a9db93c10b253fb67a8c644))
|
||||
* **Google Sheets Node:** Do not insert row_number as a new column, do not checkForSchemaChanges in update operation ([#10201](https://github.com/n8n-io/n8n/issues/10201)) ([5136d10](https://github.com/n8n-io/n8n/commit/5136d10ca3492f92af67d4a1d4abc774419580cc))
|
||||
* **Google Sheets Node:** Fix Google Sheet URL regex ([#10195](https://github.com/n8n-io/n8n/issues/10195)) ([e6fd996](https://github.com/n8n-io/n8n/commit/e6fd996973d4f40facf0ebf1eea3cc26acd0603d))
|
||||
* **HTTP Request Node:** Resolve max pages expression ([#10192](https://github.com/n8n-io/n8n/issues/10192)) ([bfc8e1b](https://github.com/n8n-io/n8n/commit/bfc8e1b56f7714e1f52aae747d58d686b86e60f0))
|
||||
* **LinkedIn Node:** Fix issue with some characters cutting off posts early ([#10185](https://github.com/n8n-io/n8n/issues/10185)) ([361b5e7](https://github.com/n8n-io/n8n/commit/361b5e7c37ba49b68dcf5b8122621aad4d8d96e0))
|
||||
* **Postgres Node:** Expressions in query parameters for Postgres executeQuery operation ([#10217](https://github.com/n8n-io/n8n/issues/10217)) ([519fc4d](https://github.com/n8n-io/n8n/commit/519fc4d75325a80b84cc4dcacf52d6f4c02e3a44))
|
||||
* **Postgres Node:** Option to treat query parameters enclosed in single quotas as text ([#10214](https://github.com/n8n-io/n8n/issues/10214)) ([00ec253](https://github.com/n8n-io/n8n/commit/00ec2533374d3def465efee718592fc4001d5602))
|
||||
* **Read/Write Files from Disk Node:** Notice update in file selector, replace backslashes with forward slashes if windows path ([#10186](https://github.com/n8n-io/n8n/issues/10186)) ([3eac673](https://github.com/n8n-io/n8n/commit/3eac673b17986c5c74bd2adb5ad589ba0ca55319))
|
||||
* **Text Classifier Node:** Use proper documentation URL and respect continueOnFail ([#10216](https://github.com/n8n-io/n8n/issues/10216)) ([452f52c](https://github.com/n8n-io/n8n/commit/452f52c124017e002e86c547ba42b1633b14beed))
|
||||
* **Trello Node:** Use body for POST requests ([#10189](https://github.com/n8n-io/n8n/issues/10189)) ([7775d50](https://github.com/n8n-io/n8n/commit/7775d5059b7f69d9af22e7ad7d12c6cf9092a4e5))
|
||||
* **Wait Node:** Authentication fix ([#10236](https://github.com/n8n-io/n8n/issues/10236)) ([f87854f](https://github.com/n8n-io/n8n/commit/f87854f8db360b7b870583753fcfb4af95adab8c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Calendly Trigger Node:** Add OAuth Credentials Support ([#10251](https://github.com/n8n-io/n8n/issues/10251)) ([326c983](https://github.com/n8n-io/n8n/commit/326c983915a2c382e32398358e7dcadd022c0b77))
|
||||
* **core:** Allow filtering workflows by project and transferring workflows in Public API ([#10231](https://github.com/n8n-io/n8n/issues/10231)) ([d719899](https://github.com/n8n-io/n8n/commit/d719899223907b20a17883a35e4ef637a3453532))
|
||||
* **editor:** Show new executions as `Queued` in the UI, until they actually start ([#10204](https://github.com/n8n-io/n8n/issues/10204)) ([44728d7](https://github.com/n8n-io/n8n/commit/44728d72423f5549dda09589f4a618ebd80899cb))
|
||||
* **HTTP Request Node:** Add option to disable lowercase headers ([#10154](https://github.com/n8n-io/n8n/issues/10154)) ([5aba69b](https://github.com/n8n-io/n8n/commit/5aba69bcf4d232d9860f3cd9fe57cb8839a2f96f))
|
||||
* **Information Extractor Node:** Add new simplified AI-node for information extraction ([#10149](https://github.com/n8n-io/n8n/issues/10149)) ([3d235b0](https://github.com/n8n-io/n8n/commit/3d235b0b2df756df35ac60e3dcd87ad183a07167))
|
||||
* Introduce Google Cloud Platform as external secrets provider ([#10146](https://github.com/n8n-io/n8n/issues/10146)) ([3ccb9df](https://github.com/n8n-io/n8n/commit/3ccb9df2f902e46f8cbb9c46c0727f29d752a773))
|
||||
* **n8n Form Trigger Node:** Improvements ([#10092](https://github.com/n8n-io/n8n/issues/10092)) ([711b667](https://github.com/n8n-io/n8n/commit/711b667ebefe55740e5eb39f1f0f24ceee10e7b0))
|
||||
* Recovery option for jsonParse helper ([#10182](https://github.com/n8n-io/n8n/issues/10182)) ([d165b33](https://github.com/n8n-io/n8n/commit/d165b33ceac4d24d0fc290bffe63b5f551204e38))
|
||||
* **Sentiment Analysis Node:** Implement Sentiment Analysis node ([#10184](https://github.com/n8n-io/n8n/issues/10184)) ([8ef0a0c](https://github.com/n8n-io/n8n/commit/8ef0a0c58ac2a84aad649ccbe72aa907d005cc44))
|
||||
* **Shopify Node:** Update Shopify API version ([#10155](https://github.com/n8n-io/n8n/issues/10155)) ([e2ee915](https://github.com/n8n-io/n8n/commit/e2ee91569a382bfbf787cf45204c72c821a860a0))
|
||||
* Support create, read, delete variables in Public API ([#10241](https://github.com/n8n-io/n8n/issues/10241)) ([af695eb](https://github.com/n8n-io/n8n/commit/af695ebf934526d926ea87fe87df61aa73d70979))
|
||||
|
||||
|
||||
|
||||
# [1.52.0](https://github.com/n8n-io/n8n/compare/n8n@1.51.0...n8n@1.52.0) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Fix handling of common events for relays ([#10135](https://github.com/n8n-io/n8n/issues/10135)) ([d2a3a4a](https://github.com/n8n-io/n8n/commit/d2a3a4a080cdcc04f50fa33fd81d361efce3f709))
|
||||
* **core:** Fix SSH Tunnels when using private key ([#10148](https://github.com/n8n-io/n8n/issues/10148)) ([a96db34](https://github.com/n8n-io/n8n/commit/a96db344e54658787426d967dfa299c7a6dd14e7))
|
||||
* **core:** Metadata inserts using existing IDs and failing with postgres ([#10108](https://github.com/n8n-io/n8n/issues/10108)) ([4547a49](https://github.com/n8n-io/n8n/commit/4547a49db15a20f5f147e859b6c2c01f60f9565c))
|
||||
* **core:** Respect prefix for all Prometheus metrics ([#10130](https://github.com/n8n-io/n8n/issues/10130)) ([b1816db](https://github.com/n8n-io/n8n/commit/b1816db449ed451443f353b69166b7ca700ba51e))
|
||||
* **core:** Support branches containing slashes in source control ([#10109](https://github.com/n8n-io/n8n/issues/10109)) ([03a833d](https://github.com/n8n-io/n8n/commit/03a833db51a25dda6cf0d8494f06c6704f6f3c7f))
|
||||
* **core:** Support execution recovery when saving execution progress ([#10104](https://github.com/n8n-io/n8n/issues/10104)) ([d887c82](https://github.com/n8n-io/n8n/commit/d887c82d808a79babc726fc789cc014194ae2ac6))
|
||||
* **editor:** Allow `$secrets` to resolve on credentials ([#10093](https://github.com/n8n-io/n8n/issues/10093)) ([bf57f38](https://github.com/n8n-io/n8n/commit/bf57f38d1c417ba8b20144934c8e97a75c1f51cc))
|
||||
* **editor:** Fix saving and connecting on LDAP setup form ([#10163](https://github.com/n8n-io/n8n/issues/10163)) ([30784fb](https://github.com/n8n-io/n8n/commit/30784fb76cec790a782fae40973a956a8d81c0b2))
|
||||
* **editor:** Fix updating/uninstalling community nodes ([#10138](https://github.com/n8n-io/n8n/issues/10138)) ([de015ff](https://github.com/n8n-io/n8n/commit/de015ff2978a5ee3345449626025c6d0793b6f5a))
|
||||
* **editor:** Remove "move" action from workflow and credential on community plan ([#10057](https://github.com/n8n-io/n8n/issues/10057)) ([5a9a271](https://github.com/n8n-io/n8n/commit/5a9a2713b499cc7dcddb500a54e24bbf7145b504))
|
||||
* **editor:** UX Improvements to RBAC feature set ([#9683](https://github.com/n8n-io/n8n/issues/9683)) ([028a8a2](https://github.com/n8n-io/n8n/commit/028a8a2c754e4f6d6a5f0918a656eb4554eb869f))
|
||||
* **HelpScout Node:** Fix issue with thread types not working correctly ([#10084](https://github.com/n8n-io/n8n/issues/10084)) ([68d3beb](https://github.com/n8n-io/n8n/commit/68d3bebfeebea9054bbbaebac31c2e3fa34336bb))
|
||||
* **MQTT Node:** Node hangs forever on failed connection ([#10048](https://github.com/n8n-io/n8n/issues/10048)) ([76c2906](https://github.com/n8n-io/n8n/commit/76c290655de7d4e626725a05fd991a0858cca0d7))
|
||||
* **n8n Form Trigger Node:** Execution from canvas ([#10132](https://github.com/n8n-io/n8n/issues/10132)) ([b07c5e2](https://github.com/n8n-io/n8n/commit/b07c5e201165165c4e91ddd19b6fa79703ba2a9c))
|
||||
* **Notion Node:** Fix issue preventing some database page urls from working ([#10070](https://github.com/n8n-io/n8n/issues/10070)) ([7848c19](https://github.com/n8n-io/n8n/commit/7848c19f543d5f5f62b89cc5644639c6afdb8fa6))
|
||||
* **RabbitMQ Node:** Fix issue with arguments not being sent ([#9397](https://github.com/n8n-io/n8n/issues/9397)) ([1c666e6](https://github.com/n8n-io/n8n/commit/1c666e6e7c2be2e2d0dcc528870fddfa8b02318b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **editor:** Split Tools and Models into sub-sections ([#10159](https://github.com/n8n-io/n8n/issues/10159)) ([3846eb9](https://github.com/n8n-io/n8n/commit/3846eb967afd77dba6f037e8185ed94494454d5a))
|
||||
* Introduce Azure Key Vault as external secrets provider ([#10054](https://github.com/n8n-io/n8n/issues/10054)) ([1b6c2d3](https://github.com/n8n-io/n8n/commit/1b6c2d3a37a78ed07ada93be2a57e4b7f7149e58))
|
||||
* **Pinecone Vector Store Node, Supabase Vector Store Node:** Add update operation to vector store nodes ([#10060](https://github.com/n8n-io/n8n/issues/10060)) ([7e1eeb4](https://github.com/n8n-io/n8n/commit/7e1eeb4c31d3f25ec31baa7390b11a7e3280ce01))
|
||||
* **Send Email Node:** Smtp credential improvements ([#10147](https://github.com/n8n-io/n8n/issues/10147)) ([dc13ceb](https://github.com/n8n-io/n8n/commit/dc13ceb41649eab42ef073247f3b52c040826e98))
|
||||
|
||||
|
||||
|
||||
# [1.51.0](https://github.com/n8n-io/n8n/compare/n8n@1.50.0...n8n@1.51.0) (2024-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AMQP Sender Node:** Node hangs forever on disconnect ([#10026](https://github.com/n8n-io/n8n/issues/10026)) ([27410ab](https://github.com/n8n-io/n8n/commit/27410ab2af87573045f38e14e7e20bedd3b0365d))
|
||||
* **AMQP Trigger Node:** Manual execution updated error reduced wait time ([#10035](https://github.com/n8n-io/n8n/issues/10035)) ([f78f4ea](https://github.com/n8n-io/n8n/commit/f78f4ea3492560bc7056023fd0276990f3ac9b00))
|
||||
* **AWS Comprehend Node:** Add paired item support ([#10015](https://github.com/n8n-io/n8n/issues/10015)) ([470d496](https://github.com/n8n-io/n8n/commit/470d4966c67a3e4155d59e6fadab467b73134ec4))
|
||||
* **core:** Ensure executions cannot resume if already running ([#10014](https://github.com/n8n-io/n8n/issues/10014)) ([d651be4](https://github.com/n8n-io/n8n/commit/d651be4e01a869a6f7d70e691e0f5e244f59490e))
|
||||
* **core:** Redact `csrfSecret` when returning oauth credentials to the frontend ([#10075](https://github.com/n8n-io/n8n/issues/10075)) ([48f047e](https://github.com/n8n-io/n8n/commit/48f047ee2ecbfbd364151816df5fc21e09ca72a6))
|
||||
* **core:** Stopping an execution should reject any response promises ([#9992](https://github.com/n8n-io/n8n/issues/9992)) ([36b314d](https://github.com/n8n-io/n8n/commit/36b314d0311ef84f275efbc20997c6a77db81b31))
|
||||
* **editor:** Ensure all static assets are accessible from the server ([#10062](https://github.com/n8n-io/n8n/issues/10062)) ([3bde845](https://github.com/n8n-io/n8n/commit/3bde8453efa9a4d14404c63bdc061c87843d49d2))
|
||||
* **editor:** Handle disabled nodes in schema view ([#10052](https://github.com/n8n-io/n8n/issues/10052)) ([ab5688c](https://github.com/n8n-io/n8n/commit/ab5688c582c05afd7d3e0967eda0f5dc73d6d3ed))
|
||||
* **editor:** Make schema view use the correct output ([#10016](https://github.com/n8n-io/n8n/issues/10016)) ([c29664d](https://github.com/n8n-io/n8n/commit/c29664d68851ec33e4d810fa24aba72bb6cecc86))
|
||||
* **editor:** Provide autocomplete for nodes, even when intermediate node has not run ([#10036](https://github.com/n8n-io/n8n/issues/10036)) ([46d6edc](https://github.com/n8n-io/n8n/commit/46d6edc2a4edd49ae58c0c60977809554e07f4ee))
|
||||
* **editor:** Remove push event listeners when migrating away from the canvas ([#10063](https://github.com/n8n-io/n8n/issues/10063)) ([0d12f0a](https://github.com/n8n-io/n8n/commit/0d12f0a6b36aaaae5e1f9fab8ad73feeba9ec5ed))
|
||||
* **editor:** Use selected input item for autocomplete ([#10019](https://github.com/n8n-io/n8n/issues/10019)) ([1d2b403](https://github.com/n8n-io/n8n/commit/1d2b403644278fa6158272edc4295d4565554e37))
|
||||
* **Email Trigger (IMAP) Node:** Reconnect not working correctly ([#10064](https://github.com/n8n-io/n8n/issues/10064)) ([68d5d7e](https://github.com/n8n-io/n8n/commit/68d5d7e2e90ede5d021a12304dd665247dde5243))
|
||||
* Filter component - array contains comparison not correct when ignore case option set to true ([#10012](https://github.com/n8n-io/n8n/issues/10012)) ([4a3b97c](https://github.com/n8n-io/n8n/commit/4a3b97cede531adbf81274c1ec2ce4ee400cb48e))
|
||||
* **GitHub Node:** File Create operation prevent duplicated base64 encoding ([#10040](https://github.com/n8n-io/n8n/issues/10040)) ([9bcc926](https://github.com/n8n-io/n8n/commit/9bcc926a91d7afab0c2ef6eb57e818ef79e3a8f7))
|
||||
* **HTTP Request Node:** Respect the original encoding of the incoming response ([#9869](https://github.com/n8n-io/n8n/issues/9869)) ([2d19aef](https://github.com/n8n-io/n8n/commit/2d19aef54083d97e94e50a1ee58e8525bbf28548))
|
||||
* HTTP Request tool - allow hyphens in placeholders ([#10037](https://github.com/n8n-io/n8n/issues/10037)) ([8cd9370](https://github.com/n8n-io/n8n/commit/8cd93704bee116eceb0e3bd5fa849c4b314454ec))
|
||||
* HTTP Request tool - do not error on missing headers ([#10044](https://github.com/n8n-io/n8n/issues/10044)) ([04b62e0](https://github.com/n8n-io/n8n/commit/04b62e0398eafd923d5f27a3e1c71b925ddb8817))
|
||||
* **HubSpot Node:** Migrate from v2 owners api ([#10013](https://github.com/n8n-io/n8n/issues/10013)) ([56dd491](https://github.com/n8n-io/n8n/commit/56dd491bcaeab1d11d7874f190eaf20d2e315ca1))
|
||||
* Number input defaults to 0 not allowing to have arbitrary precision ([#10021](https://github.com/n8n-io/n8n/issues/10021)) ([e4e66ab](https://github.com/n8n-io/n8n/commit/e4e66ab7da5651fede8b3065419ffb393a2fd16d))
|
||||
* **OpenAI Chat Model Node:** Respect baseURL override for /models ([#10076](https://github.com/n8n-io/n8n/issues/10076)) ([e5dda57](https://github.com/n8n-io/n8n/commit/e5dda5731dfbb50f5aaf2b152f9c5bc89b1d80a6))
|
||||
* **Telegram Trigger Node:** Fix issue with videos not being downloaded ([#10007](https://github.com/n8n-io/n8n/issues/10007)) ([e84ab35](https://github.com/n8n-io/n8n/commit/e84ab35c4ab0ec47bdbd4343e58c62bbb70f3ec9))
|
||||
* **Webhook Node:** Binary property option name and description update ([#10043](https://github.com/n8n-io/n8n/issues/10043)) ([9302e33](https://github.com/n8n-io/n8n/commit/9302e33d558564bb5ba172eaeb8c300693b87286))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Asana Node:** Add support for project privacy settings ([#10027](https://github.com/n8n-io/n8n/issues/10027)) ([429481c](https://github.com/n8n-io/n8n/commit/429481c5c4b7f448739a561596873038185ba467))
|
||||
* Better error when calling expression function on input that is undefined or null ([#10009](https://github.com/n8n-io/n8n/issues/10009)) ([519e57b](https://github.com/n8n-io/n8n/commit/519e57bda5115149357fb2b1c2270e481ea09e38))
|
||||
* **editor:** Make expression autocomplete search case-insensitive ([#10017](https://github.com/n8n-io/n8n/issues/10017)) ([cde6fe9](https://github.com/n8n-io/n8n/commit/cde6fe90e5c8a9c5983e27f0d82599425fba915b))
|
||||
* **editor:** Tweak node creator search logic for AI sub-nodes ([#10025](https://github.com/n8n-io/n8n/issues/10025)) ([7db1656](https://github.com/n8n-io/n8n/commit/7db16561dc890849e2d5742bb73f9d5b8e79e37d))
|
||||
* **Google Vertex Chat Model Node:** Add support for Google Vertex AI Chat models ([#9970](https://github.com/n8n-io/n8n/issues/9970)) ([071130a](https://github.com/n8n-io/n8n/commit/071130a2dc0b450eb6ce6d39fe28cfeefd05633c))
|
||||
* **Postgres Chat Memory Node:** Implement Postgres Chat Memory node ([#10071](https://github.com/n8n-io/n8n/issues/10071)) ([9cbbb63](https://github.com/n8n-io/n8n/commit/9cbbb6335df0d36f66f22c18041d12f14dc59b32))
|
||||
* **Text Classifier Node:** Add Text Classifier Node ([#9997](https://github.com/n8n-io/n8n/issues/9997)) ([28ca7d6](https://github.com/n8n-io/n8n/commit/28ca7d6a2dd818c8795acda6ddf7329b8621d9de))
|
||||
|
||||
|
||||
|
||||
# [1.50.0](https://github.com/n8n-io/n8n/compare/n8n@1.49.0...n8n@1.50.0) (2024-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Aborting manual trigger tests should call `closeFunction` ([#9980](https://github.com/n8n-io/n8n/issues/9980)) ([6107798](https://github.com/n8n-io/n8n/commit/61077985163037ed3c6a8e9e7476cd6c525ff5f2))
|
||||
* **core:** Allow owner and admin to edit nodes with credentials that haven't been shared with them explicitly ([#9922](https://github.com/n8n-io/n8n/issues/9922)) ([0f49598](https://github.com/n8n-io/n8n/commit/0f495986f89b60ec9bb86801f9779ee9aa87ccfb))
|
||||
* **core:** Clear active execution on cancellation in scaling mode ([#9979](https://github.com/n8n-io/n8n/issues/9979)) ([7e972c7](https://github.com/n8n-io/n8n/commit/7e972c78afaf950effec17d8eee16cbf86101d03))
|
||||
* **core:** Disconnect Redis after pausing queue during worker shutdown ([#9928](https://github.com/n8n-io/n8n/issues/9928)) ([c82579b](https://github.com/n8n-io/n8n/commit/c82579bf760cc4b5a2670b14e4e48fc37e2e2263))
|
||||
* **core:** Don't execute 'workflowExecuteBefore' hook on execution continuations ([#9905](https://github.com/n8n-io/n8n/issues/9905)) ([adb8315](https://github.com/n8n-io/n8n/commit/adb83155ca9478a548e6fe926735d5872de10fea))
|
||||
* **core:** Prevent multiple values in the execution metadata for the same key and executionId ([#9953](https://github.com/n8n-io/n8n/issues/9953)) ([2e6b03b](https://github.com/n8n-io/n8n/commit/2e6b03b2cb471aefa8104b7b80cf12e64f16e4fb))
|
||||
* **Google Sheets Node:** Append fails if cells have some default values added by data validation rules ([#9950](https://github.com/n8n-io/n8n/issues/9950)) ([d1821eb](https://github.com/n8n-io/n8n/commit/d1821eba9221eb243b62ad561193102b24dd05a5))
|
||||
* **Invoice Ninja Node:** Fix assigning an invoice to a payment ([#9590](https://github.com/n8n-io/n8n/issues/9590)) ([7a3c127](https://github.com/n8n-io/n8n/commit/7a3c127b2cbea01f9a21c8d517d1dc919bc8121f))
|
||||
* **Invoice Ninja Node:** Fix emailing and marking invoice as paid / sent ([#9589](https://github.com/n8n-io/n8n/issues/9589)) ([908ddd8](https://github.com/n8n-io/n8n/commit/908ddd8a24e8a858d9c1eddf2f727234e66a62f7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Chat Trigger Node:** Add support for file uploads & harmonize public and development chat ([#9802](https://github.com/n8n-io/n8n/issues/9802)) ([df78315](https://github.com/n8n-io/n8n/commit/df783151b86e2db3e325d3b9d85f4abb71d3d246))
|
||||
* **Google Cloud Firestore Node:** Add support for service account and document creation with id ([#9713](https://github.com/n8n-io/n8n/issues/9713)) ([cb1bbf5](https://github.com/n8n-io/n8n/commit/cb1bbf5fd395ec4855ac21d851b180c8526b698a))
|
||||
* **Orbit Node:** Deprecate Orbit nicely ([#9962](https://github.com/n8n-io/n8n/issues/9962)) ([9577d9c](https://github.com/n8n-io/n8n/commit/9577d9c847b56d9907d2bbe9ec85127bb8f67cfa))
|
||||
* Qdrant Vector Store search filter ([#9900](https://github.com/n8n-io/n8n/issues/9900)) ([fbe4bca](https://github.com/n8n-io/n8n/commit/fbe4bca634e8e03c9455843e1a1f89706d1557d2))
|
||||
* **Splunk Node:** Overhaul ([#9813](https://github.com/n8n-io/n8n/issues/9813)) ([e5c3247](https://github.com/n8n-io/n8n/commit/e5c324753fb41752f9722d61c5d336d6e5c67cca))
|
||||
* **Telegram Node:** Add support to Keyboard Button Mini Apps ([#9511](https://github.com/n8n-io/n8n/issues/9511)) ([3a17943](https://github.com/n8n-io/n8n/commit/3a179439c7586189b8264131fd16da9d14f074b6))
|
||||
|
||||
|
||||
|
||||
# [1.49.0](https://github.com/n8n-io/n8n/compare/n8n@1.48.0...n8n@1.49.0) (2024-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Add a WebCrypto Polyfill for older versions of Node.js 18 ([#9894](https://github.com/n8n-io/n8n/issues/9894)) ([59c8bf1](https://github.com/n8n-io/n8n/commit/59c8bf1c44057b3f798645a22ad16362401ebeed))
|
||||
* **core:** Don't allow using credentials that are not part of the same project ([#9916](https://github.com/n8n-io/n8n/issues/9916)) ([ab2a548](https://github.com/n8n-io/n8n/commit/ab2a5488560a814fc72c0c5cd71e5f62f05cd235))
|
||||
* **core:** Filter out certain executions from crash recovery ([#9904](https://github.com/n8n-io/n8n/issues/9904)) ([7044d1c](https://github.com/n8n-io/n8n/commit/7044d1ca2841b6d87ae929072bb94dda82909795))
|
||||
* **core:** Fix AddActivatedAtUserSetting migration on MariaDB ([#9910](https://github.com/n8n-io/n8n/issues/9910)) ([db29e84](https://github.com/n8n-io/n8n/commit/db29e84666b814fd4710dc3ade6e53304216fad5))
|
||||
* **core:** Fix execution cancellation in scaling mode ([#9841](https://github.com/n8n-io/n8n/issues/9841)) ([e613de2](https://github.com/n8n-io/n8n/commit/e613de28ca2db23746b586e0a0b33f1c1ee1abe5))
|
||||
* **core:** Fix worker logs relay ([#9919](https://github.com/n8n-io/n8n/issues/9919)) ([7c53433](https://github.com/n8n-io/n8n/commit/7c5343319144ce3524b14018eef77eace221b608))
|
||||
* **core:** Throw on adding execution without execution data ([#9903](https://github.com/n8n-io/n8n/issues/9903)) ([abb7458](https://github.com/n8n-io/n8n/commit/abb74587db88a56453b269826885df0d01766290))
|
||||
* **editor:** Don't try to load credentials on the demo route ([#9926](https://github.com/n8n-io/n8n/issues/9926)) ([b80df2a](https://github.com/n8n-io/n8n/commit/b80df2a47ebe4450862e200c9cf47f6e94012c91))
|
||||
* **editor:** Enable expression preview in SQL node when looking at executions ([#9733](https://github.com/n8n-io/n8n/issues/9733)) ([d9747d5](https://github.com/n8n-io/n8n/commit/d9747d5e9b42d7f379f6f4219b960893b7b153b3))
|
||||
* **editor:** Fix frontend project roles ([#9901](https://github.com/n8n-io/n8n/issues/9901)) ([f229577](https://github.com/n8n-io/n8n/commit/f2295772094ff936e210f52ebcbc938915d1c129))
|
||||
* **editor:** Fix new node credential creation via Resource Locator Component ([#9896](https://github.com/n8n-io/n8n/issues/9896)) ([55cbc90](https://github.com/n8n-io/n8n/commit/55cbc900a48c579b712dddfa74e133e1d9c11799))
|
||||
* **editor:** Fix performance issues related to expressions and pinned data ([#9882](https://github.com/n8n-io/n8n/issues/9882)) ([13d83f2](https://github.com/n8n-io/n8n/commit/13d83f2037d659fccc8889dd994ddd984467d987))
|
||||
* **editor:** Improve text wrapping in schema view ([#9888](https://github.com/n8n-io/n8n/issues/9888)) ([dc1c5fc](https://github.com/n8n-io/n8n/commit/dc1c5fce8af732c438d2f1698ee08f18d2358a6c))
|
||||
* **Execute Workflow Node:** Continue on fail behaviour not correctly implemented ([#9890](https://github.com/n8n-io/n8n/issues/9890)) ([16b1a09](https://github.com/n8n-io/n8n/commit/16b1a094b19e5f803a460b99c6062a1175bec153))
|
||||
* **LinkedIn Node:** Fix issue with legacy credential no longer working ([#9912](https://github.com/n8n-io/n8n/issues/9912)) ([873b7e5](https://github.com/n8n-io/n8n/commit/873b7e59dcea276c9f792570805a6de8ad4607a3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add Zep Cloud Memory component ([#9657](https://github.com/n8n-io/n8n/issues/9657)) ([41c47a2](https://github.com/n8n-io/n8n/commit/41c47a28a9d4502287ca1bbbb4704f2763288a11))
|
||||
* **Copper Node:** Update credential to support HTTP Request node ([#9837](https://github.com/n8n-io/n8n/issues/9837)) ([e6ad5a7](https://github.com/n8n-io/n8n/commit/e6ad5a71935a5f82168bf300246ccb3535648b0b))
|
||||
* **editor:** Add docs sidebar to credential modal ([#9914](https://github.com/n8n-io/n8n/issues/9914)) ([b2f8ea7](https://github.com/n8n-io/n8n/commit/b2f8ea7918d7e10e91db0e04ef5b7ad40a5bdbb5))
|
||||
* **editor:** Remove Segment ([#9878](https://github.com/n8n-io/n8n/issues/9878)) ([10f7d4b](https://github.com/n8n-io/n8n/commit/10f7d4b5b92013407c9a4eb9edd619d385efe10f))
|
||||
* **Embeddings Cohere Node:** Add v3 Cohere models ([#9887](https://github.com/n8n-io/n8n/issues/9887)) ([403e19b](https://github.com/n8n-io/n8n/commit/403e19b3e316db81b62eb456b38e7325bf13529c))
|
||||
* **GitHub Node:** Add support for state reasons when editing an issue ([#9848](https://github.com/n8n-io/n8n/issues/9848)) ([61c20d1](https://github.com/n8n-io/n8n/commit/61c20d1ae3c65b04c767c5b704c4fc4efd356ccf))
|
||||
* Introduce debug info button ([#9895](https://github.com/n8n-io/n8n/issues/9895)) ([be9a247](https://github.com/n8n-io/n8n/commit/be9a247577ffc28559a23fea2db9b2c598dca036))
|
||||
* **Merge Node:** Overhaul, v3 ([#9528](https://github.com/n8n-io/n8n/issues/9528)) ([af69c80](https://github.com/n8n-io/n8n/commit/af69c80bf5a22f80979405041210dc77d2682c51))
|
||||
* **Vector Store Tool Node:** Add Vector Store Tool ([#9865](https://github.com/n8n-io/n8n/issues/9865)) ([df2bc84](https://github.com/n8n-io/n8n/commit/df2bc84d2b3830d31319c108f1b01256de95e774))
|
||||
* **Zammad Node:** Add reply_to and sender fields to article on ticket creation ([#9911](https://github.com/n8n-io/n8n/issues/9911)) ([957b2d6](https://github.com/n8n-io/n8n/commit/957b2d6108dccd9495291c4764816cc27e112e87))
|
||||
|
||||
|
||||
|
||||
# [1.48.0](https://github.com/n8n-io/n8n/compare/n8n@1.47.0...n8n@1.48.0) (2024-06-27)
|
||||
|
||||
|
||||
|
|
|
@ -95,8 +95,8 @@ development environment ready in minutes.
|
|||
## License
|
||||
|
||||
n8n is [fair-code](https://faircode.io) distributed under the
|
||||
[**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) and the
|
||||
[**n8n Enterprise License**](https://github.com/n8n-io/n8n/blob/master/LICENSE_EE.md).
|
||||
[**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) and the
|
||||
[**n8n Enterprise License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE_EE.md).
|
||||
|
||||
Proprietary licenses are available for enterprise customers. [Get in touch](mailto:license@n8n.io)
|
||||
|
||||
|
|
51
biome.jsonc
51
biome.jsonc
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"vcs": {
|
||||
"clientKind": "git",
|
||||
"enabled": true,
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
"**/.turbo",
|
||||
"**/coverage",
|
||||
"**/dist",
|
||||
"**/package.json",
|
||||
"**/pnpm-lock.yaml",
|
||||
"**/CHANGELOG.md"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"attributePosition": "auto",
|
||||
"ignore": [
|
||||
// Handled by prettier
|
||||
"**/*.vue"
|
||||
]
|
||||
},
|
||||
"organizeImports": { "enabled": false },
|
||||
"linter": {
|
||||
"enabled": false
|
||||
},
|
||||
"javascript": {
|
||||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
},
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all",
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "single",
|
||||
"attributePosition": "auto"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"$schema": "../node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"extends": ["../biome.jsonc"],
|
||||
"formatter": {
|
||||
"ignore": ["fixtures/**"]
|
||||
}
|
||||
}
|
|
@ -7,15 +7,15 @@ export function getManualChatModal() {
|
|||
}
|
||||
|
||||
export function getManualChatInput() {
|
||||
return getManualChatModal().get('.chat-inputs textarea');
|
||||
return cy.getByTestId('workflow-chat-input');
|
||||
}
|
||||
|
||||
export function getManualChatSendButton() {
|
||||
return getManualChatModal().get('.chat-input-send-button');
|
||||
return getManualChatModal().getByTestId('workflow-chat-send-button');
|
||||
}
|
||||
|
||||
export function getManualChatMessages() {
|
||||
return getManualChatModal().get('.chat-messages-list .chat-message');
|
||||
return getManualChatModal().get('.messages .message');
|
||||
}
|
||||
|
||||
export function getManualChatModalCloseButton() {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export function getSaveChangesModal() {
|
||||
return cy.get('.el-overlay').contains('Save changes before leaving?');
|
||||
}
|
|
@ -1,43 +1,41 @@
|
|||
import { CredentialsModal, WorkflowPage } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
||||
export const getHomeButton = () => cy.getByTestId('project-home-menu-item');
|
||||
export const getMenuItems = () => cy.getByTestId('project-menu-item');
|
||||
export const getAddProjectButton = () =>
|
||||
cy.getByTestId('add-project-menu-item').should('contain', 'Add project').should('be.visible');
|
||||
export const getAddProjectButton = () => cy.getByTestId('add-project-menu-item');
|
||||
export const getProjectTabs = () => cy.getByTestId('project-tabs').find('a');
|
||||
export const getProjectTabWorkflows = () => getProjectTabs().filter('a[href$="/workflows"]');
|
||||
export const getProjectTabCredentials = () => getProjectTabs().filter('a[href$="/credentials"]');
|
||||
export const getProjectTabSettings = () => getProjectTabs().filter('a[href$="/settings"]');
|
||||
export const getProjectSettingsNameInput = () =>
|
||||
cy.getByTestId('project-settings-name-input').find('input');
|
||||
export const getProjectSettingsNameInput = () => cy.getByTestId('project-settings-name-input');
|
||||
export const getProjectSettingsSaveButton = () => cy.getByTestId('project-settings-save-button');
|
||||
export const getProjectSettingsCancelButton = () =>
|
||||
cy.getByTestId('project-settings-cancel-button');
|
||||
export const getProjectSettingsDeleteButton = () =>
|
||||
cy.getByTestId('project-settings-delete-button');
|
||||
export const getProjectMembersSelect = () => cy.getByTestId('project-members-select');
|
||||
export const addProjectMember = (email: string, role?: string) => {
|
||||
export const addProjectMember = (email: string) => {
|
||||
getProjectMembersSelect().click();
|
||||
getProjectMembersSelect().get('.el-select-dropdown__item').contains(email.toLowerCase()).click();
|
||||
|
||||
if (role) {
|
||||
cy.getByTestId(`user-list-item-${email}`)
|
||||
.find('[data-test-id="projects-settings-user-role-select"]')
|
||||
.click();
|
||||
getVisibleSelect().find('li').contains(role).click();
|
||||
}
|
||||
};
|
||||
export const getProjectNameInput = () => cy.get('#projectName');
|
||||
export const getResourceMoveModal = () => cy.getByTestId('project-move-resource-modal');
|
||||
export const getResourceMoveConfirmModal = () =>
|
||||
cy.getByTestId('project-move-resource-confirm-modal');
|
||||
export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-modal-select');
|
||||
|
||||
export function createProject(name: string) {
|
||||
getAddProjectButton().click();
|
||||
getAddProjectButton().should('be.visible').click();
|
||||
|
||||
getProjectSettingsNameInput().should('be.visible').clear().type(name);
|
||||
getProjectNameInput()
|
||||
.should('be.visible')
|
||||
.should('be.focused')
|
||||
.should('have.value', 'My project')
|
||||
.clear()
|
||||
.type(name);
|
||||
getProjectSettingsSaveButton().click();
|
||||
}
|
||||
|
||||
|
@ -48,7 +46,7 @@ export function createWorkflow(fixtureKey: string, name: string) {
|
|||
workflowPage.actions.zoomToFit();
|
||||
}
|
||||
|
||||
export function createCredential(name: string, closeModal = true) {
|
||||
export function createCredential(name: string) {
|
||||
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||
|
@ -56,8 +54,13 @@ export function createCredential(name: string, closeModal = true) {
|
|||
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
|
||||
credentialsModal.actions.setName(name);
|
||||
credentialsModal.actions.save();
|
||||
|
||||
if (closeModal) {
|
||||
credentialsModal.actions.close();
|
||||
}
|
||||
credentialsModal.actions.close();
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
createProject: (name: string) => {
|
||||
getAddProjectButton().click();
|
||||
getProjectSettingsNameInput().type(name);
|
||||
getProjectSettingsSaveButton().click();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { getManualChatModal } from './modals/chat-modal';
|
||||
import { ROUTES } from '../constants';
|
||||
import { getManualChatModal } from './modals/chat-modal';
|
||||
|
||||
/**
|
||||
* Types
|
||||
|
|
|
@ -37,7 +37,6 @@ export const INSTANCE_MEMBERS = [
|
|||
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
||||
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking ‘Test workflow’';
|
||||
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
|
||||
export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received';
|
||||
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
|
||||
export const CODE_NODE_NAME = 'Code';
|
||||
export const SET_NODE_NAME = 'Set';
|
||||
|
@ -54,14 +53,11 @@ export const AGENT_NODE_NAME = 'AI Agent';
|
|||
export const BASIC_LLM_CHAIN_NODE_NAME = 'Basic LLM Chain';
|
||||
export const AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME = 'Window Buffer Memory';
|
||||
export const AI_TOOL_CALCULATOR_NODE_NAME = 'Calculator';
|
||||
export const AI_TOOL_CODE_NODE_NAME = 'Code Tool';
|
||||
export const AI_TOOL_CODE_NODE_NAME = 'Custom Code Tool';
|
||||
export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia';
|
||||
export const AI_TOOL_HTTP_NODE_NAME = 'HTTP Request Tool';
|
||||
export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model';
|
||||
export const AI_MEMORY_POSTGRES_NODE_NAME = 'Postgres Chat Memory';
|
||||
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';
|
||||
export const WEBHOOK_NODE_NAME = 'Webhook';
|
||||
export const EXECUTE_WORKFLOW_NODE_NAME = 'Execute Workflow';
|
||||
|
||||
export const META_KEY = Cypress.platform === 'darwin' ? 'meta' : 'ctrl';
|
||||
|
||||
|
|
|
@ -25,4 +25,9 @@ module.exports = defineConfig({
|
|||
screenshotsFolder: 'screenshots',
|
||||
videosFolder: 'videos',
|
||||
},
|
||||
env: {
|
||||
MAX_PINNED_DATA_SIZE: process.env.VUE_APP_MAX_PINNED_DATA_SIZE
|
||||
? parseInt(process.env.VUE_APP_MAX_PINNED_DATA_SIZE, 10)
|
||||
: 16 * 1024,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
||||
|
||||
const WorkflowsPage = new WorkflowsPageClass();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SettingsLogStreamingPage } from '../pages';
|
||||
import { getVisibleDropdown } from '../utils';
|
||||
import { getVisibleModalOverlay } from '../utils/modal';
|
||||
import { getVisibleDropdown } from '../utils';
|
||||
|
||||
const settingsLogStreamingPage = new SettingsLogStreamingPage();
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import {
|
|||
SET_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { MessageBox as MessageBoxClass } from '../pages/modals/message-box';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
// Suite-specific constants
|
||||
const CODE_NODE_NEW_NAME = 'Something else';
|
||||
|
@ -20,6 +20,24 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('should undo/redo adding nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 0);
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
||||
});
|
||||
|
||||
it('should undo/redo adding connected nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should undo/redo adding node in the middle', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -206,6 +224,21 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should undo/redo renaming node using NDV', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
ndv.actions.rename(CODE_NODE_NEW_NAME);
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.actions.hitUndo();
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).should('exist');
|
||||
WorkflowPage.actions.hitRedo();
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NEW_NAME).should('exist');
|
||||
});
|
||||
|
||||
it('should undo/redo renaming node using keyboard shortcut', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
|
|
@ -11,21 +11,6 @@ describe('Inline expression editor', () => {
|
|||
cy.on('uncaught:exception', (error) => error.name !== 'ExpressionError');
|
||||
});
|
||||
|
||||
describe('Basic UI functionality', () => {
|
||||
it('should open and close inline expression preview', () => {
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.openNode('Schedule');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('123');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^123$/);
|
||||
// click outside to close
|
||||
ndv.getters.outputPanel().click();
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static data', () => {
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { successToast } from '../pages/notifications';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
|
@ -7,8 +9,6 @@ import {
|
|||
IF_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { successToast } from '../pages/notifications';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
describe('Canvas Actions', () => {
|
||||
|
@ -145,16 +145,7 @@ describe('Canvas Actions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should delete node by pressing keyboard backspace', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
|
||||
cy.get('body').type('{backspace}');
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
it('should delete connections by clicking on the delete button', () => {
|
||||
it('should delete connections by pressing the delete button', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
|
@ -7,8 +9,6 @@ import {
|
|||
SWITCH_NODE_NAME,
|
||||
MERGE_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const ExecutionsTab = new WorkflowExecutionsTab();
|
||||
|
|
|
@ -12,13 +12,8 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Data pinning', () => {
|
||||
const maxPinnedDataSize = 16384;
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.window().then((win) => {
|
||||
win.maxPinnedDataSize = maxPinnedDataSize;
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to pin node output', () => {
|
||||
|
@ -144,7 +139,7 @@ describe('Data pinning', () => {
|
|||
|
||||
ndv.actions.pastePinnedData([
|
||||
{
|
||||
test: '1'.repeat(maxPinnedDataSize),
|
||||
test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE') as number),
|
||||
},
|
||||
]);
|
||||
errorToast().should('contain', 'Workflow has reached the maximum allowed pinned data size');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
@ -40,13 +40,11 @@ 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.actions.mapDataFromHeader(2, 'value');
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', "{{ $json['Readable date'] }}{{ $json.timestamp }}");
|
||||
.should('have.text', "{{ $json.timestamp }} {{ $json['Readable date'] }}");
|
||||
});
|
||||
|
||||
it('maps expressions from table json, and resolves value based on hover', () => {
|
||||
|
@ -135,7 +133,6 @@ describe('Data mapping', () => {
|
|||
|
||||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '0');
|
||||
|
||||
ndv.getters
|
||||
|
@ -148,9 +145,8 @@ describe('Data mapping', () => {
|
|||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', '{{ $json.input }}{{ $json.input[0].count }}');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.actions.validateExpressionPreview('value', '[object Object]0');
|
||||
.should('have.text', '{{ $json.input[0].count }} {{ $json.input }}');
|
||||
ndv.actions.validateExpressionPreview('value', '0 [object Object]');
|
||||
});
|
||||
|
||||
it('maps expressions from schema view', () => {
|
||||
|
@ -167,7 +163,6 @@ describe('Data mapping', () => {
|
|||
|
||||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.actions.validateExpressionPreview('value', '0');
|
||||
|
||||
ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown();
|
||||
|
@ -175,8 +170,8 @@ describe('Data mapping', () => {
|
|||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', '{{ $json.input }}{{ $json.input[0].count }}');
|
||||
ndv.actions.validateExpressionPreview('value', '[object Object]0');
|
||||
.should('have.text', '{{ $json.input[0].count }} {{ $json.input }}');
|
||||
ndv.actions.validateExpressionPreview('value', '0 [object Object]');
|
||||
});
|
||||
|
||||
it('maps expressions from previous nodes', () => {
|
||||
|
@ -197,7 +192,6 @@ describe('Data mapping', () => {
|
|||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`);
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
|
||||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
@ -206,17 +200,17 @@ describe('Data mapping', () => {
|
|||
.inlineExpressionEditorInput()
|
||||
.should(
|
||||
'have.text',
|
||||
`{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input }}{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`,
|
||||
`{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }} {{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input }}`,
|
||||
);
|
||||
|
||||
ndv.actions.selectInputNode('Set');
|
||||
|
||||
ndv.getters.executingLoader().should('not.exist');
|
||||
ndv.getters.inputDataContainer().should('exist');
|
||||
ndv.actions.validateExpressionPreview('value', '[object Object]0');
|
||||
ndv.actions.validateExpressionPreview('value', '0 [object Object]');
|
||||
|
||||
ndv.getters.inputTbodyCell(2, 0).realHover();
|
||||
ndv.actions.validateExpressionPreview('value', '[object Object]1');
|
||||
ndv.actions.validateExpressionPreview('value', '1 [object Object]');
|
||||
});
|
||||
|
||||
it('maps keys to path', () => {
|
||||
|
@ -277,12 +271,12 @@ describe('Data mapping', () => {
|
|||
|
||||
ndv.actions.typeIntoParameterInput('value', 'fun');
|
||||
ndv.actions.clearParameterInput('value'); // keep focus on param
|
||||
cy.wait(300);
|
||||
|
||||
ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown();
|
||||
|
||||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.actions.validateExpressionPreview('value', '0');
|
||||
|
||||
ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown();
|
||||
|
@ -290,8 +284,8 @@ describe('Data mapping', () => {
|
|||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', '{{ $json.input }}{{ $json.input[0].count }}');
|
||||
ndv.actions.validateExpressionPreview('value', '[object Object]0');
|
||||
.should('have.text', '{{ $json.input[0].count }} {{ $json.input }}');
|
||||
ndv.actions.validateExpressionPreview('value', '0 [object Object]');
|
||||
});
|
||||
|
||||
it('renders expression preview when a previous node is selected', () => {
|
||||
|
@ -323,12 +317,19 @@ describe('Data mapping', () => {
|
|||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.openNode('Set');
|
||||
ndv.actions.clearParameterInput('value');
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
ndv.getters.parameterInput('includeOtherFields').find('input[type="checkbox"]').should('exist');
|
||||
ndv.getters.parameterInput('includeOtherFields').find('input[type="text"]').should('not.exist');
|
||||
const pill = ndv.getters.inputDataContainer().find('span').contains('count');
|
||||
pill.should('be.visible');
|
||||
pill.realMouseDown();
|
||||
pill.realMouseMove(100, 100);
|
||||
ndv.getters
|
||||
.inputDataContainer()
|
||||
.should('exist')
|
||||
.find('span')
|
||||
.contains('count')
|
||||
.realMouseDown()
|
||||
.realMouseMove(100, 100);
|
||||
cy.wait(50);
|
||||
|
||||
ndv.getters
|
||||
.parameterInput('includeOtherFields')
|
||||
|
@ -339,40 +340,13 @@ describe('Data mapping', () => {
|
|||
.find('input[type="text"]')
|
||||
.should('exist')
|
||||
.invoke('css', 'border')
|
||||
.should('include', 'dashed rgb(90, 76, 194)');
|
||||
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
|
||||
|
||||
ndv.getters
|
||||
.parameterInput('value')
|
||||
.find('input[type="text"]')
|
||||
.should('exist')
|
||||
.invoke('css', 'border')
|
||||
.should('include', 'dashed rgb(90, 76, 194)');
|
||||
});
|
||||
|
||||
it('maps expressions to a specific location in the editor', () => {
|
||||
cy.fixture('Test_workflow_3.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
});
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.openNode('Set');
|
||||
ndv.actions.typeIntoParameterInput('value', '=');
|
||||
ndv.getters.inlineExpressionEditorInput().find('.cm-content').paste('hello world\n\nnewline');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
|
||||
ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown();
|
||||
ndv.actions.mapToParameter('value');
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', '{{ $json.input[0].count }}hello worldnewline');
|
||||
ndv.getters.inlineExpressionEditorInput().type('{esc}');
|
||||
ndv.actions.validateExpressionPreview('value', '0hello world\n\nnewline');
|
||||
|
||||
ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown();
|
||||
ndv.actions.mapToParameter('value', 'center');
|
||||
|
||||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', '{{ $json.input[0].count }}hello world{{ $json.input }}newline');
|
||||
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
||||
import { BACKEND_BASE_URL } from '../constants';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import type { ExecutionResponse } from '../types';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
|
@ -15,4 +19,53 @@ describe('Schedule Trigger node', () => {
|
|||
ndv.getters.outputPanel().contains('timestamp');
|
||||
ndv.getters.backToCanvas().click();
|
||||
});
|
||||
|
||||
it('should execute once per second when activated', () => {
|
||||
workflowPage.actions.renameWorkflow('Schedule Trigger Workflow');
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.openNode('Schedule Trigger');
|
||||
|
||||
cy.getByTestId('parameter-input-field').click();
|
||||
getVisibleSelect().find('.option-headline').contains('Seconds').click();
|
||||
cy.getByTestId('parameter-input-secondsInterval').clear().type('1');
|
||||
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.actions.activateWorkflow();
|
||||
workflowPage.getters.activatorSwitch().should('have.class', 'is-checked');
|
||||
|
||||
cy.url().then((url) => {
|
||||
const workflowId = url.split('/').pop();
|
||||
|
||||
cy.wait(1200);
|
||||
cy.request<ExecutionResponse>('GET', `${BACKEND_BASE_URL}/rest/executions`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(workflowId).to.not.be.undefined;
|
||||
expect(response.body.data.results.length).to.be.greaterThan(0);
|
||||
const matchingExecutions = response.body.data.results.filter(
|
||||
(execution) => execution.workflowId === workflowId,
|
||||
);
|
||||
expect(matchingExecutions).to.have.length(1);
|
||||
|
||||
cy.wait(1200);
|
||||
cy.request<ExecutionResponse>('GET', `${BACKEND_BASE_URL}/rest/executions`).then(
|
||||
(response1) => {
|
||||
expect(response1.status).to.eq(200);
|
||||
expect(response1.body.data.results.length).to.be.greaterThan(0);
|
||||
const matchingExecutions1 = response1.body.data.results.filter(
|
||||
(execution: any) => execution.workflowId === workflowId,
|
||||
);
|
||||
expect(matchingExecutions1).to.have.length(2);
|
||||
|
||||
workflowPage.actions.activateWorkflow();
|
||||
workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked');
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { BACKEND_BASE_URL, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||
import { cowBase64 } from '../support/binaryTestFiles';
|
||||
import { BACKEND_BASE_URL, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as projects from '../composables/projects';
|
||||
import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN, NOTION_NODE_NAME } from '../constants';
|
||||
import {
|
||||
CredentialsModal,
|
||||
|
@ -8,7 +7,8 @@ import {
|
|||
WorkflowSharingModal,
|
||||
WorkflowsPage,
|
||||
} from '../pages';
|
||||
import { getVisibleDropdown, getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import * as projects from '../composables/projects';
|
||||
|
||||
/**
|
||||
* User U1 - Instance owner
|
||||
|
@ -135,11 +135,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowPage.actions.openNode('Notion');
|
||||
ndv.getters
|
||||
.credentialInput()
|
||||
.find('input')
|
||||
.should('have.value', 'Credential C1')
|
||||
.should('be.enabled');
|
||||
ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled');
|
||||
ndv.actions.close();
|
||||
|
||||
cy.waitForLoad();
|
||||
|
@ -180,8 +176,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
).should('be.visible');
|
||||
|
||||
credentialsModal.getters.usersSelect().click();
|
||||
getVisiblePopper()
|
||||
.find('[data-test-id="project-sharing-info"]')
|
||||
cy.getByTestId('project-sharing-info')
|
||||
.filter(':visible')
|
||||
.should('have.length', 3)
|
||||
.contains(INSTANCE_ADMIN.email)
|
||||
|
@ -193,79 +188,11 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
credentialsModal.actions.saveSharing();
|
||||
credentialsModal.actions.close();
|
||||
});
|
||||
|
||||
it('credentials should work between team and personal projects', () => {
|
||||
cy.resetDatabase();
|
||||
cy.enableFeature('sharing');
|
||||
cy.enableFeature('advancedPermissions');
|
||||
cy.enableFeature('projectRole:admin');
|
||||
cy.enableFeature('projectRole:editor');
|
||||
cy.changeQuota('maxTeamProjects', -1);
|
||||
|
||||
cy.signinAsOwner();
|
||||
cy.visit('/');
|
||||
|
||||
projects.createProject('Development');
|
||||
|
||||
projects.getHomeButton().click();
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
projects.createWorkflow('Test_workflow_1.json', 'Test workflow');
|
||||
|
||||
projects.getHomeButton().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
projects.createCredential('Notion API');
|
||||
|
||||
credentialsPage.getters.credentialCard('Notion API').click();
|
||||
credentialsModal.actions.changeTab('Sharing');
|
||||
credentialsModal.getters.usersSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 4)
|
||||
.filter(':contains("Development")')
|
||||
.should('have.length', 1)
|
||||
.click();
|
||||
credentialsModal.getters.saveButton().click();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
projects.getProjectTabWorkflows().click();
|
||||
workflowsPage.getters.workflowCardActions('Test workflow').click();
|
||||
getVisibleDropdown().find('li').contains('Share').click();
|
||||
|
||||
workflowSharingModal.getters.usersSelect().filter(':visible').click();
|
||||
getVisibleSelect().find('li').should('have.length', 3).first().click();
|
||||
workflowSharingModal.getters.saveButton().click();
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
projects.createWorkflow('Test_workflow_1.json', 'Test workflow 2');
|
||||
workflowPage.actions.openShareModal();
|
||||
workflowSharingModal.getters.usersSelect().should('not.exist');
|
||||
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.createCredentialButton().click();
|
||||
projects.createCredential('Notion API 2', false);
|
||||
credentialsModal.actions.changeTab('Sharing');
|
||||
credentialsModal.getters.usersSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 4).first().click();
|
||||
credentialsModal.getters.saveButton().click();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
credentialsPage.getters
|
||||
.credentialCards()
|
||||
.should('have.length', 2)
|
||||
.filter(':contains("Owned by me")')
|
||||
.should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Credential Usage in Cross Shared Workflows', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetDatabase();
|
||||
cy.enableFeature('sharing');
|
||||
cy.enableFeature('advancedPermissions');
|
||||
cy.enableFeature('projectRole:admin');
|
||||
cy.enableFeature('projectRole:editor');
|
||||
|
@ -276,18 +203,23 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
});
|
||||
|
||||
it('should only show credentials from the same team project', () => {
|
||||
cy.enableFeature('advancedPermissions');
|
||||
cy.enableFeature('projectRole:admin');
|
||||
cy.enableFeature('projectRole:editor');
|
||||
cy.changeQuota('maxTeamProjects', -1);
|
||||
|
||||
// Create a notion credential in the home project
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
|
||||
// Create a notion credential in one project
|
||||
projects.createProject('Development');
|
||||
projects.actions.createProject('Development');
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
|
||||
// Create a notion credential in another project
|
||||
projects.createProject('Test');
|
||||
projects.actions.createProject('Test');
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
|
@ -303,6 +235,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
});
|
||||
|
||||
it('should only show credentials in their personal project for members', () => {
|
||||
cy.enableFeature('sharing');
|
||||
cy.reload();
|
||||
|
||||
// Create a notion credential as the owner
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.actions.createNewCredential('Notion API');
|
||||
|
@ -332,6 +267,8 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
|
||||
it('should only show credentials in their personal project for members if the workflow was shared with them', () => {
|
||||
const workflowName = 'Test workflow';
|
||||
cy.enableFeature('sharing');
|
||||
cy.reload();
|
||||
|
||||
// Create a notion credential as the owner and a workflow that is shared
|
||||
// with member 0
|
||||
|
@ -362,6 +299,7 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
|
||||
it("should show all credentials from all personal projects the workflow's been shared into for the global owner", () => {
|
||||
const workflowName = 'Test workflow';
|
||||
cy.enableFeature('sharing');
|
||||
|
||||
// As member 1, create a new notion credential. This should not show up.
|
||||
cy.signinAsMember(1);
|
||||
|
@ -406,6 +344,8 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
});
|
||||
|
||||
it('should show all personal credentials if the global owner owns the workflow', () => {
|
||||
cy.enableFeature('sharing');
|
||||
|
||||
// As member 0, create a new notion credential.
|
||||
cy.signinAsMember();
|
||||
cy.visit(credentialsPage.url);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { WorkflowPage } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const wf = new WorkflowPage();
|
||||
|
||||
|
@ -52,6 +51,28 @@ describe('Workflow tags', () => {
|
|||
wf.getters.tagPills().should('have.length', 0); // none attached
|
||||
});
|
||||
|
||||
it('should update a tag via modal', () => {
|
||||
wf.actions.openTagManagerModal();
|
||||
|
||||
const [first] = TEST_TAGS;
|
||||
|
||||
cy.contains('Create a tag').click();
|
||||
cy.getByTestId('tags-table').find('input').type(first).type('{enter}');
|
||||
cy.getByTestId('tags-table').should('contain.text', first);
|
||||
cy.getByTestId('edit-tag-button').eq(-1).click({ force: true });
|
||||
cy.wait(300);
|
||||
cy.getByTestId('tags-table')
|
||||
.find('.el-input--large')
|
||||
.should('be.visible')
|
||||
.type(' Updated')
|
||||
.type('{enter}');
|
||||
cy.contains('Done').click();
|
||||
wf.getters.createTagButton().click();
|
||||
wf.getters.tagsInDropdown().should('have.length', 1); // one stored
|
||||
wf.getters.tagsInDropdown().contains('Updated').should('exist');
|
||||
wf.getters.tagPills().should('have.length', 0); // none attached
|
||||
});
|
||||
|
||||
it('should detach a tag inline by clicking on X on tag pill', () => {
|
||||
wf.getters.createTagButton().click();
|
||||
wf.actions.addTags(TEST_TAGS);
|
||||
|
@ -65,26 +86,10 @@ describe('Workflow tags', () => {
|
|||
it('should detach a tag inline by clicking on dropdown list item', () => {
|
||||
wf.getters.createTagButton().click();
|
||||
wf.actions.addTags(TEST_TAGS);
|
||||
wf.getters.workflowTagsContainer().click();
|
||||
wf.getters.nthTagPill(1).click();
|
||||
wf.getters.tagsInDropdown().filter('.selected').first().click();
|
||||
cy.get('body').click(0, 0);
|
||||
wf.getters.workflowTags().click();
|
||||
wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1);
|
||||
});
|
||||
|
||||
it('should not show non existing tag as a selectable option', () => {
|
||||
const NON_EXISTING_TAG = 'My Test Tag';
|
||||
|
||||
wf.getters.createTagButton().click();
|
||||
wf.actions.addTags(TEST_TAGS);
|
||||
cy.get('body').click(0, 0);
|
||||
wf.getters.workflowTags().click();
|
||||
wf.getters.workflowTagsInput().type(NON_EXISTING_TAG);
|
||||
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.filter(`:contains("${NON_EXISTING_TAG}")`)
|
||||
.should('not.have.length');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN } from '../constants';
|
||||
import { MainSidebar, SettingsSidebar, SettingsUsersPage } from '../pages';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
|
||||
/**
|
||||
* User A - Instance owner
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('PAY-1858 context menu', () => {
|
||||
it('can use context menu on saved workflow', () => {
|
||||
WorkflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_filter.json', 'test');
|
||||
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 5);
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu('Then');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
|
||||
cy.reload();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu('Code');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import { SCHEDULE_TRIGGER_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { NDV, WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { SCHEDULE_TRIGGER_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { clearNotifications, errorToast, successToast } from '../pages/notifications';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
|
@ -275,6 +275,7 @@ describe('Execution', () => {
|
|||
.within(() => cy.get('.fa-check').should('not.exist'));
|
||||
|
||||
successToast().should('be.visible');
|
||||
clearNotifications();
|
||||
|
||||
// Clear execution data
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
|
@ -503,7 +504,7 @@ describe('Execution', () => {
|
|||
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('workflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('do something with them')
|
||||
|
@ -525,7 +526,7 @@ describe('Execution', () => {
|
|||
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('workflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('If')
|
||||
|
@ -547,7 +548,7 @@ describe('Execution', () => {
|
|||
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('workflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('NoOp2')
|
||||
|
@ -576,7 +577,7 @@ describe('Execution', () => {
|
|||
it('should successfully execute partial executions with nodes attached to the second output', () => {
|
||||
cy.createFixtureWorkflow('Test_Workflow_pairedItem_incomplete_manual_bug.json');
|
||||
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('workflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
workflowPage.getters.executeWorkflowButton().click();
|
||||
|
@ -596,7 +597,7 @@ describe('Execution', () => {
|
|||
it('should execute workflow partially up to the node that has issues', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_partial_execution_with_missing_credentials.json');
|
||||
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('workflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
workflowPage.getters.executeWorkflowButton().click();
|
||||
|
@ -616,45 +617,4 @@ describe('Execution', () => {
|
|||
|
||||
errorToast().should('contain', 'Problem in node ‘Telegram‘');
|
||||
});
|
||||
|
||||
it('should not show pinned data in production execution', () => {
|
||||
cy.createFixtureWorkflow('Execution-pinned-data-check.json');
|
||||
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
cy.intercept('PATCH', '/rest/workflows/*').as('workflowActivate');
|
||||
workflowPage.getters.activatorSwitch().click();
|
||||
|
||||
cy.wait('@workflowActivate');
|
||||
cy.get('body').type('{esc}');
|
||||
workflowPage.actions.openNode('Webhook');
|
||||
|
||||
cy.contains('label', 'Production URL').should('be.visible').click();
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
cy.get('.webhook-url').click();
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
cy.readClipboard().then((url) => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
}).then((resp) => {
|
||||
expect(resp.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait('@getExecution');
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.connection-run-items-label')
|
||||
.filter(':contains("5 items")')
|
||||
.should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { type ICredentialType } from 'n8n-workflow';
|
||||
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
AI_TOOL_HTTP_NODE_NAME,
|
||||
GMAIL_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
NEW_GOOGLE_ACCOUNT_NAME,
|
||||
|
@ -113,13 +110,13 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().should('have.length', 2);
|
||||
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').contains('Create New Credential').click();
|
||||
getVisibleSelect().find('li').last().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();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
// This one should not show auth type selector
|
||||
credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist');
|
||||
});
|
||||
|
@ -204,31 +201,6 @@ describe('Credentials', () => {
|
|||
.should('have.value', NEW_CREDENTIAL_NAME2);
|
||||
});
|
||||
|
||||
it('should edit credential for non-standard credential type', () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addNodeToCanvas(AGENT_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(AI_TOOL_HTTP_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
cy.getByTestId('parameter-input-authentication').click();
|
||||
cy.contains('Predefined Credential Type').click();
|
||||
cy.getByTestId('credential-select').click();
|
||||
cy.contains('Adalo API').click();
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters.nodeCredentialsEditButton().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.name().click();
|
||||
credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME);
|
||||
credentialsModal.getters.saveButton().click();
|
||||
credentialsModal.getters.closeButton().click();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
.find('input')
|
||||
.should('have.value', NEW_CREDENTIAL_NAME);
|
||||
});
|
||||
|
||||
it('should setup generic authentication for HTTP node', () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import type { RouteHandler } from 'cypress/types/net-stubbing';
|
||||
|
||||
import executionOutOfMemoryServerResponse from '../fixtures/responses/execution-out-of-memory-server-response.json';
|
||||
import { WorkflowPage } from '../pages';
|
||||
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
||||
import executionOutOfMemoryServerResponse from '../fixtures/responses/execution-out-of-memory-server-response.json';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
@ -10,281 +9,218 @@ const executionsTab = new WorkflowExecutionsTab();
|
|||
const executionsRefreshInterval = 4000;
|
||||
|
||||
// Test suite for executions tab
|
||||
describe('Workflow Executions', () => {
|
||||
describe('when workflow is saved', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', 'My test workflow');
|
||||
});
|
||||
|
||||
it('should render executions tab correctly', () => {
|
||||
createMockExecutions();
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
|
||||
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
|
||||
|
||||
executionsTab.getters.executionListItems().should('have.length', 11);
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 9);
|
||||
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
|
||||
executionsTab.getters
|
||||
.executionListItems()
|
||||
.first()
|
||||
.invoke('attr', 'class')
|
||||
.should('match', /_active_/);
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when request is not done before leaving the page', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*');
|
||||
cy.intercept('GET', '/rest/executions/active?filter=*');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(1000);
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
|
||||
const throttleResponse: RouteHandler = async (req) => {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => resolve(req.continue()), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
cy.intercept('GET', '/rest/executions?filter=*', throttleResponse);
|
||||
cy.intercept('GET', '/rest/executions/active?filter=*', throttleResponse);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
|
||||
it('should error toast when server error message returned without stack trace', () => {
|
||||
executionsTab.actions.createManualExecutions(1);
|
||||
const message = 'Workflow did not finish, possible out-of-memory issue';
|
||||
cy.intercept('GET', '/rest/executions/*', {
|
||||
statusCode: 200,
|
||||
body: executionOutOfMemoryServerResponse,
|
||||
}).as('getExecution');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(['@getExecution']);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body') // Access the body of the iframe document
|
||||
.should('not.be.empty') // Ensure the body is not empty
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.el-notification:has(.el-notification--error)')
|
||||
.should('be.visible')
|
||||
.filter(`:contains("${message}")`)
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('should show workflow data in executions tab after hard reload and modify name and tags', () => {
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
|
||||
workflowPage.getters.workflowTags().click();
|
||||
getVisibleSelect().find('li:contains("Manage tags")').click();
|
||||
cy.get('button:contains("Add new")').click();
|
||||
cy.getByTestId('tags-table').find('input').type('nutag').type('{enter}');
|
||||
cy.get('button:contains("Done")').click();
|
||||
|
||||
cy.reload();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.workflowTags().click();
|
||||
workflowPage.getters.tagsInDropdown().first().should('have.text', 'nutag').click();
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
|
||||
let newWorkflowName = 'Renamed workflow';
|
||||
workflowPage.actions.renameWorkflow(newWorkflowName);
|
||||
workflowPage.getters.isWorkflowSaved();
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
newWorkflowName = 'New workflow';
|
||||
workflowPage.actions.renameWorkflow(newWorkflowName);
|
||||
workflowPage.getters.isWorkflowSaved();
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
workflowPage.getters.workflowTags().click();
|
||||
workflowPage.getters.tagsDropdown().find('.el-tag__close').first().click();
|
||||
cy.get('body').click(0, 0);
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
});
|
||||
|
||||
it('should load items and auto scroll after filter change', () => {
|
||||
createMockExecutions();
|
||||
createMockExecutions();
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
|
||||
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
|
||||
|
||||
executionsTab.getters.executionListItems().eq(10).click();
|
||||
|
||||
cy.getByTestId('executions-filter-button').click();
|
||||
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
|
||||
getVisibleSelect().find('li:contains("Error")').click();
|
||||
|
||||
executionsTab.getters.executionListItems().should('have.length', 5);
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||
executionsTab.getters.failedExecutionListItems().should('have.length', 4);
|
||||
|
||||
cy.getByTestId('executions-filter-button').click();
|
||||
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
|
||||
getVisibleSelect().find('li:contains("Success")').click();
|
||||
|
||||
// check if the list is scrolled
|
||||
executionsTab.getters.executionListItems().eq(10).should('be.visible');
|
||||
executionsTab.getters.executionsList().then(($el) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = $el[0];
|
||||
expect(scrollTop).to.be.greaterThan(0);
|
||||
expect(scrollTop + clientHeight).to.be.lessThan(scrollHeight);
|
||||
|
||||
// scroll to the bottom
|
||||
$el[0].scrollTo(0, scrollHeight);
|
||||
executionsTab.getters.executionListItems().should('have.length', 18);
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 18);
|
||||
executionsTab.getters.failedExecutionListItems().should('have.length', 0);
|
||||
});
|
||||
|
||||
cy.getByTestId('executions-filter-button').click();
|
||||
cy.getByTestId('executions-filter-reset-button').should('be.visible').click();
|
||||
executionsTab.getters.executionListItems().eq(11).should('be.visible');
|
||||
});
|
||||
|
||||
it('should redirect back to editor after seeing a couple of execution using browser back button', () => {
|
||||
createMockExecutions();
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
|
||||
executionsTab.getters.executionListItems().eq(2).click();
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
executionsTab.getters.executionListItems().eq(4).click();
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
executionsTab.getters.executionListItems().eq(6).click();
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
|
||||
cy.go('back');
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
cy.go('back');
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
cy.go('back');
|
||||
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
|
||||
cy.go('back');
|
||||
|
||||
cy.url().should('not.include', '/executions');
|
||||
cy.url().should('include', '/workflow/');
|
||||
workflowPage.getters.nodeViewRoot().should('be.visible');
|
||||
});
|
||||
describe('Current Workflow Executions', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', 'My test workflow');
|
||||
});
|
||||
|
||||
describe('when new workflow is not saved', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
it('should render executions tab correctly', () => {
|
||||
createMockExecutions();
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
it('should open executions tab', () => {
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.getters.executionsSidebar().should('be.visible');
|
||||
executionsTab.getters.executionsEmptyList().should('be.visible');
|
||||
cy.getByTestId('workflow-execution-no-trigger-content').should('be.visible');
|
||||
cy.get('button:contains("Add first step")').should('be.visible').click();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.getByTestId('node-creator-item-name')
|
||||
.should('be.visible')
|
||||
.filter(':contains("Trigger")')
|
||||
.click();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.getters.executionsSidebar().should('be.visible');
|
||||
executionsTab.getters.executionsEmptyList().should('be.visible');
|
||||
cy.getByTestId('workflow-execution-no-content').should('be.visible');
|
||||
cy.wait(['@getExecutions']);
|
||||
|
||||
workflowPage.getters.saveButton().find('button').should('be.enabled').click();
|
||||
workflowPage.getters.isWorkflowSaved();
|
||||
workflowPage.getters.nodeViewRoot().should('be.visible');
|
||||
});
|
||||
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
|
||||
|
||||
executionsTab.getters.executionListItems().should('have.length', 11);
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 9);
|
||||
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
|
||||
executionsTab.getters
|
||||
.executionListItems()
|
||||
.first()
|
||||
.invoke('attr', 'class')
|
||||
.should('match', /_active_/);
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when request is not done before leaving the page', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*');
|
||||
cy.intercept('GET', '/rest/executions/active?filter=*');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(1000);
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
|
||||
const throttleResponse: RouteHandler = async (req) => {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => resolve(req.continue()), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
cy.intercept('GET', '/rest/executions?filter=*', throttleResponse);
|
||||
cy.intercept('GET', '/rest/executions/active?filter=*', throttleResponse);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
|
||||
it('should error toast when server error message returned without stack trace', () => {
|
||||
executionsTab.actions.createManualExecutions(1);
|
||||
const message = 'Workflow did not finish, possible out-of-memory issue';
|
||||
cy.intercept('GET', '/rest/executions/*', {
|
||||
statusCode: 200,
|
||||
body: executionOutOfMemoryServerResponse,
|
||||
}).as('getExecution');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(['@getExecution']);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body') // Access the body of the iframe document
|
||||
.should('not.be.empty') // Ensure the body is not empty
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.el-notification:has(.el-notification--error)')
|
||||
.should('be.visible')
|
||||
.filter(`:contains("${message}")`)
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('should auto load more items if there is space and auto scroll', () => {
|
||||
cy.viewport(1280, 960);
|
||||
executionsTab.actions.createManualExecutions(24);
|
||||
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
executionsTab.getters.executionListItems().its('length').should('be.gte', 10);
|
||||
|
||||
cy.getByTestId('current-executions-list').scrollTo('bottom');
|
||||
cy.wait(['@getExecutions']);
|
||||
executionsTab.getters.executionListItems().should('have.length', 24);
|
||||
|
||||
executionsTab.getters.executionListItems().eq(14).click();
|
||||
cy.wait(['@getExecution']);
|
||||
cy.reload();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
executionsTab.getters.executionListItems().eq(14).should('not.be.visible');
|
||||
executionsTab.getters.executionListItems().should('have.length', 24);
|
||||
executionsTab.getters.executionListItems().first().should('not.be.visible');
|
||||
cy.getByTestId('current-executions-list').scrollTo(0, 0);
|
||||
executionsTab.getters.executionListItems().first().should('be.visible');
|
||||
executionsTab.getters.executionListItems().eq(14).should('not.be.visible');
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait(['@getExecutions']);
|
||||
executionsTab.getters.executionListItems().eq(14).should('not.be.visible');
|
||||
executionsTab.getters.executionListItems().should('have.length', 24);
|
||||
executionsTab.getters.executionListItems().first().should('not.be.visible');
|
||||
cy.getByTestId('current-executions-list').scrollTo(0, 0);
|
||||
executionsTab.getters.executionListItems().first().should('be.visible');
|
||||
executionsTab.getters.executionListItems().eq(14).should('not.be.visible');
|
||||
});
|
||||
|
||||
it('should show workflow data in executions tab after hard reload and modify name and tags', () => {
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
|
||||
workflowPage.getters.workflowTags().click();
|
||||
getVisibleSelect().find('li:contains("Manage tags")').click();
|
||||
cy.get('button:contains("Add new")').click();
|
||||
cy.getByTestId('tags-table').find('input').type('nutag').type('{enter}');
|
||||
cy.get('button:contains("Done")').click();
|
||||
|
||||
cy.reload();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.workflowTags().click();
|
||||
workflowPage.getters.tagsInDropdown().first().should('have.text', 'nutag').click();
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
|
||||
let newWorkflowName = 'Renamed workflow';
|
||||
workflowPage.actions.renameWorkflow(newWorkflowName);
|
||||
workflowPage.getters.isWorkflowSaved();
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
newWorkflowName = 'New workflow';
|
||||
workflowPage.actions.renameWorkflow(newWorkflowName);
|
||||
workflowPage.getters.isWorkflowSaved();
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
workflowPage.getters.workflowTags().click();
|
||||
workflowPage.getters.tagsDropdown().find('.el-tag__close').first().click();
|
||||
cy.get('body').click(0, 0);
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
checkMainHeaderELements();
|
||||
workflowPage.getters.saveButton().find('button').should('not.exist');
|
||||
workflowPage.getters.tagPills().should('have.length', 2);
|
||||
workflowPage.getters
|
||||
.workflowNameInputContainer()
|
||||
.invoke('attr', 'title')
|
||||
.should('eq', newWorkflowName);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -292,11 +228,9 @@ const createMockExecutions = () => {
|
|||
executionsTab.actions.createManualExecutions(5);
|
||||
// Make some failed executions by enabling Code node with syntax error
|
||||
executionsTab.actions.toggleNodeEnabled('Error');
|
||||
workflowPage.getters.disabledNodes().should('have.length', 0);
|
||||
executionsTab.actions.createManualExecutions(2);
|
||||
// Then add some more successful ones
|
||||
executionsTab.actions.toggleNodeEnabled('Error');
|
||||
workflowPage.getters.disabledNodes().should('have.length', 1);
|
||||
executionsTab.actions.createManualExecutions(4);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import type { ICredentialType } from 'n8n-workflow';
|
||||
|
||||
import CustomCredential from '../fixtures/Custom_credential.json';
|
||||
import CustomNodeFixture from '../fixtures/Custom_node.json';
|
||||
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
||||
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
||||
import { CredentialsModal, WorkflowPage } from '../pages';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
import {
|
||||
confirmCommunityNodeUninstall,
|
||||
confirmCommunityNodeUpdate,
|
||||
getCommunityCards,
|
||||
installFirstCommunityNode,
|
||||
visitCommunityNodesSettings,
|
||||
} from '../pages/settings-community-nodes';
|
||||
import CustomNodeFixture from '../fixtures/Custom_node.json';
|
||||
import { CredentialsModal, WorkflowPage } from '../pages';
|
||||
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
||||
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
||||
import CustomCredential from '../fixtures/Custom_credential.json';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
@ -22,7 +14,7 @@ const workflowPage = new WorkflowPage();
|
|||
// We separate-out the custom nodes because they require injecting nodes and credentials
|
||||
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
||||
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
||||
describe('Community and custom nodes in canvas', () => {
|
||||
describe('Community Nodes', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
@ -103,89 +95,3 @@ describe('Community and custom nodes in canvas', () => {
|
|||
credentialsModal.getters.editCredentialModal().should('contain.text', 'Custom E2E Credential');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Community nodes', () => {
|
||||
const mockPackage = {
|
||||
createdAt: '2024-07-22T19:08:06.505Z',
|
||||
updatedAt: '2024-07-22T19:08:06.505Z',
|
||||
packageName: 'n8n-nodes-chatwork',
|
||||
installedVersion: '1.0.0',
|
||||
authorName: null,
|
||||
authorEmail: null,
|
||||
installedNodes: [
|
||||
{
|
||||
name: 'Chatwork',
|
||||
type: 'n8n-nodes-chatwork.chatwork',
|
||||
latestVersion: 1,
|
||||
},
|
||||
],
|
||||
updateAvailable: '1.1.2',
|
||||
};
|
||||
|
||||
it('can install, update and uninstall community nodes', () => {
|
||||
cy.intercept(
|
||||
{
|
||||
hostname: 'api.npms.io',
|
||||
pathname: '/v2/search',
|
||||
query: { q: 'keywords:n8n-community-node-package' },
|
||||
},
|
||||
{ body: {} },
|
||||
);
|
||||
cy.intercept(
|
||||
{ method: 'GET', pathname: '/rest/community-packages', times: 1 },
|
||||
{
|
||||
body: { data: [] },
|
||||
},
|
||||
).as('getEmptyPackages');
|
||||
visitCommunityNodesSettings();
|
||||
cy.wait('@getEmptyPackages');
|
||||
|
||||
// install a package
|
||||
cy.intercept(
|
||||
{ method: 'POST', pathname: '/rest/community-packages', times: 1 },
|
||||
{
|
||||
body: { data: mockPackage },
|
||||
},
|
||||
).as('installPackage');
|
||||
cy.intercept(
|
||||
{ method: 'GET', pathname: '/rest/community-packages', times: 1 },
|
||||
{
|
||||
body: { data: [mockPackage] },
|
||||
},
|
||||
).as('getPackages');
|
||||
installFirstCommunityNode('n8n-nodes-chatwork@1.0.0');
|
||||
cy.wait('@installPackage');
|
||||
cy.wait('@getPackages');
|
||||
getCommunityCards().should('have.length', 1);
|
||||
getCommunityCards().eq(0).should('include.text', 'v1.0.0');
|
||||
|
||||
// update the package
|
||||
cy.intercept(
|
||||
{ method: 'PATCH', pathname: '/rest/community-packages' },
|
||||
{
|
||||
body: { data: { ...mockPackage, installedVersion: '1.2.0', updateAvailable: undefined } },
|
||||
},
|
||||
).as('updatePackage');
|
||||
getCommunityCards().eq(0).find('button').click();
|
||||
confirmCommunityNodeUpdate();
|
||||
cy.wait('@updatePackage');
|
||||
getCommunityCards().should('have.length', 1);
|
||||
getCommunityCards().eq(0).should('not.include.text', 'v1.0.0');
|
||||
|
||||
// uninstall the package
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'DELETE',
|
||||
pathname: '/rest/community-packages',
|
||||
query: { name: 'n8n-nodes-chatwork' },
|
||||
},
|
||||
{ statusCode: 204 },
|
||||
).as('uninstallPackage');
|
||||
getCommunityCards().getByTestId('action-toggle').click();
|
||||
cy.getByTestId('action-uninstall').click();
|
||||
confirmCommunityNodeUninstall();
|
||||
cy.wait('@uninstallPackage');
|
||||
|
||||
cy.getByTestId('action-box').should('exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { BACKEND_BASE_URL } from '../constants';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
|
@ -16,6 +17,33 @@ describe('ADO-2106 connections should be colored correctly for pinned data in ex
|
|||
workflowPage.getters.getConnectionBetweenNodes('Webhook', 'Set').should('have.class', 'pinned');
|
||||
});
|
||||
|
||||
it('should not color connections for pinned data nodes for production executions', () => {
|
||||
workflowPage.actions.activateWorkflow();
|
||||
|
||||
// Execute the workflow
|
||||
cy.request('POST', `${BACKEND_BASE_URL}/webhook/23fc3930-b8f9-41d9-89db-b647291a2201`, {
|
||||
here: 'is some data',
|
||||
}).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]')
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('not.have.class', 'pinned');
|
||||
});
|
||||
|
||||
it('should color connections for pinned data nodes for manual executions', () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
import type { ExecutionError } from 'n8n-workflow/src';
|
||||
|
||||
import {
|
||||
closeManualChatModal,
|
||||
getManualChatMessages,
|
||||
getManualChatModalLogs,
|
||||
getManualChatModalLogsEntries,
|
||||
sendManualChatMessage,
|
||||
} from '../composables/modals/chat-modal';
|
||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import {
|
||||
clickCreateNewCredential,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
addMemoryNodeToParent,
|
||||
addNodeToCanvas,
|
||||
addToolNodeToParent,
|
||||
navigateToNewWorkflowPage,
|
||||
openNode,
|
||||
} from '../composables/workflow';
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
AI_MEMORY_POSTGRES_NODE_NAME,
|
||||
AI_TOOL_CALCULATOR_NODE_NAME,
|
||||
CHAT_TRIGGER_NODE_DISPLAY_NAME,
|
||||
MANUAL_CHAT_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { NDV, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { createMockNodeExecutionData, getVisibleSelect, runMockWorkflowExecution } from '../utils';
|
||||
|
||||
const ndv = new NDV();
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
function createRunDataWithError(inputMessage: string) {
|
||||
return [
|
||||
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { input: inputMessage },
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AI_MEMORY_POSTGRES_NODE_NAME, {
|
||||
jsonData: {
|
||||
ai_memory: {
|
||||
json: {
|
||||
action: 'loadMemoryVariables',
|
||||
values: {
|
||||
input: inputMessage,
|
||||
system_message: 'You are a helpful assistant',
|
||||
formatting_instructions:
|
||||
'IMPORTANT: Always call `format_final_response` to format your final response!',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputOverride: {
|
||||
ai_memory: [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
action: 'loadMemoryVariables',
|
||||
values: {
|
||||
input: inputMessage,
|
||||
system_message: 'You are a helpful assistant',
|
||||
formatting_instructions:
|
||||
'IMPORTANT: Always call `format_final_response` to format your final response!',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
error: {
|
||||
message: 'Internal error',
|
||||
timestamp: 1722591723244,
|
||||
name: 'NodeOperationError',
|
||||
description: 'Internal error',
|
||||
context: {},
|
||||
cause: {
|
||||
name: 'error',
|
||||
severity: 'FATAL',
|
||||
code: '3D000',
|
||||
file: 'postinit.c',
|
||||
line: '885',
|
||||
routine: 'InitPostgres',
|
||||
} as unknown as Error,
|
||||
} as ExecutionError,
|
||||
}),
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
executionStatus: 'error',
|
||||
error: {
|
||||
level: 'error',
|
||||
tags: {
|
||||
packageName: 'workflow',
|
||||
},
|
||||
context: {},
|
||||
functionality: 'configuration-node',
|
||||
name: 'NodeOperationError',
|
||||
timestamp: 1722591723244,
|
||||
node: {
|
||||
parameters: {
|
||||
notice: '',
|
||||
sessionIdType: 'fromInput',
|
||||
tableName: 'n8n_chat_histories',
|
||||
},
|
||||
id: '6b9141da-0135-4e9d-94d1-2d658cbf48b5',
|
||||
name: 'Postgres Chat Memory',
|
||||
type: '@n8n/n8n-nodes-langchain.memoryPostgresChat',
|
||||
typeVersion: 1,
|
||||
position: [1140, 500],
|
||||
credentials: {
|
||||
postgres: {
|
||||
id: 'RkyZetVpGsSfEAhQ',
|
||||
name: 'Postgres account',
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: ['database "chat11" does not exist'],
|
||||
description: 'Internal error',
|
||||
message: 'Internal error',
|
||||
} as unknown as ExecutionError,
|
||||
metadata: {
|
||||
subRun: [
|
||||
{
|
||||
node: 'Postgres Chat Memory',
|
||||
runIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function setupTestWorkflow(chatTrigger: boolean = false) {
|
||||
// Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model
|
||||
if (chatTrigger) {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
} else {
|
||||
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
||||
}
|
||||
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||
|
||||
if (!chatTrigger) {
|
||||
// Remove chat trigger
|
||||
WorkflowPage.getters
|
||||
.canvasNodeByName(CHAT_TRIGGER_NODE_DISPLAY_NAME)
|
||||
.find('[data-test-id="delete-node-button"]')
|
||||
.click({ force: true });
|
||||
|
||||
// Set manual trigger to output standard pinned data
|
||||
openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
ndv.actions.editPinnedData();
|
||||
ndv.actions.savePinnedData();
|
||||
ndv.actions.close();
|
||||
}
|
||||
|
||||
// Calculator is added just to make OpenAI Chat Model work (tools can not be empty with OpenAI model)
|
||||
addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME);
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addMemoryNodeToParent(AI_MEMORY_POSTGRES_NODE_NAME, AGENT_NODE_NAME);
|
||||
|
||||
clickCreateNewCredential();
|
||||
setCredentialValues({
|
||||
password: 'testtesttest',
|
||||
});
|
||||
|
||||
ndv.getters.parameterInput('sessionIdType').click();
|
||||
getVisibleSelect().contains('Define below').click();
|
||||
ndv.getters.parameterInput('sessionKey').type('asdasd');
|
||||
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
AGENT_NODE_NAME,
|
||||
true,
|
||||
);
|
||||
|
||||
clickCreateNewCredential();
|
||||
setCredentialValues({
|
||||
apiKey: 'sk_test_123',
|
||||
});
|
||||
clickGetBackToCanvas();
|
||||
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
}
|
||||
|
||||
function checkMessages(inputMessage: string, outputMessage: string) {
|
||||
const messages = getManualChatMessages();
|
||||
messages.should('have.length', 2);
|
||||
messages.should('contain', inputMessage);
|
||||
messages.should('contain', outputMessage);
|
||||
|
||||
getManualChatModalLogs().should('exist');
|
||||
getManualChatModalLogsEntries()
|
||||
.should('have.length', 1)
|
||||
.should('contain', AI_MEMORY_POSTGRES_NODE_NAME);
|
||||
}
|
||||
|
||||
describe("AI-233 Make root node's logs pane active in case of an error in sub-nodes", () => {
|
||||
beforeEach(() => {
|
||||
navigateToNewWorkflowPage();
|
||||
});
|
||||
|
||||
it('should open logs tab by default when there was an error', () => {
|
||||
setupTestWorkflow(true);
|
||||
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
const inputMessage = 'Test the code tool';
|
||||
|
||||
clickExecuteNode();
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => sendManualChatMessage(inputMessage),
|
||||
runData: createRunDataWithError(inputMessage),
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
checkMessages(inputMessage, '[ERROR: Internal error]');
|
||||
closeManualChatModal();
|
||||
|
||||
// Open the AI Agent node to see the logs
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
// Finally check that logs pane is opened by default
|
||||
ndv.getters.outputDataContainer().should('be.visible');
|
||||
|
||||
ndv.getters.aiOutputModeToggle().should('be.visible');
|
||||
ndv.getters
|
||||
.aiOutputModeToggle()
|
||||
.find('[role="radio"]')
|
||||
.should('have.length', 2)
|
||||
.eq(1)
|
||||
.should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
.findChildByTestId('node-error-message')
|
||||
.should('be.visible')
|
||||
.should('contain', 'Error in sub-node');
|
||||
});
|
||||
|
||||
it('should switch to logs tab on error, when NDV is already opened', () => {
|
||||
setupTestWorkflow(false);
|
||||
|
||||
openNode(AGENT_NODE_NAME);
|
||||
|
||||
const inputMessage = 'Test the code tool';
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => clickExecuteNode(),
|
||||
runData: createRunDataWithError(inputMessage),
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
// Check that logs pane is opened by default
|
||||
ndv.getters.outputDataContainer().should('be.visible');
|
||||
|
||||
ndv.getters.aiOutputModeToggle().should('be.visible');
|
||||
ndv.getters
|
||||
.aiOutputModeToggle()
|
||||
.find('[role="radio"]')
|
||||
.should('have.length', 2)
|
||||
.eq(1)
|
||||
.should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
.findChildByTestId('node-error-message')
|
||||
.should('be.visible')
|
||||
.should('contain', 'Error in sub-node');
|
||||
});
|
||||
});
|
|
@ -162,6 +162,64 @@ describe('NDV', () => {
|
|||
ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
});
|
||||
|
||||
it('resolves expression with default item when input node is not parent, while still pairing items', () => {
|
||||
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
});
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
workflowPage.actions.openNode('Set2');
|
||||
|
||||
ndv.getters.inputPanel().contains('6 items').should('exist');
|
||||
ndv.getters
|
||||
.outputRunSelector()
|
||||
.find('input')
|
||||
.should('exist')
|
||||
.should('have.value', '2 of 2 (6 items)');
|
||||
|
||||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.switchOutputMode('Table');
|
||||
|
||||
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1111');
|
||||
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
cy.wait(100);
|
||||
ndv.getters.outputHoveringItem().should('not.exist');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||
|
||||
ndv.actions.selectInputNode('Code1');
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.getters.inputTableRow(1).should('have.text', '1000');
|
||||
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
ndv.getters.outputTableRow(1).should('have.text', '1000');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||
|
||||
ndv.actions.selectInputNode('Code');
|
||||
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
cy.wait(100);
|
||||
ndv.getters.inputTableRow(1).should('have.text', '6666');
|
||||
|
||||
ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item');
|
||||
|
||||
ndv.getters.outputHoveringItem().should('not.exist');
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||
|
||||
ndv.actions.selectInputNode('When clicking');
|
||||
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
ndv.getters
|
||||
.inputTableRow(1)
|
||||
.should('have.text', "This is an item, but it's empty.")
|
||||
.realHover();
|
||||
|
||||
ndv.getters.outputHoveringItem().should('have.length', 6);
|
||||
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||
});
|
||||
|
||||
it('can pair items between input and output across branches and runs', () => {
|
||||
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
|
@ -227,7 +285,7 @@ describe('NDV', () => {
|
|||
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
// biome-ignore format:
|
||||
/* prettier-ignore */
|
||||
const PINNED_DATA = [
|
||||
{
|
||||
"id": "abc",
|
||||
|
@ -263,6 +321,7 @@ describe('NDV', () => {
|
|||
]
|
||||
}
|
||||
];
|
||||
/* prettier-ignore */
|
||||
workflowPage.actions.openNode('Get thread details1');
|
||||
ndv.actions.pastePinnedData(PINNED_DATA);
|
||||
ndv.actions.close();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import type { Interception } from 'cypress/types/net-stubbing';
|
||||
import { META_KEY } from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { getPopper } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
|
||||
|
@ -80,6 +82,32 @@ describe('Canvas Actions', () => {
|
|||
workflowPage.getters.stickies().should('have.length', 0);
|
||||
});
|
||||
|
||||
it('change sticky color', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
|
||||
workflowPage.actions.toggleColorPalette();
|
||||
|
||||
getPopper().should('be.visible');
|
||||
|
||||
workflowPage.actions.pickColor();
|
||||
|
||||
workflowPage.actions.toggleColorPalette();
|
||||
|
||||
getPopper().should('not.be.visible');
|
||||
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.wait('@createWorkflow').then((interception: Interception) => {
|
||||
const { request } = interception;
|
||||
const color = request.body?.nodes[0]?.parameters?.color;
|
||||
expect(color).to.equal(2);
|
||||
});
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('edits sticky and updates content as markdown', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
|
||||
|
|
|
@ -37,16 +37,6 @@ describe('Resource Locator', () => {
|
|||
ndv.getters.resourceLocatorErrorMessage().should('contain', NO_CREDENTIALS_MESSAGE);
|
||||
});
|
||||
|
||||
it('should show create credentials modal when clicking "add your credential"', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet');
|
||||
ndv.getters.resourceLocator('documentId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('documentId').click();
|
||||
ndv.getters.resourceLocatorErrorMessage().should('contain', NO_CREDENTIALS_MESSAGE);
|
||||
ndv.getters.resourceLocatorAddCredentials().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
});
|
||||
|
||||
it('should show appropriate error when credentials are not valid', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet');
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import planData from '../fixtures/Plan_data_opt_in_trial.json';
|
||||
import {
|
||||
BannerStack,
|
||||
MainSidebar,
|
||||
|
@ -6,6 +5,7 @@ import {
|
|||
visitPublicApiPage,
|
||||
getPublicApiUpgradeCTA,
|
||||
} from '../pages';
|
||||
import planData from '../fixtures/Plan_data_opt_in_trial.json';
|
||||
|
||||
const mainSidebar = new MainSidebar();
|
||||
const bannerStack = new BannerStack();
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import generateOTPToken from 'cypress-otp';
|
||||
|
||||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
||||
import { INSTANCE_OWNER, INSTANCE_ADMIN, BACKEND_BASE_URL } from '../constants';
|
||||
import { SigninPage } from '../pages';
|
||||
import { MfaLoginPage } from '../pages/mfa-login';
|
||||
import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||
import { MfaLoginPage } from '../pages/mfa-login';
|
||||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
||||
|
||||
const MFA_SECRET = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD';
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('Debug', () => {
|
|||
it('should be able to debug executions', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('postWorkflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('postWorkflowRun');
|
||||
|
||||
cy.signinAsOwner();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
|
||||
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
|
||||
import { MainSidebar } from '../pages/sidebar/main-sidebar';
|
||||
import { TemplatesPage } from '../pages/templates';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
import { WorkflowsPage } from '../pages/workflows';
|
||||
import { MainSidebar } from '../pages/sidebar/main-sidebar';
|
||||
import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
|
||||
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
|
||||
|
||||
const templatesPage = new TemplatesPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
|
|
@ -128,6 +128,11 @@ describe('Editor actions should work', () => {
|
|||
createNewWorkflowAndActivate();
|
||||
});
|
||||
|
||||
it('after saving a new workflow', () => {
|
||||
editWorkflowAndDeactivate();
|
||||
editWorkflowMoreAndActivate();
|
||||
});
|
||||
|
||||
it('after switching between Editor and Executions', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
|
@ -142,7 +147,7 @@ describe('Editor actions should work', () => {
|
|||
it('after switching between Editor and Debug', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('postWorkflowRun');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('postWorkflowRun');
|
||||
|
||||
editWorkflowAndDeactivate();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
|
|
@ -1,3 +1,34 @@
|
|||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
addMemoryNodeToParent,
|
||||
addNodeToCanvas,
|
||||
addOutputParserNodeToParent,
|
||||
addToolNodeToParent,
|
||||
clickExecuteWorkflowButton,
|
||||
clickManualChatButton,
|
||||
disableNode,
|
||||
getExecuteWorkflowButton,
|
||||
navigateToNewWorkflowPage,
|
||||
openNode,
|
||||
} from '../composables/workflow';
|
||||
import {
|
||||
clickCreateNewCredential,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
toggleParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import {
|
||||
closeManualChatModal,
|
||||
getManualChatDialog,
|
||||
getManualChatMessages,
|
||||
getManualChatModal,
|
||||
getManualChatModalLogs,
|
||||
getManualChatModalLogsEntries,
|
||||
getManualChatModalLogsTree,
|
||||
sendManualChatMessage,
|
||||
} from '../composables/modals/chat-modal';
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
MANUAL_CHAT_TRIGGER_NODE_NAME,
|
||||
|
@ -10,41 +41,7 @@ import {
|
|||
AI_TOOL_WIKIPEDIA_NODE_NAME,
|
||||
BASIC_LLM_CHAIN_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
CHAT_TRIGGER_NODE_DISPLAY_NAME,
|
||||
} from './../constants';
|
||||
import {
|
||||
closeManualChatModal,
|
||||
getManualChatDialog,
|
||||
getManualChatMessages,
|
||||
getManualChatModal,
|
||||
getManualChatModalLogs,
|
||||
getManualChatModalLogsEntries,
|
||||
getManualChatModalLogsTree,
|
||||
sendManualChatMessage,
|
||||
} from '../composables/modals/chat-modal';
|
||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import {
|
||||
clickCreateNewCredential,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
toggleParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
addMemoryNodeToParent,
|
||||
addNodeToCanvas,
|
||||
addOutputParserNodeToParent,
|
||||
addToolNodeToParent,
|
||||
clickExecuteWorkflowButton,
|
||||
clickManualChatButton,
|
||||
disableNode,
|
||||
getExecuteWorkflowButton,
|
||||
navigateToNewWorkflowPage,
|
||||
getNodes,
|
||||
openNode,
|
||||
getConnectionBySourceAndTarget,
|
||||
} from '../composables/workflow';
|
||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||
|
||||
describe('Langchain Integration', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -334,27 +331,4 @@ describe('Langchain Integration', () => {
|
|||
|
||||
closeManualChatModal();
|
||||
});
|
||||
|
||||
it('should auto-add chat trigger and basic LLM chain when adding LLM node', () => {
|
||||
addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true);
|
||||
|
||||
getConnectionBySourceAndTarget(
|
||||
CHAT_TRIGGER_NODE_DISPLAY_NAME,
|
||||
BASIC_LLM_CHAIN_NODE_NAME,
|
||||
).should('exist');
|
||||
|
||||
getConnectionBySourceAndTarget(
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
BASIC_LLM_CHAIN_NODE_NAME,
|
||||
).should('exist');
|
||||
getNodes().should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should not auto-add nodes if AI nodes are already present', () => {
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||
|
||||
addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true);
|
||||
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
||||
getNodes().should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
import workflow from '../fixtures/Manual_wait_set.json';
|
||||
import { importWorkflow, visitDemoPage } from '../pages/demo';
|
||||
import { errorToast } from '../pages/notifications';
|
||||
import { importWorkflow, vistDemoPage } from '../pages/demo';
|
||||
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');
|
||||
vistDemoPage();
|
||||
importWorkflow(workflow);
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
});
|
||||
|
||||
it('can override theme to dark', () => {
|
||||
visitDemoPage('dark');
|
||||
vistDemoPage('dark');
|
||||
cy.get('body').should('have.attr', 'data-theme', 'dark');
|
||||
errorToast().should('not.exist');
|
||||
});
|
||||
|
||||
it('can override theme to light', () => {
|
||||
visitDemoPage('light');
|
||||
vistDemoPage('light');
|
||||
cy.get('body').should('have.attr', 'data-theme', 'light');
|
||||
errorToast().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,14 +35,13 @@ describe('Personal Settings', () => {
|
|||
successToast().find('.el-notification__closeBtn').click();
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
it('not allow malicious values for personal data', () => {
|
||||
cy.visit('/settings/personal');
|
||||
INVALID_NAMES.forEach((name) => {
|
||||
cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name);
|
||||
cy.getByTestId('personal-data-form').find('input[name="lastName"]').clear().type(name);
|
||||
cy.getByTestId('save-settings-button').click();
|
||||
errorToast().should('contain', 'Potentially malicious string');
|
||||
errorToast().should('contain', 'Malicious firstName | Malicious lastName');
|
||||
errorToast().find('.el-notification__closeBtn').click();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
import * as setupCredsModal from '../composables/modals/workflow-credential-setup-modal';
|
||||
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 {
|
||||
clickUseWorkflowButtonByTitle,
|
||||
visitTemplateCollectionPage,
|
||||
|
@ -10,6 +5,11 @@ import {
|
|||
} from '../pages/template-collection';
|
||||
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
import * as formStep from '../composables/setup-template-form-step';
|
||||
import { getSetupWorkflowCredentialsButton } from '../composables/setup-workflow-credentials-button';
|
||||
import * as setupCredsModal from '../composables/modals/workflow-credential-setup-modal';
|
||||
import TestTemplate1 from '../fixtures/Test_Template_1.json';
|
||||
import TestTemplate2 from '../fixtures/Test_Template_2.json';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { WorkflowsPage } from '../pages/workflows';
|
||||
import {
|
||||
closeVersionUpdatesPanel,
|
||||
getVersionCard,
|
||||
getVersionUpdatesPanelOpenButton,
|
||||
openVersionUpdatesPanel,
|
||||
} from '../composables/versions';
|
||||
import { WorkflowsPage } from '../pages/workflows';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import * as projects from '../composables/projects';
|
||||
import {
|
||||
INSTANCE_ADMIN,
|
||||
INSTANCE_MEMBERS,
|
||||
INSTANCE_OWNER,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
|
@ -13,9 +11,9 @@ import {
|
|||
CredentialsPage,
|
||||
WorkflowExecutionsTab,
|
||||
NDV,
|
||||
MainSidebar,
|
||||
} from '../pages';
|
||||
import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';
|
||||
import * as projects from '../composables/projects';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
@ -23,7 +21,6 @@ const credentialsPage = new CredentialsPage();
|
|||
const credentialsModal = new CredentialsModal();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
const ndv = new NDV();
|
||||
const mainSidebar = new MainSidebar();
|
||||
|
||||
describe('Projects', { disableAutoLogin: true }, () => {
|
||||
before(() => {
|
||||
|
@ -240,30 +237,10 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
cy.signinAsMember(1);
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.getByTestId('add-project-menu-item').should('not.exist');
|
||||
projects.getAddProjectButton().should('not.exist');
|
||||
projects.getMenuItems().should('not.exist');
|
||||
});
|
||||
|
||||
it('should not show viewer role if not licensed', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabSettings().click();
|
||||
|
||||
cy.get(
|
||||
`[data-test-id="user-list-item-${INSTANCE_MEMBERS[0].email}"] [data-test-id="projects-settings-user-role-select"]`,
|
||||
).click();
|
||||
|
||||
cy.get('.el-select-dropdown__item.is-disabled')
|
||||
.should('contain.text', 'Viewer')
|
||||
.get('span:contains("Upgrade")')
|
||||
.filter(':visible')
|
||||
.click();
|
||||
|
||||
getVisibleModalOverlay().should('contain.text', 'Upgrade to unlock additional roles');
|
||||
});
|
||||
|
||||
describe('when starting from scratch', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetDatabase();
|
||||
|
@ -280,7 +257,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
|
||||
// Create a project and add a credential to it
|
||||
cy.intercept('POST', '/rest/projects').as('projectCreate');
|
||||
projects.getAddProjectButton().click();
|
||||
projects.getAddProjectButton().should('contain', 'Add project').should('be.visible').click();
|
||||
cy.wait('@projectCreate');
|
||||
projects.getMenuItems().should('have.length', 1);
|
||||
projects.getMenuItems().first().click();
|
||||
|
@ -441,7 +418,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should move resources between projects', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
// Create a workflow and a credential in the Home project
|
||||
|
@ -487,15 +464,44 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.find('button:contains("Next")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(':contains("Project 1")')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Project 1')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Next")').click();
|
||||
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Confirm")')
|
||||
.should('be.disabled');
|
||||
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('input[type="checkbox"]')
|
||||
.first()
|
||||
.parents('label')
|
||||
.click();
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('button:contains("Confirm")')
|
||||
.should('be.disabled');
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('input[type="checkbox"]')
|
||||
.last()
|
||||
.parents('label')
|
||||
.click();
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('button:contains("Confirm")')
|
||||
.should('not.be.disabled')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
|
@ -503,77 +509,9 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
.filter(':contains("Owned by me")')
|
||||
.should('not.exist');
|
||||
|
||||
// Move the workflow from Project 1 to Project 2
|
||||
projects.getMenuItems().first().click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||
workflowsPage.getters.workflowMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(':contains("Project 2")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
|
||||
// Move the workflow from Project 2 to a member user
|
||||
projects.getMenuItems().last().click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||
workflowsPage.getters.workflowMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_MEMBERS[0].email}")`)
|
||||
.click();
|
||||
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
|
||||
// Move the workflow from member user back to Home
|
||||
projects.getHomeButton().click();
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
.should('have.length', 3)
|
||||
.filter(':has(.n8n-badge:contains("Project"))')
|
||||
.should('have.length', 2);
|
||||
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
|
||||
workflowsPage.getters.workflowMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
||||
.click();
|
||||
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
.should('have.length', 3)
|
||||
.filter(':contains("Owned by me")')
|
||||
.should('have.length', 1);
|
||||
|
||||
// Move the credential from Project 1 to Project 2
|
||||
projects.getMenuItems().first().click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
||||
|
@ -582,237 +520,48 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.find('button:contains("Next")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(':contains("Project 2")')
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Project 2')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
projects.getResourceMoveModal().find('button:contains("Next")').click();
|
||||
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Confirm")')
|
||||
.should('be.disabled');
|
||||
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('input[type="checkbox"]')
|
||||
.first()
|
||||
.parents('label')
|
||||
.click();
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('button:contains("Confirm")')
|
||||
.should('be.disabled');
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('input[type="checkbox"]')
|
||||
.last()
|
||||
.parents('label')
|
||||
.click();
|
||||
projects
|
||||
.getResourceMoveConfirmModal()
|
||||
.find('button:contains("Confirm")')
|
||||
.should('not.be.disabled')
|
||||
.click();
|
||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||
|
||||
// Move the credential from Project 2 to admin user
|
||||
projects.getMenuItems().last().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 2);
|
||||
|
||||
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
||||
credentialsPage.getters.credentialMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_ADMIN.email}")`)
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
|
||||
// Move the credential from admin user back to instance owner
|
||||
projects.getHomeButton().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 3);
|
||||
|
||||
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
||||
credentialsPage.getters.credentialMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(`:contains("${INSTANCE_OWNER.email}")`)
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
|
||||
credentialsPage.getters
|
||||
.credentialCards()
|
||||
.should('have.length', 3)
|
||||
.filter(':contains("Owned by me")')
|
||||
.should('have.length', 2);
|
||||
|
||||
// Move the credential from admin user back to its original project (Project 1)
|
||||
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
|
||||
credentialsPage.getters.credentialMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move credential")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 5)
|
||||
.filter(':contains("Project 1")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move credential")').click();
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters
|
||||
.credentialCards()
|
||||
.filter(':contains("Credential in Project 1")')
|
||||
.should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should allow to change inaccessible credential when the workflow was moved to a team project', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
// Create a credential in the Home project
|
||||
projects.getProjectTabCredentials().should('be.visible').click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
projects.createCredential('Credential in Home project');
|
||||
|
||||
// Create a workflow in the Home project
|
||||
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();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
// Create a project and add a user to it
|
||||
projects.createProject('Project 1');
|
||||
projects.addProjectMember(INSTANCE_MEMBERS[0].email);
|
||||
projects.getProjectSettingsSaveButton().click();
|
||||
|
||||
// Move the workflow from Home to Project 1
|
||||
projects.getHomeButton().click();
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
.should('have.length', 1)
|
||||
.filter(':contains("Owned by me")')
|
||||
.should('exist');
|
||||
workflowsPage.getters.workflowCardActions('My workflow').click();
|
||||
workflowsPage.getters.workflowMoveButton().click();
|
||||
|
||||
projects
|
||||
.getResourceMoveModal()
|
||||
.should('be.visible')
|
||||
.find('button:contains("Move workflow")')
|
||||
.should('be.disabled');
|
||||
projects.getProjectMoveSelect().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 4)
|
||||
.filter(':contains("Project 1")')
|
||||
.click();
|
||||
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
|
||||
|
||||
workflowsPage.getters
|
||||
.workflowCards()
|
||||
.should('have.length', 1)
|
||||
.filter(':contains("Owned by me")')
|
||||
.should('not.exist');
|
||||
|
||||
//Log out with instance owner and log in with the member user
|
||||
mainSidebar.actions.openUserMenu();
|
||||
cy.getByTestId('user-menu-item-logout').click();
|
||||
|
||||
cy.get('input[name="email"]').type(INSTANCE_MEMBERS[0].email);
|
||||
cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password);
|
||||
cy.getByTestId('form-submit-button').click();
|
||||
|
||||
// Open the moved workflow
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
workflowsPage.getters.workflowCards().first().click();
|
||||
|
||||
// Check if the credential can be changed
|
||||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
ndv.getters.credentialInput().find('input').should('be.enabled');
|
||||
});
|
||||
|
||||
it('should handle viewer role', () => {
|
||||
cy.enableFeature('projectRole:viewer');
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
projects.createProject('Development');
|
||||
projects.addProjectMember(INSTANCE_MEMBERS[0].email, 'Viewer');
|
||||
projects.getProjectSettingsSaveButton().click();
|
||||
|
||||
projects.getProjectTabWorkflows().click();
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
projects.createWorkflow('Test_workflow_4_executions_view.json', 'WF with random error');
|
||||
executionsTab.actions.createManualExecutions(2);
|
||||
executionsTab.actions.toggleNodeEnabled('Error');
|
||||
executionsTab.actions.createManualExecutions(2);
|
||||
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
projects.createCredential('Notion API');
|
||||
|
||||
mainSidebar.actions.openUserMenu();
|
||||
cy.getByTestId('user-menu-item-logout').click();
|
||||
|
||||
cy.get('input[name="email"]').type(INSTANCE_MEMBERS[0].email);
|
||||
cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password);
|
||||
cy.getByTestId('form-submit-button').click();
|
||||
|
||||
mainSidebar.getters.executions().click();
|
||||
cy.getByTestId('global-execution-list-item').first().find('td:last button').click();
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.filter(':contains("Retry")')
|
||||
.should('have.class', 'is-disabled');
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.filter(':contains("Delete")')
|
||||
.should('have.class', 'is-disabled');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
cy.getByTestId('workflow-card-name').should('be.visible').first().click();
|
||||
workflowPage.getters.nodeViewRoot().should('be.visible');
|
||||
workflowPage.getters.executeWorkflowButton().should('not.exist');
|
||||
workflowPage.getters.nodeCreatorPlusButton().should('not.exist');
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3).last().click();
|
||||
cy.get('body').type('{backspace}');
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3).last().rightclick();
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.should('be.visible')
|
||||
.filter(
|
||||
':contains("Open"), :contains("Copy"), :contains("Select all"), :contains("Clear selection")',
|
||||
)
|
||||
.should('not.have.class', 'is-disabled');
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.getByTestId('retry-execution-button')
|
||||
.should('be.visible')
|
||||
.find('.is-disabled')
|
||||
.should('exist');
|
||||
cy.get('button:contains("Debug")').should('be.disabled');
|
||||
cy.get('button[title="Retry execution"]').should('be.disabled');
|
||||
cy.get('button[title="Delete this execution"]').should('be.disabled');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().filter(':contains("Notion")').click();
|
||||
cy.getByTestId('node-credentials-config-container')
|
||||
.should('be.visible')
|
||||
.find('input')
|
||||
.should('not.have.length');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { IF_NODE_NAME } from '../constants';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { IF_NODE_NAME } from '../constants';
|
||||
|
||||
const nodeCreatorFeature = new NodeCreator();
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { getSaveChangesModal } from '../composables/modals/save-changes-modal';
|
||||
import { EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||
|
||||
const WorkflowsPage = new WorkflowsPageClass();
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('Workflows', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(WorkflowsPage.url);
|
||||
});
|
||||
|
||||
it('should ask to save unsaved changes before leaving route', () => {
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
|
||||
cy.getByTestId('project-home-menu-item').click();
|
||||
|
||||
getSaveChangesModal().should('be.visible');
|
||||
});
|
||||
});
|
|
@ -1,495 +0,0 @@
|
|||
import { type ICredentialType } from 'n8n-workflow';
|
||||
|
||||
import { clickCreateNewCredential, openCredentialSelect } from '../composables/ndv';
|
||||
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';
|
||||
|
||||
const wf = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
const aiAssistant = new AIAssistant();
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
||||
describe('AI Assistant::disabled', () => {
|
||||
beforeEach(() => {
|
||||
aiAssistant.actions.disableAssistant();
|
||||
wf.actions.visit();
|
||||
});
|
||||
|
||||
it('does not show assistant button if feature is disabled', () => {
|
||||
aiAssistant.getters.askAssistantFloatingButton().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AI Assistant::enabled', () => {
|
||||
beforeEach(() => {
|
||||
aiAssistant.actions.enableAssistant();
|
||||
wf.actions.visit();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
aiAssistant.actions.disableAssistant();
|
||||
});
|
||||
|
||||
it('renders placeholder UI', () => {
|
||||
aiAssistant.getters.askAssistantFloatingButton().should('be.visible');
|
||||
aiAssistant.getters.askAssistantFloatingButton().click();
|
||||
aiAssistant.getters.askAssistantChat().should('be.visible');
|
||||
aiAssistant.getters.placeholderMessage().should('be.visible');
|
||||
aiAssistant.getters.chatInput().should('be.visible');
|
||||
aiAssistant.getters.sendMessageButton().should('be.disabled');
|
||||
aiAssistant.getters.closeChatButton().should('be.visible');
|
||||
aiAssistant.getters.closeChatButton().click();
|
||||
aiAssistant.getters.askAssistantChat().should('not.be.visible');
|
||||
});
|
||||
|
||||
it('should resize assistant chat up', () => {
|
||||
aiAssistant.getters.askAssistantFloatingButton().click();
|
||||
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
|
||||
aiAssistant.getters.askAssistantChat().then((element) => {
|
||||
const { width, left } = element[0].getBoundingClientRect();
|
||||
cy.drag(aiAssistant.getters.askAssistantSidebarResizer(), [left - 10, 0], {
|
||||
abs: true,
|
||||
clickToFinish: true,
|
||||
});
|
||||
aiAssistant.getters.askAssistantChat().then((newElement) => {
|
||||
const newWidth = newElement[0].getBoundingClientRect().width;
|
||||
expect(newWidth).to.be.greaterThan(width);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should resize assistant chat down', () => {
|
||||
aiAssistant.getters.askAssistantFloatingButton().click();
|
||||
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
|
||||
aiAssistant.getters.askAssistantChat().then((element) => {
|
||||
const { width, left } = element[0].getBoundingClientRect();
|
||||
cy.drag(aiAssistant.getters.askAssistantSidebarResizer(), [left + 10, 0], {
|
||||
abs: true,
|
||||
clickToFinish: true,
|
||||
});
|
||||
aiAssistant.getters.askAssistantChat().then((newElement) => {
|
||||
const newWidth = newElement[0].getBoundingClientRect().width;
|
||||
expect(newWidth).to.be.lessThan(width);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should start chat session from node error view', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Stop and Error');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesAll().should('have.length', 1);
|
||||
aiAssistant.getters
|
||||
.chatMessagesAll()
|
||||
.eq(0)
|
||||
.should('contain.text', 'Hey, this is an assistant message');
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().should('be.disabled');
|
||||
});
|
||||
|
||||
it('should render chat input correctly', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Stop and Error');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
// Send button should be disabled when input is empty
|
||||
aiAssistant.getters.sendMessageButton().should('be.disabled');
|
||||
aiAssistant.getters.chatInput().type('Yo ');
|
||||
aiAssistant.getters.sendMessageButton().should('not.be.disabled');
|
||||
aiAssistant.getters.chatInput().then((element) => {
|
||||
const { height } = element[0].getBoundingClientRect();
|
||||
// Shift + Enter should add a new line
|
||||
aiAssistant.getters.chatInput().type('Hello{shift+enter}there');
|
||||
aiAssistant.getters.chatInput().then((newElement) => {
|
||||
const newHeight = newElement[0].getBoundingClientRect().height;
|
||||
// Chat input should grow as user adds new lines
|
||||
expect(newHeight).to.be.greaterThan(height);
|
||||
aiAssistant.getters.sendMessageButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
// New lines should be rendered as <br> in the chat
|
||||
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
||||
aiAssistant.getters.chatMessagesUser().eq(0).find('br').should('have.length', 1);
|
||||
// Chat input should be cleared now
|
||||
aiAssistant.getters.chatInput().should('have.value', '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should render and handle quick replies', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/quick_reply_message_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Stop and Error');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.quickReplyButtons().should('have.length', 2);
|
||||
aiAssistant.getters.quickReplyButtons().eq(0).click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
||||
aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', "Sure, let's do it");
|
||||
});
|
||||
|
||||
it('should show quick replies when node is executed after new suggestion', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', (req) => {
|
||||
req.reply((res) => {
|
||||
if (['init-error-helper', 'message'].includes(req.body.payload.type)) {
|
||||
res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' });
|
||||
} else if (req.body.payload.type === 'event') {
|
||||
res.send({ statusCode: 200, fixture: 'aiAssistant/node_execution_error_response.json' });
|
||||
} else {
|
||||
res.send({ statusCode: 500 });
|
||||
}
|
||||
});
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Edit Fields');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesAssistant().should('have.length', 1);
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
// Respond 'Yes' to the quick reply (request new suggestion)
|
||||
aiAssistant.getters.quickReplies().contains('Yes').click();
|
||||
cy.wait('@chatRequest');
|
||||
// No quick replies at this point
|
||||
aiAssistant.getters.quickReplies().should('not.exist');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
// But after executing the node again, quick replies should be shown
|
||||
aiAssistant.getters.chatMessagesAssistant().should('have.length', 4);
|
||||
aiAssistant.getters.quickReplies().should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should warn before starting a new session', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Edit Fields');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.closeChatButton().click();
|
||||
ndv.getters.backToCanvas().click();
|
||||
wf.actions.openNode('Stop and Error');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
||||
// Since we already have an active session, a warning should be shown
|
||||
aiAssistant.getters.newAssistantSessionModal().should('be.visible');
|
||||
aiAssistant.getters
|
||||
.newAssistantSessionModal()
|
||||
.find('button')
|
||||
.contains('Start new session')
|
||||
.click();
|
||||
cy.wait('@chatRequest');
|
||||
// New session should start with initial assistant message
|
||||
aiAssistant.getters.chatMessagesAll().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should apply code diff to code node', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/code_diff_suggestion_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat/apply-suggestion', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/apply_code_diff_response.json',
|
||||
}).as('applySuggestion');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Code');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
||||
cy.wait('@chatRequest');
|
||||
// Should have two assistant messages
|
||||
aiAssistant.getters.chatMessagesAll().should('have.length', 2);
|
||||
aiAssistant.getters.codeDiffs().should('have.length', 1);
|
||||
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
||||
aiAssistant.getters.applyCodeDiffButtons().first().click();
|
||||
cy.wait('@applySuggestion');
|
||||
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 0);
|
||||
aiAssistant.getters.undoReplaceCodeButtons().should('have.length', 1);
|
||||
aiAssistant.getters.codeReplacedMessage().should('be.visible');
|
||||
ndv.getters
|
||||
.parameterInput('jsCode')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', 'item.json.myNewField = 1');
|
||||
// Clicking undo should revert the code back but not call the assistant
|
||||
aiAssistant.getters.undoReplaceCodeButtons().first().click();
|
||||
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
||||
aiAssistant.getters.codeReplacedMessage().should('not.exist');
|
||||
cy.get('@applySuggestion.all').then((interceptions) => {
|
||||
expect(interceptions).to.have.length(1);
|
||||
});
|
||||
ndv.getters
|
||||
.parameterInput('jsCode')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', 'item.json.myNewField = 1aaa');
|
||||
// Replacing the code again should also not call the assistant
|
||||
cy.get('@applySuggestion.all').then((interceptions) => {
|
||||
expect(interceptions).to.have.length(1);
|
||||
});
|
||||
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
||||
aiAssistant.getters.applyCodeDiffButtons().first().click();
|
||||
ndv.getters
|
||||
.parameterInput('jsCode')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', 'item.json.myNewField = 1');
|
||||
});
|
||||
|
||||
it('should end chat session when `end_session` event is received', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/end_session_response.json',
|
||||
}).as('chatRequest');
|
||||
cy.createFixtureWorkflow('aiAssistant/test_workflow.json');
|
||||
wf.actions.openNode('Stop and Error');
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesSystem().should('have.length', 1);
|
||||
aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended');
|
||||
});
|
||||
|
||||
it('should reset session after it ended and sidebar is closed', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', (req) => {
|
||||
req.reply((res) => {
|
||||
if (['init-support-chat'].includes(req.body.payload.type)) {
|
||||
res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' });
|
||||
} else {
|
||||
res.send({ statusCode: 200, fixture: 'aiAssistant/end_session_response.json' });
|
||||
}
|
||||
});
|
||||
}).as('chatRequest');
|
||||
aiAssistant.actions.openChat();
|
||||
aiAssistant.actions.sendMessage('Hello');
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.actions.closeChat();
|
||||
aiAssistant.actions.openChat();
|
||||
// After closing and reopening the chat, all messages should be still there
|
||||
aiAssistant.getters.chatMessagesAll().should('have.length', 2);
|
||||
// End the session
|
||||
aiAssistant.actions.sendMessage('Thanks, bye');
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesSystem().should('have.length', 1);
|
||||
aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended');
|
||||
aiAssistant.actions.closeChat();
|
||||
aiAssistant.actions.openChat();
|
||||
// Now, session should be reset
|
||||
aiAssistant.getters.placeholderMessage().should('be.visible');
|
||||
});
|
||||
|
||||
it('Should not reset assistant session when workflow is saved', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
aiAssistant.actions.openChat();
|
||||
aiAssistant.actions.sendMessage('Hello');
|
||||
wf.actions.openNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
ndv.getters.nodeExecuteButton().click();
|
||||
wf.getters.isWorkflowSaved();
|
||||
aiAssistant.getters.placeholderMessage().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AI Assistant Credential Help', () => {
|
||||
beforeEach(() => {
|
||||
aiAssistant.actions.enableAssistant();
|
||||
wf.actions.visit();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
aiAssistant.actions.disableAssistant();
|
||||
});
|
||||
|
||||
it('should start credential help from node credential', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
wf.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
wf.actions.addNodeToCanvas(GMAIL_NODE_NAME);
|
||||
wf.actions.openNode('Gmail');
|
||||
openCredentialSelect();
|
||||
clickCreateNewCredential();
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.visible');
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
||||
aiAssistant.getters
|
||||
.chatMessagesUser()
|
||||
.eq(0)
|
||||
.should('contain.text', 'How do I set up the credentials for Gmail OAuth2 API?');
|
||||
|
||||
aiAssistant.getters
|
||||
.chatMessagesAssistant()
|
||||
.eq(0)
|
||||
.should('contain.text', 'Hey, this is an assistant message');
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.disabled');
|
||||
});
|
||||
|
||||
it('should start credential help from credential list', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/simple_message_response.json',
|
||||
}).as('chatRequest');
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
|
||||
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.visible');
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').click();
|
||||
cy.wait('@chatRequest');
|
||||
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
||||
aiAssistant.getters
|
||||
.chatMessagesUser()
|
||||
.eq(0)
|
||||
.should('contain.text', 'How do I set up the credentials for Notion API?');
|
||||
|
||||
aiAssistant.getters
|
||||
.chatMessagesAssistant()
|
||||
.eq(0)
|
||||
.should('contain.text', 'Hey, this is an assistant message');
|
||||
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.disabled');
|
||||
});
|
||||
|
||||
it('should not show assistant button when click to connect', () => {
|
||||
cy.intercept('/types/credentials.json', { middleware: true }, (req) => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
||||
req.on('response', (res) => {
|
||||
const credentials: ICredentialType[] = res.body || [];
|
||||
|
||||
const index = credentials.findIndex((c) => c.name === 'slackOAuth2Api');
|
||||
|
||||
credentials[index] = {
|
||||
...credentials[index],
|
||||
__overwrittenProperties: ['clientId', 'clientSecret'],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
wf.actions.visit(true);
|
||||
wf.actions.addNodeToCanvas('Manual');
|
||||
wf.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
credentialsModal.getters.credentialInputs().should('have.length', 0);
|
||||
aiAssistant.getters.credentialEditAssistantButton().should('not.exist');
|
||||
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().eq(1).click();
|
||||
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
||||
aiAssistant.getters.credentialEditAssistantButton().should('exist');
|
||||
});
|
||||
|
||||
it('should not show assistant button when click to connect with some fields', () => {
|
||||
cy.intercept('/types/credentials.json', { middleware: true }, (req) => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
||||
req.on('response', (res) => {
|
||||
const credentials: ICredentialType[] = res.body || [];
|
||||
|
||||
const index = credentials.findIndex((c) => c.name === 'microsoftOutlookOAuth2Api');
|
||||
|
||||
credentials[index] = {
|
||||
...credentials[index],
|
||||
__overwrittenProperties: ['authUrl', 'accessTokenUrl', 'clientId', 'clientSecret'],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
wf.actions.visit(true);
|
||||
wf.actions.addNodeToCanvas('Manual');
|
||||
wf.actions.addNodeToCanvas('Microsoft Outlook', true, true, 'Get a calendar');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
||||
aiAssistant.getters.credentialEditAssistantButton().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('General help', () => {
|
||||
beforeEach(() => {
|
||||
aiAssistant.actions.enableAssistant();
|
||||
wf.actions.visit();
|
||||
});
|
||||
|
||||
it('assistant returns code snippet', () => {
|
||||
cy.intercept('POST', '/rest/ai-assistant/chat', {
|
||||
statusCode: 200,
|
||||
fixture: 'aiAssistant/code_snippet_response.json',
|
||||
}).as('chatRequest');
|
||||
|
||||
aiAssistant.getters.askAssistantFloatingButton().should('be.visible');
|
||||
aiAssistant.getters.askAssistantFloatingButton().click();
|
||||
aiAssistant.getters.askAssistantChat().should('be.visible');
|
||||
aiAssistant.getters.placeholderMessage().should('be.visible');
|
||||
aiAssistant.getters.chatInput().should('be.visible');
|
||||
|
||||
aiAssistant.getters.chatInput().type('Show me an expression');
|
||||
aiAssistant.getters.sendMessageButton().click();
|
||||
|
||||
aiAssistant.getters.chatMessagesAll().should('have.length', 3);
|
||||
aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', 'Show me an expression');
|
||||
|
||||
aiAssistant.getters
|
||||
.chatMessagesAssistant()
|
||||
.eq(0)
|
||||
.should('contain.text', 'To use expressions in n8n, follow these steps:');
|
||||
|
||||
aiAssistant.getters
|
||||
.chatMessagesAssistant()
|
||||
.eq(0)
|
||||
.should(
|
||||
'include.html',
|
||||
`<pre><code class="language-json">[
|
||||
{
|
||||
"headers": {
|
||||
"host": "n8n.instance.address",
|
||||
...
|
||||
},
|
||||
"params": {},
|
||||
"query": {},
|
||||
"body": {
|
||||
"name": "Jim",
|
||||
"age": 30,
|
||||
"city": "New York"
|
||||
}
|
||||
}
|
||||
]
|
||||
</code></pre>`,
|
||||
);
|
||||
aiAssistant.getters.codeSnippet().should('have.text', '{{$json.body.city}}');
|
||||
});
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
import { EXECUTE_WORKFLOW_NODE_NAME } from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass, NDV } from '../pages';
|
||||
import { getVisiblePopper } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('Workflow Selector Parameter', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetDatabase();
|
||||
cy.signinAsOwner();
|
||||
['Get_Weather', 'Search_DB'].forEach((workflowName) => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow(`Test_Subworkflow_${workflowName}.json`, workflowName);
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
});
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addInitialNodeToCanvas(EXECUTE_WORKFLOW_NODE_NAME, {
|
||||
keepNdvOpen: true,
|
||||
action: 'Call Another Workflow',
|
||||
});
|
||||
});
|
||||
it('should render sub-workflows list', () => {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
.should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should show required parameter warning', () => {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
ndv.getters.parameterInputIssues('workflowId').should('exist');
|
||||
});
|
||||
|
||||
it('should filter sub-workflows list', () => {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
ndv.getters.resourceLocatorSearch('workflowId').type('Weather');
|
||||
|
||||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
.should('have.length', 1)
|
||||
.click();
|
||||
|
||||
ndv.getters
|
||||
.resourceLocatorInput('workflowId')
|
||||
.find('input')
|
||||
.should('have.value', 'Get_Weather');
|
||||
});
|
||||
|
||||
it('should render sub-workflow links correctly', () => {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').first().click();
|
||||
|
||||
ndv.getters.resourceLocatorInput('workflowId').find('a').should('exist');
|
||||
cy.getByTestId('radio-button-expression').eq(1).click();
|
||||
ndv.getters.resourceLocatorInput('workflowId').find('a').should('not.exist');
|
||||
});
|
||||
|
||||
it('should switch to ID mode on expression', () => {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').first().click();
|
||||
ndv.getters
|
||||
.resourceLocatorModeSelector('workflowId')
|
||||
.find('input')
|
||||
.should('have.value', 'From list');
|
||||
cy.getByTestId('radio-button-expression').eq(1).click();
|
||||
ndv.getters
|
||||
.resourceLocatorModeSelector('workflowId')
|
||||
.find('input')
|
||||
.should('have.value', 'By ID');
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
import { WorkflowsPage } from '../pages';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
describe('n8n.io iframe', () => {
|
||||
describe('when telemetry is disabled', () => {
|
||||
it('should not load the iframe when visiting /home/workflows', () => {
|
||||
cy.overrideSettings({ telemetry: { enabled: false } });
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.get('iframe').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when telemetry is enabled', () => {
|
||||
it('should load the iframe when visiting /home/workflows', () => {
|
||||
const testInstanceId = 'test-instance-id';
|
||||
|
||||
cy.overrideSettings({ telemetry: { enabled: true }, instanceId: testInstanceId });
|
||||
|
||||
const testUserId = Cypress.env('currentUserId');
|
||||
|
||||
const iframeUrl = `https://n8n.io/self-install?instanceId=${testInstanceId}&userId=${testUserId}`;
|
||||
|
||||
cy.intercept(iframeUrl, (req) => req.reply(200)).as('iframeRequest');
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
cy.get('iframe').should('exist').and('have.attr', 'src', iframeUrl);
|
||||
|
||||
cy.wait('@iframeRequest').its('response.statusCode').should('eq', 200);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,8 +1,10 @@
|
|||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import { clickCreateNewCredential } from '../composables/ndv';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, NOTION_NODE_NAME } from '../constants';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
import { clickCreateNewCredential } from '../composables/ndv';
|
||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||
import { successToast } from '../pages/notifications';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
@ -132,10 +134,6 @@ describe('NDV', () => {
|
|||
'contains.text',
|
||||
"An expression here won't work because it uses .item and n8n can't figure out the matching item.",
|
||||
);
|
||||
ndv.getters.nodeRunErrorIndicator().should('be.visible');
|
||||
// The error details should be hidden behind a tooltip
|
||||
ndv.getters.nodeRunErrorIndicator().should('not.contain', 'Start Time');
|
||||
ndv.getters.nodeRunErrorIndicator().should('not.contain', 'Execution Time');
|
||||
});
|
||||
|
||||
it('should save workflow using keyboard shortcut from NDV', () => {
|
||||
|
@ -203,7 +201,7 @@ describe('NDV', () => {
|
|||
.contains(key)
|
||||
.should('be.visible');
|
||||
});
|
||||
getObjectValueItem().find('label').click({ force: true });
|
||||
getObjectValueItem().find('label').click();
|
||||
expandedObjectProps.forEach((key) => {
|
||||
ndv.getters
|
||||
.outputPanel()
|
||||
|
@ -341,6 +339,38 @@ describe('NDV', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should not retrieve remote options when required params throw errors', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId' });
|
||||
|
||||
ndv.getters.inputPanel().click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
|
||||
ndv.getters.parameterInputIssues('remoteOptions').realHover({ scrollBehavior: false });
|
||||
// Remote options dropdown should not be visible
|
||||
ndv.getters.parameterInput('remoteOptions').find('.el-select').should('not.exist');
|
||||
});
|
||||
|
||||
it('should retrieve remote options when non-required params throw errors', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'otherField' });
|
||||
|
||||
ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should flag issues as soon as params are set', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Webhook');
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
|
@ -586,13 +616,7 @@ describe('NDV', () => {
|
|||
ndv.getters.outputTableRow(1).find('mark').should('have.text', '<lib');
|
||||
|
||||
ndv.getters.outputDisplayMode().find('label').eq(1).should('include.text', 'JSON');
|
||||
ndv.getters
|
||||
.outputDisplayMode()
|
||||
.find('label')
|
||||
.eq(1)
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
ndv.getters.outputDisplayMode().find('label').eq(1).click();
|
||||
|
||||
ndv.getters.outputDataContainer().find('.json-data').should('exist');
|
||||
ndv.getters
|
||||
|
@ -607,7 +631,8 @@ describe('NDV', () => {
|
|||
ndv.getters.outputDisplayMode().find('label').eq(2).click({ force: true });
|
||||
ndv.getters
|
||||
.outputDataContainer()
|
||||
.findChildByTestId('run-data-schema-item-value')
|
||||
.findChildByTestId('run-data-schema-item')
|
||||
.find('> span')
|
||||
.should('include.text', '<?xml version="1.0" encoding="UTF-8"?>');
|
||||
});
|
||||
|
||||
|
@ -674,23 +699,6 @@ describe('NDV', () => {
|
|||
ndv.getters.parameterInput('operation').find('input').should('have.value', 'Delete');
|
||||
});
|
||||
|
||||
it('Should show a notice when remote options cannot be fetched because of missing credentials', () => {
|
||||
cy.intercept('POST', '/rest/dynamic-node-parameters/options', { statusCode: 403 }).as(
|
||||
'parameterOptions',
|
||||
);
|
||||
|
||||
workflowPage.actions.addInitialNodeToCanvas(NOTION_NODE_NAME, {
|
||||
keepNdvOpen: true,
|
||||
action: 'Update a database page',
|
||||
});
|
||||
|
||||
ndv.actions.addItemToFixedCollection('propertiesUi');
|
||||
ndv.getters
|
||||
.parameterInput('key')
|
||||
.find('input')
|
||||
.should('have.value', 'Set up credential to see options');
|
||||
});
|
||||
|
||||
it('Should show error state when remote options cannot be fetched', () => {
|
||||
cy.intercept('POST', '/rest/dynamic-node-parameters/options', { statusCode: 500 }).as(
|
||||
'parameterOptions',
|
||||
|
@ -701,11 +709,6 @@ describe('NDV', () => {
|
|||
action: 'Update a database page',
|
||||
});
|
||||
|
||||
clickCreateNewCredential();
|
||||
setCredentialValues({
|
||||
apiKey: 'sk_test_123',
|
||||
});
|
||||
|
||||
ndv.actions.addItemToFixedCollection('propertiesUi');
|
||||
ndv.getters
|
||||
.parameterInput('key')
|
||||
|
@ -736,6 +739,23 @@ describe('NDV', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Stop listening for trigger event from NDV', () => {
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('workflowRun');
|
||||
workflowPage.actions.addInitialNodeToCanvas('Local File Trigger', {
|
||||
keepNdvOpen: true,
|
||||
action: 'On Changes To A Specific File',
|
||||
isTrigger: true,
|
||||
});
|
||||
ndv.getters.triggerPanelExecuteButton().should('exist');
|
||||
ndv.getters.triggerPanelExecuteButton().realClick();
|
||||
ndv.getters.triggerPanelExecuteButton().should('contain', 'Stop Listening');
|
||||
ndv.getters.triggerPanelExecuteButton().realClick();
|
||||
cy.wait('@workflowRun').then(() => {
|
||||
ndv.getters.triggerPanelExecuteButton().should('contain', 'Test step');
|
||||
successToast().should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow selecting item for expressions', () => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
|
@ -762,33 +782,4 @@ describe('NDV', () => {
|
|||
ndv.actions.expressionSelectItem(1);
|
||||
ndv.getters.inlineExpressionEditorOutput().should('have.text', '1');
|
||||
});
|
||||
|
||||
it('should show data from the correct output in schema view', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_multiple_outputs.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.executeWorkflow();
|
||||
workflowPage.actions.openNode('Only Item 1');
|
||||
ndv.getters.inputPanel().should('be.visible');
|
||||
ndv.getters
|
||||
.inputPanel()
|
||||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('contain.text', 'onlyOnItem1');
|
||||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.openNode('Only Item 2');
|
||||
ndv.getters.inputPanel().should('be.visible');
|
||||
ndv.getters
|
||||
.inputPanel()
|
||||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('contain.text', 'onlyOnItem2');
|
||||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.openNode('Only Item 3');
|
||||
ndv.getters.inputPanel().should('be.visible');
|
||||
ndv.getters
|
||||
.inputPanel()
|
||||
.find('[data-test-id=run-data-schema-item]')
|
||||
.should('contain.text', 'onlyOnItem3');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { successToast } from '../pages/notifications';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const ndv = new NDV();
|
||||
|
@ -39,55 +38,6 @@ describe('Code node', () => {
|
|||
|
||||
successToast().contains('Node executed successfully');
|
||||
});
|
||||
|
||||
it('should show lint errors in `runOnceForAllItems` mode', () => {
|
||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||||
|
||||
getEditor()
|
||||
.type('{selectall}')
|
||||
.paste(`$input.itemMatching()
|
||||
$input.item
|
||||
$('When clicking ‘Test workflow’').item
|
||||
$input.first(1)
|
||||
|
||||
for (const item of $input.all()) {
|
||||
item.foo
|
||||
}
|
||||
|
||||
return
|
||||
`);
|
||||
getParameter().get('.cm-lint-marker-error').should('have.length', 6);
|
||||
getParameter().contains('itemMatching').realHover();
|
||||
cy.get('.cm-tooltip-lint').should(
|
||||
'have.text',
|
||||
'`.itemMatching()` expects an item index to be passed in as its argument.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show lint errors in `runOnceForEachItem` mode', () => {
|
||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||||
|
||||
ndv.getters.parameterInput('mode').click();
|
||||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||||
getEditor()
|
||||
.type('{selectall}')
|
||||
.paste(`$input.itemMatching()
|
||||
$input.all()
|
||||
$input.first()
|
||||
$input.item()
|
||||
|
||||
return []
|
||||
`);
|
||||
|
||||
getParameter().get('.cm-lint-marker-error').should('have.length', 5);
|
||||
getParameter().contains('all').realHover();
|
||||
cy.get('.cm-tooltip-lint').should(
|
||||
'have.text',
|
||||
"Method `$input.all()` is only available in the 'Run Once for All Items' mode.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ask AI', () => {
|
||||
|
|
|
@ -5,11 +5,11 @@ import {
|
|||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
NOTION_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { WorkflowExecutionsTab } from '../pages';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { WorkflowExecutionsTab } from '../pages';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
|
||||
const NEW_WORKFLOW_NAME = 'Something else';
|
||||
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
{
|
||||
"name": "PAY-1707",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "eaa428a8-eb9d-478a-b997-aed6ed298507",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
920,
|
||||
380
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "6b285c91-e7ea-4943-8ba3-59ce01a35d20",
|
||||
"name": "Edit Fields1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
920,
|
||||
540
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return Array.from({length: 5}, _ => ({}))"
|
||||
},
|
||||
"id": "70e682aa-dfef-4db7-a158-971ec7976d49",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
700,
|
||||
380
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return Array.from({length: 5}, _ => ({}))"
|
||||
},
|
||||
"id": "d5ee979e-9f53-4e62-8eb2-cdb92be8ea6e",
|
||||
"name": "Code1",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
700,
|
||||
540
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "dd660366-ca4a-4736-8b1f-454560e87bfb",
|
||||
"options": {}
|
||||
},
|
||||
"id": "20c33c8a-ab2f-4dd4-990f-6390feeb840c",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
480,
|
||||
440
|
||||
],
|
||||
"webhookId": "dd660366-ca4a-4736-8b1f-454560e87bfb"
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Code1": [
|
||||
{
|
||||
"json": {}
|
||||
},
|
||||
{
|
||||
"json": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"Code1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Code1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "01e6693e-54f3-432d-9b1f-922ef92b4ab6",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7"
|
||||
},
|
||||
"id": "hU0gp19G29ehWktc",
|
||||
"tags": []
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
{
|
||||
"name": "Get Weather",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "82eed1ba-179b-4f8f-8a85-b45f0d4e5857",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
560,
|
||||
340
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "6ad8dc55-20f3-45af-a724-c7ecac90d338",
|
||||
"name": "response",
|
||||
"value": "Weather is sunny",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "8f3e00f6-fc92-4aba-817b-93d206158bda",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
780,
|
||||
340
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
{
|
||||
"name": "Search DB",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "64465f9b-63de-43f9-8d90-b5b2eb7a2dc7",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
640,
|
||||
380
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "6ad8dc55-20f3-45af-a724-c7ecac90d338",
|
||||
"name": "response",
|
||||
"value": "10 results found",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "b580fd2b-00c8-4a52-8acb-024f204c0947",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
860,
|
||||
380
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "6026f7a4-f5dc-4c27-9f83-3a02fc6e33ae",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
|
||||
},
|
||||
"id": "BFFhCdBZmNSkx4qf",
|
||||
"tags": []
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
{
|
||||
"name": "Multiple outputs",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "64b27674-3da6-46ce-9008-e173182efa48",
|
||||
"name": "When clicking ‘Test workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"position": [
|
||||
16,
|
||||
-32
|
||||
],
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.code }}",
|
||||
"rightValue": 1,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "Item1"
|
||||
},
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "a659050f-0867-471d-8914-d499b6ad7b31",
|
||||
"leftValue": "={{ $json.code }}",
|
||||
"rightValue": 2,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "Item2"
|
||||
},
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "109fc001-53af-48f1-b79c-5e9afc8b94bd",
|
||||
"leftValue": "={{ $json.code }}",
|
||||
"rightValue": 3,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "Item3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"position": [
|
||||
192,
|
||||
-32
|
||||
],
|
||||
"id": "3863cc7a-8f45-46fc-a60c-36aad5b12877",
|
||||
"name": "Switch",
|
||||
"typeVersion": 3
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "f71bac89-8852-41b2-98dd-cb689f011dcb",
|
||||
"name": "",
|
||||
"value": "",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
480,
|
||||
-192
|
||||
],
|
||||
"id": "85940094-4656-4cdf-a871-1b3b46421de3",
|
||||
"name": "Only Item 1",
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
480,
|
||||
-32
|
||||
],
|
||||
"id": "a7f4e2b5-8cc9-4881-aa06-38601988740e",
|
||||
"name": "Only Item 2",
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
480,
|
||||
128
|
||||
],
|
||||
"id": "7e44ad56-415a-4991-a70e-fea86c430031",
|
||||
"name": "Only Item 3",
|
||||
"typeVersion": 3.4
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"When clicking ‘Test workflow’": [
|
||||
{
|
||||
"json": {
|
||||
"name": "First item",
|
||||
"onlyOnItem1": true,
|
||||
"code": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "Second item",
|
||||
"onlyOnItem2": true,
|
||||
"code": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "Third item",
|
||||
"onlyOnItem3": true,
|
||||
"code": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Switch",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Switch": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Only Item 1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Only Item 2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Only Item 3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "1e2a7b45-7730-42d6-989e-f3fa80de303e",
|
||||
"meta": {
|
||||
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
|
||||
},
|
||||
"id": "V2ld4YU11fsHgr1z",
|
||||
"tags": []
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"data": {
|
||||
"sessionId": "f9130bd7-c078-4862-a38a-369b27b0ff20-e96eb9f7-d581-4684-b6a9-fd3dfe9fe1fb-emTezIGat7bQsDdtIlbti",
|
||||
"parameters": {
|
||||
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "Hi there! Here is my top solution to fix the error in your **Code** node 👇"
|
||||
},
|
||||
{
|
||||
"type": "code-diff",
|
||||
"description": "Fix the syntax error by changing '1asd' to a valid value. In this case, it seems like '1' was intended.",
|
||||
"suggestionId": "1",
|
||||
"codeDiff": "@@ -2,2 +2,2 @@\n item.json.myNewField = 1asd;\n+ item.json.myNewField = 1;\n",
|
||||
"role": "assistant",
|
||||
"quickReplies": [
|
||||
{
|
||||
"text": "Give me another solution",
|
||||
"type": "new-suggestion"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"sessionId": "f1d19ed5-0d55-4bad-b49a-f0c56bd6f76f-705b5dbf-12d4-4805-87a3-1e5b3c716d29-W1JgVNrpfitpSNF9rAjB4",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "To use expressions in n8n, follow these steps:\n\n1. Hover over the parameter where you want to use an expression.\n2. Select **Expressions** in the **Fixed/Expression** toggle.\n3. Write your expression in the parameter, or select **Open expression editor** to open the expressions editor. You can browse the available data in the **Variable selector**. All expressions have the format `{{ your expression here }}`.\n\n### Example: Get data from webhook body\n\nIf your webhook data looks like this:\n\n```json\n[\n {\n \"headers\": {\n \"host\": \"n8n.instance.address\",\n ...\n },\n \"params\": {},\n \"query\": {},\n \"body\": {\n \"name\": \"Jim\",\n \"age\": 30,\n \"city\": \"New York\"\n }\n }\n]\n```\n\nYou can use the following expression to get the value of `city`:\n\n```js\n{{$json.body.city}}\n```\n\nThis expression accesses the incoming JSON-formatted data using n8n's custom `$json` variable and finds the value of `city` (in this example, \"New York\").",
|
||||
"codeSnippet": "{{$json.body.city}}"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "Did this answer solve your question?",
|
||||
"quickReplies": [
|
||||
{
|
||||
"text": "Yes, thanks",
|
||||
"type": "all-good",
|
||||
"isFeedback": true
|
||||
},
|
||||
{
|
||||
"text": "No, I am still stuck",
|
||||
"type": "still-stuck",
|
||||
"isFeedback": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"title": "Glad to Help",
|
||||
"text": "I'm glad I could help. If you have any more questions or need further assistance with your n8n workflows, feel free to ask!"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "event",
|
||||
"eventName": "end-session"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "It seems like my suggestion did not work. Do you want me to come up with a different suggestion? You can also provide more context via the chat.",
|
||||
"quickReplies": [
|
||||
{
|
||||
"text": "Yes",
|
||||
"type": "new-suggestion"
|
||||
},
|
||||
{
|
||||
"text": "No, I don't think you can help",
|
||||
"type": "event:end-session"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "Hey, this is an assistant message",
|
||||
"quickReplies": [
|
||||
{
|
||||
"text": "Sure, let's do it",
|
||||
"type": "yes"
|
||||
},
|
||||
{
|
||||
"text": "Nah, doesn't sound good",
|
||||
"type": "no"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"sessionId": "1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "message",
|
||||
"text": "Hey, this is an assistant message"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "ebfced75-2ce1-4c41-a971-6c3b83522c4d",
|
||||
"name": "When clicking ‘Test workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
360,
|
||||
220
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"errorMessage": "This is an error message"
|
||||
},
|
||||
"id": "f2e60459-401a-49d5-acfc-7b2b31cfdcf7",
|
||||
"name": "Stop and Error",
|
||||
"type": "n8n-nodes-base.stopAndError",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1020,
|
||||
220
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1aaa;\n}\n\nreturn $input.all();"
|
||||
},
|
||||
"id": "b54d4db9-b257-41a8-862f-26d293115bad",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
840,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "053ada73-f7db-4e6a-8cc8-85756cc6ca4e",
|
||||
"name": "age",
|
||||
"value": "={{ 32sad }}",
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "5fd89612-a871-4679-b7b0-d659e09c6a0e",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
600,
|
||||
100
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Stop and Error",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
|
@ -7,27 +7,25 @@
|
|||
"test:e2e:ui": "scripts/run-e2e.js ui",
|
||||
"test:e2e:dev": "scripts/run-e2e.js dev",
|
||||
"test:e2e:all": "scripts/run-e2e.js all",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
"format": "prettier --write . --ignore-path ../.prettierignore",
|
||||
"lint": "eslint . --quiet",
|
||||
"lintfix": "eslint . --fix",
|
||||
"develop": "cd ..; pnpm dev",
|
||||
"start": "cd ..; pnpm start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n/api-types": "workspace:*",
|
||||
"@types/lodash": "catalog:",
|
||||
"eslint-plugin-cypress": "^3.5.0",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"eslint-plugin-cypress": "^3.3.0",
|
||||
"n8n-workflow": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ngneat/falso": "^7.2.0",
|
||||
"@sinonjs/fake-timers": "^13.0.2",
|
||||
"cypress": "^13.14.2",
|
||||
"@sinonjs/fake-timers": "^11.2.2",
|
||||
"cypress": "^13.11.0",
|
||||
"cypress-otp": "^1.0.3",
|
||||
"cypress-real-events": "^1.13.0",
|
||||
"lodash": "catalog:",
|
||||
"nanoid": "catalog:",
|
||||
"start-server-and-test": "^2.0.8"
|
||||
"cypress-real-events": "^1.12.0",
|
||||
"lodash": "4.17.21",
|
||||
"nanoid": "3.3.6",
|
||||
"start-server-and-test": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Actions
|
||||
*/
|
||||
|
||||
export function visitDemoPage(theme?: 'dark' | 'light') {
|
||||
export function vistDemoPage(theme?: 'dark' | 'light') {
|
||||
const query = theme ? `?theme=${theme}` : '';
|
||||
cy.visit('/workflows/demo' + query);
|
||||
cy.waitForLoad();
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import { overrideFeatureFlag } from '../../composables/featureFlags';
|
||||
import { BasePage } from '../base';
|
||||
|
||||
const AI_ASSISTANT_FEATURE = {
|
||||
name: 'aiAssistant',
|
||||
experimentName: '021_ai_debug_helper',
|
||||
enabledFor: 'variant',
|
||||
disabledFor: 'control',
|
||||
};
|
||||
|
||||
export class AIAssistant extends BasePage {
|
||||
url = '/workflows/new';
|
||||
|
||||
getters = {
|
||||
askAssistantFloatingButton: () => cy.getByTestId('ask-assistant-floating-button'),
|
||||
askAssistantSidebar: () => cy.getByTestId('ask-assistant-sidebar'),
|
||||
askAssistantSidebarResizer: () =>
|
||||
this.getters.askAssistantSidebar().find('[class^=_resizer][data-dir=left]').first(),
|
||||
askAssistantChat: () => cy.getByTestId('ask-assistant-chat'),
|
||||
placeholderMessage: () => cy.getByTestId('placeholder-message'),
|
||||
closeChatButton: () => cy.getByTestId('close-chat-button'),
|
||||
chatInputWrapper: () => cy.getByTestId('chat-input-wrapper'),
|
||||
chatInput: () => cy.getByTestId('chat-input'),
|
||||
sendMessageButton: () => cy.getByTestId('send-message-button'),
|
||||
chatMessagesAll: () => cy.get('[data-test-id^=chat-message]'),
|
||||
chatMessagesAssistant: () => cy.getByTestId('chat-message-assistant'),
|
||||
chatMessagesUser: () => cy.getByTestId('chat-message-user'),
|
||||
chatMessagesSystem: () => cy.getByTestId('chat-message-system'),
|
||||
quickReplies: () => cy.getByTestId('quick-replies'),
|
||||
quickReplyButtons: () => this.getters.quickReplies().find('button'),
|
||||
newAssistantSessionModal: () => cy.getByTestId('new-assistant-session-modal'),
|
||||
codeDiffs: () => cy.getByTestId('code-diff-suggestion'),
|
||||
applyCodeDiffButtons: () => cy.getByTestId('replace-code-button'),
|
||||
undoReplaceCodeButtons: () => cy.getByTestId('undo-replace-button'),
|
||||
codeReplacedMessage: () => cy.getByTestId('code-replaced-message'),
|
||||
nodeErrorViewAssistantButton: () =>
|
||||
cy.getByTestId('node-error-view-ask-assistant-button').find('button').first(),
|
||||
credentialEditAssistantButton: () => cy.getByTestId('credential-edit-ask-assistant-button'),
|
||||
codeSnippet: () => cy.getByTestId('assistant-code-snippet-content'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
enableAssistant: () => {
|
||||
overrideFeatureFlag(AI_ASSISTANT_FEATURE.experimentName, AI_ASSISTANT_FEATURE.enabledFor);
|
||||
cy.enableFeature(AI_ASSISTANT_FEATURE.name);
|
||||
},
|
||||
disableAssistant: () => {
|
||||
overrideFeatureFlag(AI_ASSISTANT_FEATURE.experimentName, AI_ASSISTANT_FEATURE.disabledFor);
|
||||
cy.disableFeature(AI_ASSISTANT_FEATURE.name);
|
||||
},
|
||||
sendMessage: (message: string) => {
|
||||
this.getters.chatInput().type(message).type('{enter}');
|
||||
},
|
||||
closeChat: () => {
|
||||
this.getters.closeChatButton().click();
|
||||
this.getters.askAssistantChat().should('not.be.visible');
|
||||
},
|
||||
openChat: () => {
|
||||
this.getters.askAssistantFloatingButton().click();
|
||||
this.getters.askAssistantChat().should('be.visible');
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
import { BasePage } from './base';
|
||||
import { SigninPage } from './signin';
|
||||
import { WorkflowsPage } from './workflows';
|
||||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
|
||||
export class MfaLoginPage extends BasePage {
|
||||
url = '/mfa';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { getVisibleSelect } from '../../utils';
|
||||
import { BasePage } from '../base';
|
||||
import { getVisibleSelect } from '../../utils';
|
||||
|
||||
export class CredentialsModal extends BasePage {
|
||||
getters = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BasePage } from './base';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
import { BasePage } from './base';
|
||||
|
||||
export class NDV extends BasePage {
|
||||
getters = {
|
||||
|
@ -24,7 +24,6 @@ export class NDV extends BasePage {
|
|||
editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'),
|
||||
pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller .cm-content'),
|
||||
runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'),
|
||||
aiOutputModeToggle: () => cy.getByTestId('ai-output-mode-select'),
|
||||
nodeOutputHint: () => cy.getByTestId('ndv-output-run-node-hint'),
|
||||
savePinnedDataButton: () =>
|
||||
this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
|
||||
|
@ -78,7 +77,6 @@ export class NDV extends BasePage {
|
|||
resourceLocatorDropdown: (paramName: string) =>
|
||||
this.getters.resourceLocator(paramName).find('[data-test-id="resource-locator-dropdown"]'),
|
||||
resourceLocatorErrorMessage: () => cy.getByTestId('rlc-error-container'),
|
||||
resourceLocatorAddCredentials: () => this.getters.resourceLocatorErrorMessage().find('a'),
|
||||
resourceLocatorModeSelector: (paramName: string) =>
|
||||
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
|
||||
resourceLocatorSearch: (paramName: string) =>
|
||||
|
@ -138,8 +136,6 @@ export class NDV extends BasePage {
|
|||
cy.getByTestId(`fixed-collection-${paramName}`),
|
||||
schemaViewNode: () => cy.getByTestId('run-data-schema-node'),
|
||||
schemaViewNodeName: () => cy.getByTestId('run-data-schema-node-name'),
|
||||
expressionExpanders: () => cy.getByTestId('expander'),
|
||||
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
|
@ -177,7 +173,7 @@ export class NDV extends BasePage {
|
|||
this.getters.editPinnedDataButton().click();
|
||||
|
||||
this.getters.pinnedDataEditor().click();
|
||||
this.getters.pinnedDataEditor().invoke('text', '').paste(JSON.stringify(data));
|
||||
this.getters.pinnedDataEditor().type('{selectall}{backspace}').paste(JSON.stringify(data));
|
||||
|
||||
this.actions.savePinnedData();
|
||||
},
|
||||
|
@ -207,9 +203,9 @@ export class NDV extends BasePage {
|
|||
const droppable = `[data-test-id="parameter-input-${parameterName}"]`;
|
||||
cy.draganddrop(draggable, droppable);
|
||||
},
|
||||
mapToParameter: (parameterName: string, position?: 'top' | 'center' | 'bottom') => {
|
||||
mapToParameter: (parameterName: string) => {
|
||||
const droppable = `[data-test-id="parameter-input-${parameterName}"]`;
|
||||
cy.draganddrop('', droppable, { position });
|
||||
cy.draganddrop('', droppable);
|
||||
},
|
||||
switchInputMode: (type: 'Schema' | 'Table' | 'JSON' | 'Binary') => {
|
||||
this.getters.inputDisplayMode().find('label').contains(type).click({ force: true });
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
export const getCommunityCards = () => {
|
||||
return cy.getByTestId('community-package-card');
|
||||
};
|
||||
|
||||
export const visitCommunityNodesSettings = () => {
|
||||
cy.visit('/settings/community-nodes');
|
||||
};
|
||||
|
||||
export const installFirstCommunityNode = (nodeName: string) => {
|
||||
cy.getByTestId('action-box').find('button').click();
|
||||
cy.getByTestId('communityPackageInstall-modal').find('input').eq(0).type(nodeName);
|
||||
cy.getByTestId('user-agreement-checkbox').click();
|
||||
cy.getByTestId('install-community-package-button').click();
|
||||
};
|
||||
|
||||
export const confirmCommunityNodeUpdate = () => {
|
||||
cy.getByTestId('communityPackageManageConfirm-modal').find('button').eq(1).click();
|
||||
};
|
||||
|
||||
export const confirmCommunityNodeUninstall = () => {
|
||||
cy.getByTestId('communityPackageManageConfirm-modal').find('button').eq(1).click();
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue