mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
chore: merge master
This commit is contained in:
commit
b86cc334cd
|
@ -10,3 +10,5 @@ packages/**/.turbo
|
|||
.git
|
||||
.github
|
||||
*.tsbuildinfo
|
||||
packages/cli/dist/**/e2e.*
|
||||
packages/cli/dist/ReloadNodesAndCredentials.*
|
||||
|
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -23,10 +23,10 @@ A clear and concise description of what you expected to happen.
|
|||
**Environment (please complete the following information):**
|
||||
|
||||
- OS: [e.g. Ubuntu Linux 22.04]
|
||||
- n8n Version [e.g. 0.200.1]
|
||||
- Node.js Version [e.g. 16.17.0]
|
||||
- n8n Version [e.g. 1.0.1]
|
||||
- Node.js Version [e.g. 18.16.0]
|
||||
- Database system [e.g. SQLite; n8n uses SQLite as default otherwise changed]
|
||||
- Operation mode [e.g. own; operation modes are `own`, `main` and `queue`. Default is `own`]
|
||||
- Operation mode [e.g. own; operation modes are `own`, `main` and `queue`. Default is `main`]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
|
20
.github/scripts/check-tests.mjs
vendored
20
.github/scripts/check-tests.mjs
vendored
|
@ -17,6 +17,18 @@ const filterAsync = async (asyncPredicate, arr) => {
|
|||
return filterResults.filter(({shouldKeep}) => shouldKeep).map(({item}) => item);
|
||||
}
|
||||
|
||||
const isAbstractClass = (node) => {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
return node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword) || false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const isAbstractMethod = (node) => {
|
||||
return ts.isMethodDeclaration(node) && Boolean(node.modifiers?.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword));
|
||||
}
|
||||
|
||||
|
||||
// Function to check if a file has a function declaration, function expression, object method or class
|
||||
const hasFunctionOrClass = async filePath => {
|
||||
const fileContent = await readFileAsync(filePath, 'utf-8');
|
||||
|
@ -24,7 +36,13 @@ const hasFunctionOrClass = async filePath => {
|
|||
|
||||
let hasFunctionOrClass = false;
|
||||
const visit = node => {
|
||||
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node) || ts.isClassDeclaration(node)) {
|
||||
if (
|
||||
ts.isFunctionDeclaration(node)
|
||||
|| ts.isFunctionExpression(node)
|
||||
|| ts.isArrowFunction(node)
|
||||
|| (ts.isMethodDeclaration(node) && !isAbstractMethod(node))
|
||||
|| (ts.isClassDeclaration(node) && !isAbstractClass(node))
|
||||
) {
|
||||
hasFunctionOrClass = true;
|
||||
}
|
||||
node.forEachChild(visit);
|
||||
|
|
8
.github/scripts/package.json
vendored
8
.github/scripts/package.json
vendored
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"glob": "^10.2.7",
|
||||
"semver": "^7.3.8",
|
||||
"add-stream": "^1.0.0",
|
||||
"conventional-changelog": "^4.0.0",
|
||||
"glob": "^10.3.0",
|
||||
"semver": "^7.5.2",
|
||||
"tempfile": "^5.0.0",
|
||||
"typescript": "*"
|
||||
}
|
||||
}
|
||||
|
|
42
.github/scripts/update-changelog.mjs
vendored
Normal file
42
.github/scripts/update-changelog.mjs
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
import addStream from 'add-stream';
|
||||
import createTempFile from 'tempfile';
|
||||
import conventionalChangelog from 'conventional-changelog';
|
||||
import { resolve } from 'path';
|
||||
import { createReadStream, createWriteStream } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import stream from 'stream';
|
||||
import { promisify } from 'util';
|
||||
import packageJson from '../../package.json' assert { type: 'json' };
|
||||
|
||||
const pipeline = promisify(stream.pipeline);
|
||||
|
||||
const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const fullChangelogFile = resolve(baseDir, 'CHANGELOG.md');
|
||||
const versionChangelogFile = resolve(baseDir, `CHANGELOG-${packageJson.version}.md`);
|
||||
|
||||
const changelogStream = conventionalChangelog({
|
||||
preset: 'angular',
|
||||
releaseCount: 1,
|
||||
tagPrefix: 'n8n@',
|
||||
transform: (commit, callback) => {
|
||||
callback(null, commit.header.includes('(no-changelog)') ? undefined : commit);
|
||||
},
|
||||
}).on('error', (err) => {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// We need to duplicate the stream here to pipe the changelog into two separate files
|
||||
const stream1 = new stream.PassThrough();
|
||||
const stream2 = new stream.PassThrough();
|
||||
changelogStream.pipe(stream1);
|
||||
changelogStream.pipe(stream2);
|
||||
|
||||
await pipeline(stream1, createWriteStream(versionChangelogFile));
|
||||
|
||||
// Since we can't read and write from the same file at the same time,
|
||||
// we use a temporary file to output the updated changelog to.
|
||||
const tmpFile = createTempFile();
|
||||
await pipeline(stream2, addStream(createReadStream(fullChangelogFile)), createWriteStream(tmpFile)),
|
||||
await pipeline(createReadStream(tmpFile), createWriteStream(fullChangelogFile));
|
18
.github/workflows/check-issue-template.yml
vendored
Normal file
18
.github/workflows/check-issue-template.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: Check Issue Template
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
check-issue:
|
||||
name: Check Issue Template
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run Check Issue Template
|
||||
uses: n8n-io/GH-actions-playground@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
|
22
.github/workflows/checklist.yml
vendored
Normal file
22
.github/workflows/checklist.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: PR Checklist
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
checklist_job:
|
||||
runs-on: ubuntu-latest
|
||||
name: Checklist job
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Checklist
|
||||
uses: wyozi/contextual-qa-checklist-action@master
|
||||
with:
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-footer: Make sure to check off this list before asking for review.
|
2
.github/workflows/ci-master.yml
vendored
2
.github/workflows/ci-master.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x, 18.x]
|
||||
node-version: [18.x, 20.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
28
.github/workflows/ci-pull-requests.yml
vendored
28
.github/workflows/ci-pull-requests.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Build, unit/smoke test and lint branch
|
||||
name: Build, unit test and lint branch
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
|
@ -107,29 +107,3 @@ jobs:
|
|||
env:
|
||||
ESLINT_PLUGIN_DIFF_COMMIT: ${{ github.event.pull_request.base.ref }}
|
||||
run: pnpm lint
|
||||
|
||||
smoke-test:
|
||||
name: E2E [Electron/Node 18]
|
||||
uses: ./.github/workflows/e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
user: ${{ github.event.inputs.user || 'PR User' }}
|
||||
spec: ${{ github.event.inputs.spec || 'e2e/0-smoke.cy.ts' }}
|
||||
record: false
|
||||
parallel: false
|
||||
pr_number: ${{ github.event.number }}
|
||||
containers: '[1]'
|
||||
secrets:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
checklist_job:
|
||||
runs-on: ubuntu-latest
|
||||
name: Checklist job
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Checklist
|
||||
uses: wyozi/contextual-qa-checklist-action@master
|
||||
with:
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-footer: Make sure to check off this list before asking for review.
|
||||
|
|
3
.github/workflows/docker-base-image.yml
vendored
3
.github/workflows/docker-base-image.yml
vendored
|
@ -7,10 +7,11 @@ on:
|
|||
description: 'Node.js version to build this image with.'
|
||||
type: choice
|
||||
required: true
|
||||
default: '16'
|
||||
default: '18'
|
||||
options:
|
||||
- '16'
|
||||
- '18'
|
||||
- '20'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
46
.github/workflows/docker-image-v1-rc.yml
vendored
46
.github/workflows/docker-image-v1-rc.yml
vendored
|
@ -1,46 +0,0 @@
|
|||
name: Docker Image - V1 RC
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: release-v1
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: npm install --prefix=.github/scripts --no-package-lock
|
||||
|
||||
- name: Bump package versions to 1.0.0
|
||||
run: |
|
||||
RELEASE_TYPE=major node .github/scripts/bump-versions.mjs
|
||||
pnpm i --lockfile-only
|
||||
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n-custom/Dockerfile
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/n8n:1.0.0-rc
|
||||
no-cache: true
|
10
.github/workflows/docker-images.yml
vendored
10
.github/workflows/docker-images.yml
vendored
|
@ -8,10 +8,6 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
docker-context: ['', '-debian']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
@ -41,12 +37,12 @@ jobs:
|
|||
- name: Build
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ./docker/images/n8n${{ matrix.docker-context }}
|
||||
context: ./docker/images/n8n
|
||||
build-args: |
|
||||
N8N_VERSION=${{ steps.vars.outputs.tag }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
provenance: false
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ steps.vars.outputs.tag }}${{ matrix.docker-context }}
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ steps.vars.outputs.tag }}${{ matrix.docker-context }}
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ steps.vars.outputs.tag }}
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ steps.vars.outputs.tag }}
|
||||
|
|
26
.github/workflows/release-create-pr.yml
vendored
26
.github/workflows/release-create-pr.yml
vendored
|
@ -35,37 +35,33 @@ jobs:
|
|||
fetch-depth: 0
|
||||
ref: ${{ github.event.inputs.base-branch }}
|
||||
|
||||
- name: Push the base branch
|
||||
run: |
|
||||
git checkout -b "release/${{ github.event.inputs.release-type }}"
|
||||
git push -f origin "release/${{ github.event.inputs.release-type }}"
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- run: npm install --prefix=.github/scripts --no-package-lock
|
||||
|
||||
- name: Bump package versions
|
||||
run: |
|
||||
echo "NEXT_RELEASE=$(node .github/scripts/bump-versions.mjs)" >> $GITHUB_ENV
|
||||
pnpm i --lockfile-only
|
||||
env:
|
||||
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
|
||||
|
||||
- name: Generate Changelog
|
||||
run: npx conventional-changelog-cli -p angular -i CHANGELOG.md -s -t n8n@
|
||||
- name: Update Changelog
|
||||
run: node .github/scripts/update-changelog.mjs
|
||||
|
||||
- name: Push the base branch
|
||||
run: |
|
||||
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@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
base: 'release/${{ github.event.inputs.release-type }}'
|
||||
branch: 'release/${{ env.NEXT_RELEASE }}'
|
||||
base: 'release/${{ env.NEXT_RELEASE }}'
|
||||
branch: '${{ env.NEXT_RELEASE }}-pr'
|
||||
commit-message: ':rocket: Release ${{ env.NEXT_RELEASE }}'
|
||||
delete-branch: true
|
||||
labels: 'release'
|
||||
title: ':rocket: Release ${{ env.NEXT_RELEASE }}'
|
||||
# 'TODO: add generated changelog to the body. create a script to generate custom changelog'
|
||||
body: ''
|
||||
|
||||
# TODO: post PR link to slack
|
||||
body-path: 'CHANGELOG-${{ env.NEXT_RELEASE }}.md'
|
||||
|
|
4
.github/workflows/release-publish.yml
vendored
4
.github/workflows/release-publish.yml
vendored
|
@ -5,8 +5,7 @@ on:
|
|||
types:
|
||||
- closed
|
||||
branches:
|
||||
- 'release/patch'
|
||||
- 'release/minor'
|
||||
- 'release/*'
|
||||
|
||||
jobs:
|
||||
publish-release:
|
||||
|
@ -50,6 +49,7 @@ jobs:
|
|||
tag: 'n8n@${{env.RELEASE}}'
|
||||
prerelease: true
|
||||
makeLatest: false
|
||||
body: ${{github.event.pull_request.body}}
|
||||
|
||||
- name: Trigger a release note
|
||||
continue-on-error: true
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -20,3 +20,4 @@ packages/**/.turbo
|
|||
cypress/videos/*
|
||||
cypress/screenshots/*
|
||||
*.swp
|
||||
CHANGELOG-*.md
|
||||
|
|
119
CHANGELOG.md
119
CHANGELOG.md
|
@ -1,3 +1,122 @@
|
|||
## [1.0.1](https://github.com/n8n-io/n8n/compare/n8n@1.0.0...n8n@1.0.1) (2023-07-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Fix credentials test ([#6569](https://github.com/n8n-io/n8n/issues/6569)) ([8f244df](https://github.com/n8n-io/n8n/commit/8f244df0f9efcb087a78dd8d9481489c484c77b7))
|
||||
* **core:** Fix migrations for MySQL/MariaDB ([#6591](https://github.com/n8n-io/n8n/issues/6591)) ([b9da67b](https://github.com/n8n-io/n8n/commit/b9da67b653bf19f39d0d1506d3140c71432efaed))
|
||||
* **core:** Make node execution order configurable, and backward-compatible ([#6507](https://github.com/n8n-io/n8n/issues/6507)) ([d97edbc](https://github.com/n8n-io/n8n/commit/d97edbcffa966a693548eed033ac41d4a404fc23))
|
||||
* **core:** Update pruning related config defaults for v1 ([#6577](https://github.com/n8n-io/n8n/issues/6577)) ([ffb4e47](https://github.com/n8n-io/n8n/commit/ffb4e470b56222ae11891d478e96ea9c31675afe))
|
||||
* **editor:** Restore expression completions ([#6566](https://github.com/n8n-io/n8n/issues/6566)) ([516e572](https://github.com/n8n-io/n8n/commit/516e5728f73da6393defe7633533cc142c531c7a))
|
||||
* **editor:** Show retry information in execution list only when it exists ([#6587](https://github.com/n8n-io/n8n/issues/6587)) ([2580286](https://github.com/n8n-io/n8n/commit/2580286a198e53c3bf3db6e56faed301b606db07))
|
||||
* **Sendy Node:** Fix issue with brand id not being sent ([#6530](https://github.com/n8n-io/n8n/issues/6530)) ([b9e5211](https://github.com/n8n-io/n8n/commit/b9e52117355d939e77a2e3c59a7f67ac21e31b22))
|
||||
* **Strapi Node:** Fix issue with pagination ([#4991](https://github.com/n8n-io/n8n/issues/4991)) ([4253b48](https://github.com/n8n-io/n8n/commit/4253b48b26d1625cd2fb7f38159f9528cea45f34))
|
||||
* **XML Node:** Fix issue with not returning valid data ([#6565](https://github.com/n8n-io/n8n/issues/6565)) ([c2b9d5a](https://github.com/n8n-io/n8n/commit/c2b9d5ac506375ecc316e8c79a3ce0bf143e9406))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add missing input panels to some trigger nodes ([#6518](https://github.com/n8n-io/n8n/issues/6518)) ([3b12864](https://github.com/n8n-io/n8n/commit/3b12864460a458f23b57a6f3f4b40d0d364ef6e6))
|
||||
|
||||
|
||||
|
||||
# [1.0.0](https://github.com/n8n-io/n8n/compare/n8n@0.234.0...n8n@1.0.0) (2023-06-27)
|
||||
|
||||
|
||||
### ⚠️ BREAKING CHANGES
|
||||
* **core** Docker containers now run as the user `node` instead of `root` ([#6365](https://github.com/n8n-io/n8n/pull/6365)) ([f636616](https://github.com/n8n-io/n8n/commit/f6366160a476f42cb0612d10c5777a154d8665dd))
|
||||
* **core** Drop `debian` and `rhel7` images ([#6365](https://github.com/n8n-io/n8n/pull/6365)) ([f636616](https://github.com/n8n-io/n8n/commit/f6366160a476f42cb0612d10c5777a154d8665dd))
|
||||
* **core** Drop support for deprecated `WEBHOOK_TUNNEL_URL` env variable ([#6363](https://github.com/n8n-io/n8n/pull/6363))
|
||||
* **core** Execution mode defaults to `main` now, instead of `own` ([#6363](https://github.com/n8n-io/n8n/pull/6363))
|
||||
* **core** Default push backend is `websocket` now, instead of `sse` ([#6363](https://github.com/n8n-io/n8n/pull/6363))
|
||||
* **core** Stop loading custom/community nodes from n8n's `node_modules` folder ([#6396](https://github.com/n8n-io/n8n/pull/6396)) ([a45a2c8](https://github.com/n8n-io/n8n/commit/a45a2c8c41eb7ffb2d62d5a8877c34eb45799fa9))
|
||||
* **core** User management is mandatory now. basic-auth, external-jwt-auth, and no-auth options are removed ([#6362](https://github.com/n8n-io/n8n/pull/6362)) ([8c008f5](https://github.com/n8n-io/n8n/commit/8c008f5d2217030e93d79e2baca0f2965d4d643e))
|
||||
* **core** Allow syntax errors and expression errors to fail executions ([#6352](https://github.com/n8n-io/n8n/pull/6352)) ([1197811](https://github.com/n8n-io/n8n/commit/1197811a1e3bc4ad7464d53d7e4860d0e62335a3))
|
||||
* **core** Drop support for `request` library and `N8N_USE_DEPRECATED_REQUEST_LIB` env variable ([#6413](https://github.com/n8n-io/n8n/pull/6413)) ([632ea27](https://github.com/n8n-io/n8n/commit/632ea275b7fa352d4af23339208bed66bb948da8))
|
||||
* **core** Make date extensions outputs match inputs ([#6435](https://github.com/n8n-io/n8n/pull/6435)) ([85372aa](https://github.com/n8n-io/n8n/commit/85372aabdfc52493504d4723ee1829e2ea15151d))
|
||||
* **core** Drop support for `executeSingle` method on nodes ([#4853](https://github.com/n8n-io/n8n/pull/4853)) ([9194d8b](https://github.com/n8n-io/n8n/commit/9194d8bb0ecf81e52d47ddfc4b75dc4e0efd492d))
|
||||
* **core** Change data processing for multi-input-nodes ([#4238](https://github.com/n8n-io/n8n/pull/4238)) ([b8458a5](https://github.com/n8n-io/n8n/commit/b8458a53f66b79903f0fdb168f6febdefb36d13a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** All migrations should run in a transaction ([#6519](https://github.com/n8n-io/n8n/issues/6519)) ([e152cfe](https://github.com/n8n-io/n8n/commit/e152cfe27cf3396f4b278614f1d46d9dd723f36e))
|
||||
* **Edit Image Node:** Fix transparent operation ([#6513](https://github.com/n8n-io/n8n/issues/6513)) ([4a4bcbc](https://github.com/n8n-io/n8n/commit/4a4bcbca298bf90c54d3597103e6a231855abbd2))
|
||||
* **Google Drive Node:** URL parsing ([#6527](https://github.com/n8n-io/n8n/issues/6527)) ([18aa9f3](https://github.com/n8n-io/n8n/commit/18aa9f3c62149cd603c560c2944c3146cd31e9e7))
|
||||
* **Google Sheets Node:** Incorrect read of 0 and false ([#6525](https://github.com/n8n-io/n8n/issues/6525)) ([b6202b5](https://github.com/n8n-io/n8n/commit/b6202b5585f864d97dc114e1e49a6a7dae5c674a))
|
||||
* **Merge Node:** Enrich input 2 fix ([#6526](https://github.com/n8n-io/n8n/issues/6526)) ([70822ce](https://github.com/n8n-io/n8n/commit/70822ce988543476719089c132e1d10af0d03e78))
|
||||
* **Notion Node:** Version fix ([#6531](https://github.com/n8n-io/n8n/issues/6531)) ([d3d8522](https://github.com/n8n-io/n8n/commit/d3d8522e8f0c702f56997667a252892296540450))
|
||||
* Show error when referencing node that exist but has not been executed ([#6496](https://github.com/n8n-io/n8n/issues/6496)) ([3db2707](https://github.com/n8n-io/n8n/commit/3db2707b8e47ea539f4f6c40497a928b51b40274))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Change node execution order (most top-left one first) ([#6246](https://github.com/n8n-io/n8n/issues/6246)) ([0287d5b](https://github.com/n8n-io/n8n/commit/0287d5becdce30a9c0de2a0d6ad4a0db50e198d7))
|
||||
* **core:** Remove conditional defaults in V1 release ([#6363](https://github.com/n8n-io/n8n/issues/6363)) ([f636616](https://github.com/n8n-io/n8n/commit/f6366160a476f42cb0612d10c5777a154d8665dd))
|
||||
* **editor:** Add v1 banner ([#6443](https://github.com/n8n-io/n8n/issues/6443)) ([0fe415a](https://github.com/n8n-io/n8n/commit/0fe415add2baa8e70e29087f7a90312bd1ab38af))
|
||||
* **editor:** SQL editor overhaul ([#6282](https://github.com/n8n-io/n8n/issues/6282)) ([beedfb6](https://github.com/n8n-io/n8n/commit/beedfb609ccde2ef202e08566580a2e1a6b6eafa))
|
||||
* **HTTP Request Node:** Notice about dev console ([#6516](https://github.com/n8n-io/n8n/issues/6516)) ([d431117](https://github.com/n8n-io/n8n/commit/d431117c9e5db9ff0ec6a1e7371bbf58698957c9))
|
||||
|
||||
|
||||
|
||||
# [0.236.0](https://github.com/n8n-io/n8n/compare/n8n@0.235.0...n8n@0.236.0) (2023-07-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Brevo Node:** Rename SendInBlue node to Brevo node ([#6521](https://github.com/n8n-io/n8n/issues/6521)) ([e63b398](https://github.com/n8n-io/n8n/commit/e63b3982d200ade34461b9159eb1e988f494c025))
|
||||
* **core:** Fix credentials test ([#6569](https://github.com/n8n-io/n8n/issues/6569)) ([1abd172](https://github.com/n8n-io/n8n/commit/1abd172f73e171e37c4cc3ccfaa395c6a46bdf48))
|
||||
* **core:** Fix migrations for MySQL/MariaDB ([#6591](https://github.com/n8n-io/n8n/issues/6591)) ([29882a6](https://github.com/n8n-io/n8n/commit/29882a6f39dddcd1c8c107c20a548ce8dc665cba))
|
||||
* **core:** Improve the performance of last 2 sqlite migrations ([#6522](https://github.com/n8n-io/n8n/issues/6522)) ([31cba87](https://github.com/n8n-io/n8n/commit/31cba87d307183d613890c7e6d627636b5280b52))
|
||||
* **core:** Remove typeorm patches, but still enforce transactions on every migration ([#6594](https://github.com/n8n-io/n8n/issues/6594)) ([9def7a7](https://github.com/n8n-io/n8n/commit/9def7a729b52cd6b4698c47e190e9e2bd7894da5)), closes [#6519](https://github.com/n8n-io/n8n/issues/6519)
|
||||
* **core:** Use owners file to export wf owners ([#6547](https://github.com/n8n-io/n8n/issues/6547)) ([4b755fb](https://github.com/n8n-io/n8n/commit/4b755fb0b441a37eb804c9e70d4b071a341f7155))
|
||||
* **editor:** Show retry information in execution list only when it exists ([#6587](https://github.com/n8n-io/n8n/issues/6587)) ([3ca66be](https://github.com/n8n-io/n8n/commit/3ca66be38082e7a3866d53d07328be58e913067f))
|
||||
* **Salesforce Node:** Fix typo for adding a contact to a campaign ([#6598](https://github.com/n8n-io/n8n/issues/6598)) ([7ffe3cb](https://github.com/n8n-io/n8n/commit/7ffe3cb36adeecaca6cc6ddf067a701ee55c18d1))
|
||||
* **Strapi Node:** Fix issue with pagination ([#4991](https://github.com/n8n-io/n8n/issues/4991)) ([54444fa](https://github.com/n8n-io/n8n/commit/54444fa388da12d75553e66e53a8cf6f8a99b6fc))
|
||||
* **XML Node:** Fix issue with not returning valid data ([#6565](https://github.com/n8n-io/n8n/issues/6565)) ([cdd215f](https://github.com/n8n-io/n8n/commit/cdd215f642b47413c05f229e641074d0d4048f68))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add crowd.dev node and trigger node ([#6082](https://github.com/n8n-io/n8n/issues/6082)) ([238a78f](https://github.com/n8n-io/n8n/commit/238a78f0582dbf439a9799de0edcb2e9bef29978))
|
||||
* Add various source control improvements ([#6533](https://github.com/n8n-io/n8n/issues/6533)) ([68fdc20](https://github.com/n8n-io/n8n/commit/68fdc2078928be478a286774f2889feba1c3f5fe))
|
||||
* **HTTP Request Node:** New http request generic custom auth credential ([#5798](https://github.com/n8n-io/n8n/issues/5798)) ([b17b458](https://github.com/n8n-io/n8n/commit/b17b4582a059104665888a2369c3e2256db4c1ed))
|
||||
* **Microsoft To Do Node:** Add an option to set a reminder when creating a task ([#5757](https://github.com/n8n-io/n8n/issues/5757)) ([b19833d](https://github.com/n8n-io/n8n/commit/b19833d673bd554ba86c0b234e8d13633912563a))
|
||||
* **Notion Node:** Add option to update icon when updating a page ([#5670](https://github.com/n8n-io/n8n/issues/5670)) ([225e849](https://github.com/n8n-io/n8n/commit/225e849960ce65d7f85b482f05fb3d7ffb4f9427))
|
||||
* **Strava Node:** Add hide_from_home field in Activity Update ([#5883](https://github.com/n8n-io/n8n/issues/5883)) ([7495e31](https://github.com/n8n-io/n8n/commit/7495e31a5b25e97683c7ea38225ba253d8fae8b7))
|
||||
* **Twitter Node:** Node overhaul ([#4788](https://github.com/n8n-io/n8n/issues/4788)) ([42721db](https://github.com/n8n-io/n8n/commit/42721dba80077fb796086a2bf0ecce256bf3a50f))
|
||||
|
||||
|
||||
|
||||
# [0.235.0](https://github.com/n8n-io/n8n/compare/n8n@0.234.0...n8n@0.235.0) (2023-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Add empty credential value marker to show empty pw field ([#6532](https://github.com/n8n-io/n8n/issues/6532)) ([9294e2d](https://github.com/n8n-io/n8n/commit/9294e2da3c7c99c2099f5865e610fa7217bf06be))
|
||||
* **core:** All migrations should run in a transaction ([#6519](https://github.com/n8n-io/n8n/issues/6519)) ([e152cfe](https://github.com/n8n-io/n8n/commit/e152cfe27cf3396f4b278614f1d46d9dd723f36e))
|
||||
* **core:** Rename to credential_stubs and variable_stubs.json ([#6528](https://github.com/n8n-io/n8n/issues/6528)) ([b06462f](https://github.com/n8n-io/n8n/commit/b06462f4415bd1143a00b4a66e6e626da8c52196))
|
||||
* **Edit Image Node:** Fix transparent operation ([#6513](https://github.com/n8n-io/n8n/issues/6513)) ([4a4bcbc](https://github.com/n8n-io/n8n/commit/4a4bcbca298bf90c54d3597103e6a231855abbd2))
|
||||
* **editor:** Add default author name and email to source control settings ([#6543](https://github.com/n8n-io/n8n/issues/6543)) ([e1a02c7](https://github.com/n8n-io/n8n/commit/e1a02c76257de30e08878279dea33d7854d46938))
|
||||
* **editor:** Change default branchColor and remove label ([#6541](https://github.com/n8n-io/n8n/issues/6541)) ([186271e](https://github.com/n8n-io/n8n/commit/186271e939bca19ec9c94d9455e9430d8b8cf9d7))
|
||||
* **Google Drive Node:** URL parsing ([#6527](https://github.com/n8n-io/n8n/issues/6527)) ([d9ed0b3](https://github.com/n8n-io/n8n/commit/d9ed0b31b538320a67ee4e5c0cae34656c9f4334))
|
||||
* **Google Sheets Node:** Incorrect read of 0 and false ([#6525](https://github.com/n8n-io/n8n/issues/6525)) ([806d134](https://github.com/n8n-io/n8n/commit/806d13460240abe94843e569b1820cd8d0d8edd1))
|
||||
* **Merge Node:** Enrich input 2 fix ([#6526](https://github.com/n8n-io/n8n/issues/6526)) ([c82c7f1](https://github.com/n8n-io/n8n/commit/c82c7f19128df3a11d6d0f18e8d8dab57e6a3b8f))
|
||||
* **Notion Node:** Version fix ([#6531](https://github.com/n8n-io/n8n/issues/6531)) ([38dc784](https://github.com/n8n-io/n8n/commit/38dc784d2eed25aae777c5c3c3fda1a35e20bd24))
|
||||
* **Sendy Node:** Fix issue with brand id not being sent ([#6530](https://github.com/n8n-io/n8n/issues/6530)) ([2e8dfb8](https://github.com/n8n-io/n8n/commit/2e8dfb86d4636781b319d6190e8be12e7661ee16))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add missing input panels to some trigger nodes ([#6518](https://github.com/n8n-io/n8n/issues/6518)) ([fdf8a42](https://github.com/n8n-io/n8n/commit/fdf8a428ed38bb3ceb2bc0e50b002b34843d8fc4))
|
||||
* **editor:** Prevent saving of workflow when canvas is loading ([#6497](https://github.com/n8n-io/n8n/issues/6497)) ([f89ef83](https://github.com/n8n-io/n8n/commit/f89ef83c766fafb1d0497ed91a74b93e8d2af1ec))
|
||||
* **editor:** SQL editor overhaul ([#6282](https://github.com/n8n-io/n8n/issues/6282)) ([beedfb6](https://github.com/n8n-io/n8n/commit/beedfb609ccde2ef202e08566580a2e1a6b6eafa))
|
||||
* **Google Drive Node:** Overhaul ([#5941](https://github.com/n8n-io/n8n/issues/5941)) ([d70a1cb](https://github.com/n8n-io/n8n/commit/d70a1cb0c82ee0a4b92776684c6c9079020d028f))
|
||||
* **HTTP Request Node:** Notice about dev console ([#6516](https://github.com/n8n-io/n8n/issues/6516)) ([d431117](https://github.com/n8n-io/n8n/commit/d431117c9e5db9ff0ec6a1e7371bbf58698957c9))
|
||||
* **Matrix Node:** Allow setting filename if the binary data has none ([#6536](https://github.com/n8n-io/n8n/issues/6536)) ([8b76e98](https://github.com/n8n-io/n8n/commit/8b76e980852062b192a95593035697c43d6f808e))
|
||||
|
||||
|
||||
|
||||
# [0.234.0](https://github.com/n8n-io/n8n/compare/n8n@0.233.0...n8n@0.234.0) (2023-06-22)
|
||||
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ The most important directories:
|
|||
|
||||
## Development setup
|
||||
|
||||
If you want to change or extend n8n you have to make sure that all needed
|
||||
dependencies are installed and the packages get linked correctly. Here a short guide on how that can be done:
|
||||
If you want to change or extend n8n you have to make sure that all the needed
|
||||
dependencies are installed and the packages get linked correctly. Here's a short guide on how that can be done:
|
||||
|
||||
### Requirements
|
||||
|
||||
|
@ -69,7 +69,7 @@ dependencies are installed and the packages get linked correctly. Here a short g
|
|||
|
||||
##### pnpm workspaces
|
||||
|
||||
n8n is split up in different modules which are all in a single mono repository.
|
||||
n8n is split up into different modules which are all in a single mono repository.
|
||||
To facilitate the module management, [pnpm workspaces](https://pnpm.io/workspaces) are used.
|
||||
This automatically sets up file-links between modules which depend on each other.
|
||||
|
||||
|
@ -113,24 +113,24 @@ No additional packages required.
|
|||
|
||||
> **IMPORTANT**: All the steps below have to get executed at least once to get the development setup up and running!
|
||||
|
||||
Now that everything n8n requires to run is installed the actual n8n code can be
|
||||
Now that everything n8n requires to run is installed, the actual n8n code can be
|
||||
checked out and set up:
|
||||
|
||||
1. [Fork](https://guides.github.com/activities/forking/#fork) the n8n repository
|
||||
1. [Fork](https://guides.github.com/activities/forking/#fork) the n8n repository.
|
||||
|
||||
2. Clone your forked repository
|
||||
2. Clone your forked repository:
|
||||
|
||||
```
|
||||
git clone https://github.com/<your_github_username>/n8n.git
|
||||
```
|
||||
|
||||
3. Go into repository folder
|
||||
3. Go into repository folder:
|
||||
|
||||
```
|
||||
cd n8n
|
||||
```
|
||||
|
||||
4. Add the original n8n repository as `upstream` to your forked repository
|
||||
4. Add the original n8n repository as `upstream` to your forked repository:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/n8n-io/n8n.git
|
||||
|
@ -172,13 +172,13 @@ automatically build your code, restart the backend and refresh the frontend
|
|||
pnpm dev
|
||||
```
|
||||
1. Hack, hack, hack
|
||||
1. Check if everything still runs in production mode
|
||||
1. Check if everything still runs in production mode:
|
||||
```
|
||||
pnpm build
|
||||
pnpm start
|
||||
```
|
||||
1. Create tests
|
||||
1. Run all [tests](#test-suite)
|
||||
1. Run all [tests](#test-suite):
|
||||
```
|
||||
pnpm test
|
||||
```
|
||||
|
@ -198,7 +198,7 @@ tests of all packages.
|
|||
|
||||
## Releasing
|
||||
|
||||
To start a release, trigger [this workflow](https://github.com/n8n-io/n8n/actions/workflows/release-create-pr.yml) with the SemVer release type, and select a branch to cut this release from. This workflow will then
|
||||
To start a release, trigger [this workflow](https://github.com/n8n-io/n8n/actions/workflows/release-create-pr.yml) with the SemVer release type, and select a branch to cut this release from. This workflow will then:
|
||||
|
||||
1. Bump versions of packages that have changed or have dependencies that have changed
|
||||
2. Update the Changelog
|
||||
|
@ -206,7 +206,7 @@ To start a release, trigger [this workflow](https://github.com/n8n-io/n8n/action
|
|||
4. Create a new pull-request to track any further changes that need to be included in this release
|
||||
|
||||
Once ready to release, simply merge the pull-request.
|
||||
This triggers [another workflow](https://github.com/n8n-io/n8n/actions/workflows/release-publish.yml), that will
|
||||
This triggers [another workflow](https://github.com/n8n-io/n8n/actions/workflows/release-publish.yml), that will:
|
||||
|
||||
1. Build and publish the packages that have a new version in this release
|
||||
2. Create a new tag, and GitHub release from squashed release commit
|
||||
|
@ -226,4 +226,4 @@ That we do not have any potential problems later it is sadly necessary to sign a
|
|||
|
||||
We used the most simple one that exists. It is from [Indie Open Source](https://indieopensource.com/forms/cla) which uses plain English and is literally only a few lines long.
|
||||
|
||||
A bot will automatically comment on the pull request once it got opened asking for the agreement to be signed. Before it did not get signed it is sadly not possible to merge it in.
|
||||
Once a pull request is opened, an automated bot will promptly leave a comment requesting the agreement to be signed. The pull request can only be merged once the signature is obtained.
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
const fetch = require('node-fetch');
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
const BASE_URL = 'http://localhost:5678';
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: "5hbsdn",
|
||||
projectId: '5hbsdn',
|
||||
retries: {
|
||||
openMode: 0,
|
||||
runMode: 2,
|
||||
|
@ -19,31 +18,5 @@ module.exports = defineConfig({
|
|||
screenshotOnRunFailure: true,
|
||||
experimentalInteractiveRunEvents: true,
|
||||
experimentalSessionAndOrigin: true,
|
||||
|
||||
setupNodeEvents(on, config) {
|
||||
on('task', {
|
||||
reset: () => fetch(BASE_URL + '/e2e/db/reset', { method: 'POST' }),
|
||||
'setup-owner': (payload) => {
|
||||
try {
|
||||
return fetch(BASE_URL + '/e2e/db/setup-owner', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("setup-owner failed with: ", error)
|
||||
return null
|
||||
}
|
||||
},
|
||||
'set-feature': ({ feature, enabled }) => {
|
||||
return fetch(BASE_URL + `/e2e/feature/${feature}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ enabled }),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,32 @@
|
|||
export const BACKEND_BASE_URL = 'http://localhost:5678';
|
||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
|
||||
export const BASE_URL = 'http://localhost:5678';
|
||||
export const BACKEND_BASE_URL = 'http://localhost:5678';
|
||||
export const N8N_AUTH_COOKIE = 'n8n-auth';
|
||||
|
||||
export const DEFAULT_USER_EMAIL = 'nathan@n8n.io';
|
||||
export const DEFAULT_USER_PASSWORD = 'CypressTest123';
|
||||
const DEFAULT_USER_PASSWORD = 'CypressTest123';
|
||||
|
||||
export const INSTANCE_OWNER = {
|
||||
email: 'nathan@n8n.io',
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: randFirstName(),
|
||||
lastName: randLastName(),
|
||||
};
|
||||
|
||||
export const INSTANCE_MEMBERS = [
|
||||
{
|
||||
email: 'rebecca@n8n.io',
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: randFirstName(),
|
||||
lastName: randLastName(),
|
||||
},
|
||||
{
|
||||
email: 'mustafa@n8n.io',
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: randFirstName(),
|
||||
lastName: randLastName(),
|
||||
},
|
||||
];
|
||||
|
||||
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
||||
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Execute Workflow"';
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
|
||||
const email = DEFAULT_USER_EMAIL;
|
||||
const password = DEFAULT_USER_PASSWORD;
|
||||
const firstName = randFirstName();
|
||||
const lastName = randLastName();
|
||||
|
||||
describe('Authentication', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetAll();
|
||||
});
|
||||
|
||||
it('should setup owner', () => {
|
||||
cy.setup({ email, firstName, lastName, password });
|
||||
});
|
||||
|
||||
it('should sign user in', () => {
|
||||
cy.setupOwner({ email, password, firstName, lastName });
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
expect(err.message).to.include('Not logged in');
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
cy.signin({ email, password });
|
||||
});
|
||||
});
|
|
@ -8,10 +8,6 @@ const WorkflowPage = new WorkflowPageClass();
|
|||
const multipleWorkflowsCount = 5;
|
||||
|
||||
describe('Workflows', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(WorkflowsPage.url);
|
||||
});
|
||||
|
|
|
@ -1,22 +1,8 @@
|
|||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import { SettingsLogStreamingPage } from '../pages';
|
||||
|
||||
const email = DEFAULT_USER_EMAIL;
|
||||
const password = DEFAULT_USER_PASSWORD;
|
||||
const firstName = randFirstName();
|
||||
const lastName = randLastName();
|
||||
const settingsLogStreamingPage = new SettingsLogStreamingPage();
|
||||
|
||||
describe('Log Streaming Settings', () => {
|
||||
before(() => {
|
||||
cy.setup({ email, firstName, lastName, password });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.signin({ email, password });
|
||||
});
|
||||
|
||||
it('should show the unlicensed view when the feature is disabled', () => {
|
||||
cy.visit('/settings/log-streaming');
|
||||
settingsLogStreamingPage.getters.getActionBoxUnlicensed().should('be.visible');
|
||||
|
@ -25,7 +11,7 @@ describe('Log Streaming Settings', () => {
|
|||
});
|
||||
|
||||
it('should show the licensed view when the feature is enabled', () => {
|
||||
cy.enableFeature('feat:logStreaming');
|
||||
cy.enableFeature('logStreaming');
|
||||
cy.visit('/settings/log-streaming');
|
||||
settingsLogStreamingPage.getters.getActionBoxLicensed().should('be.visible');
|
||||
settingsLogStreamingPage.getters.getAddFirstDestinationButton().should('be.visible');
|
||||
|
|
|
@ -10,10 +10,6 @@ const WorkflowPage = new WorkflowPageClass();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Undo/Redo', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
@ -125,17 +121,17 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.should('have.attr', 'style', 'left: 740px; top: 360px;');
|
||||
.should('have.attr', 'style', 'left: 740px; top: 320px;');
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.should('have.attr', 'style', 'left: 640px; top: 260px;');
|
||||
.should('have.attr', 'style', 'left: 640px; top: 220px;');
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.should('have.attr', 'style', 'left: 740px; top: 360px;');
|
||||
.should('have.attr', 'style', 'left: 740px; top: 320px;');
|
||||
});
|
||||
|
||||
it('should undo/redo deleting a connection by pressing delete button', () => {
|
||||
|
@ -285,7 +281,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.should('have.attr', 'style', 'left: 420px; top: 260px;');
|
||||
.should('have.attr', 'style', 'left: 420px; top: 220px;');
|
||||
// Third undo: Should enable last node
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
||||
|
@ -298,7 +294,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.should('have.attr', 'style', 'left: 540px; top: 400px;');
|
||||
.should('have.attr', 'style', 'left: 540px; top: 360px;');
|
||||
// Third redo: Should delete the Set node
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
|
|
|
@ -3,16 +3,14 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('Inline expression editor', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||
WorkflowPage.actions.openNode('Hacker News');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
|
||||
cy.on('uncaught:exception', (err) => err.name !== 'ExpressionError');
|
||||
});
|
||||
|
||||
it('should resolve primitive resolvables', () => {
|
||||
|
|
|
@ -11,10 +11,6 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
describe('Canvas Actions', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
@ -103,7 +99,7 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.should('have.attr', 'style', 'left: 860px; top: 260px;');
|
||||
.should('have.attr', 'style', 'left: 860px; top: 220px;');
|
||||
});
|
||||
|
||||
it('should delete connections by pressing the delete button', () => {
|
||||
|
|
|
@ -5,9 +5,7 @@ import {
|
|||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
SET_NODE_NAME,
|
||||
SWITCH_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
MERGE_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
|
@ -21,10 +19,6 @@ const ZOOM_OUT_X2_FACTOR = 0.64;
|
|||
const RENAME_NODE_NAME = 'Something else';
|
||||
|
||||
describe('Canvas Node Manipulation and Navigation', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
@ -168,7 +162,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.should('have.attr', 'style', 'left: 740px; top: 360px;');
|
||||
.should('have.attr', 'style', 'left: 740px; top: 320px;');
|
||||
});
|
||||
|
||||
it('should zoom in', () => {
|
||||
|
|
|
@ -10,10 +10,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Data pinning', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
|
|
@ -4,10 +4,6 @@ const wf = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Data transformation expressions', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wf.actions.visit();
|
||||
|
||||
|
|
|
@ -9,10 +9,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Data mapping', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
|
@ -192,7 +188,11 @@ describe('Data mapping', () => {
|
|||
ndv.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.should('have.text', `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`);
|
||||
ndv.getters.parameterExpressionPreview('value').should('not.exist');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.invoke('text')
|
||||
.invoke('replace', /\u00a0/g, ' ')
|
||||
.should('equal', '[ERROR: no data, execute "Schedule Trigger" node first]');
|
||||
|
||||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.mapDataFromHeader(1, 'value');
|
||||
|
|
|
@ -6,10 +6,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Schedule Trigger node', async () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
|
|
@ -92,10 +92,6 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
|||
};
|
||||
|
||||
describe('Webhook Trigger node', async () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||
import {
|
||||
CredentialsModal,
|
||||
CredentialsPage,
|
||||
|
@ -28,47 +28,12 @@ const workflowPage = new WorkflowPage();
|
|||
const workflowSharingModal = new WorkflowSharingModal();
|
||||
const ndv = new NDV();
|
||||
|
||||
const instanceOwner = {
|
||||
email: `${DEFAULT_USER_EMAIL}one`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U1',
|
||||
};
|
||||
|
||||
const users = [
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}two`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U2',
|
||||
},
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}three`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U3',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Sharing', () => {
|
||||
before(() => {
|
||||
cy.setupOwner(instanceOwner);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
expect(err.message).to.include('Not logged in');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should invite User U2 and User U3 to instance', () => {
|
||||
cy.inviteUsers({ instanceOwner, users });
|
||||
});
|
||||
describe('Sharing', { disableAutoLogin: true }, () => {
|
||||
before(() => cy.enableFeature('sharing', true));
|
||||
|
||||
let workflowW2Url = '';
|
||||
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
|
||||
cy.signin(users[0]);
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
|
@ -87,7 +52,7 @@ describe('Sharing', () => {
|
|||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.openShareModal();
|
||||
workflowSharingModal.actions.addUser(users[1].email);
|
||||
workflowSharingModal.actions.addUser(INSTANCE_MEMBERS[1].email);
|
||||
workflowSharingModal.actions.save();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
|
@ -100,23 +65,23 @@ describe('Sharing', () => {
|
|||
});
|
||||
|
||||
it('should create C2, share C2 with U1 and U2, as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.getters.newCredentialTypeOption('Airtable API').click();
|
||||
credentialsModal.getters.newCredentialTypeOption('Airtable Personal Access Token API').click();
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||
credentialsModal.getters.connectionParameter('Access Token').type('1234567890');
|
||||
credentialsModal.actions.setName('Credential C2');
|
||||
credentialsModal.actions.changeTab('Sharing');
|
||||
credentialsModal.actions.addUser(instanceOwner.email);
|
||||
credentialsModal.actions.addUser(users[0].email);
|
||||
credentialsModal.actions.addUser(INSTANCE_OWNER.email);
|
||||
credentialsModal.actions.addUser(INSTANCE_MEMBERS[0].email);
|
||||
credentialsModal.actions.save();
|
||||
credentialsModal.actions.close();
|
||||
});
|
||||
|
||||
it('should open W1, add node using C2 as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
|
@ -136,7 +101,7 @@ describe('Sharing', () => {
|
|||
});
|
||||
|
||||
it('should not have access to W2, as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
|
||||
cy.visit(workflowW2Url);
|
||||
cy.waitForLoad();
|
||||
|
@ -145,7 +110,7 @@ describe('Sharing', () => {
|
|||
});
|
||||
|
||||
it('should have access to W1, W2, as U1', () => {
|
||||
cy.signin(instanceOwner);
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
|
@ -165,7 +130,7 @@ describe('Sharing', () => {
|
|||
});
|
||||
|
||||
it('should automatically test C2 when opened by U2 sharee', () => {
|
||||
cy.signin(users[0]);
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.credentialCard('Credential C2').click();
|
||||
|
|
|
@ -5,10 +5,6 @@ const wf = new WorkflowPage();
|
|||
const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
||||
|
||||
describe('Workflow tags', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wf.actions.visit();
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import { SettingsSidebar, SettingsUsersPage, WorkflowPage, WorkflowsPage } from '../pages';
|
||||
import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||
import { SettingsUsersPage, WorkflowPage } from '../pages';
|
||||
import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||
|
||||
/**
|
||||
|
@ -15,28 +14,6 @@ import { PersonalSettingsPage } from '../pages/settings-personal';
|
|||
* C2 - Credential owned by User C, shared with User A and User B
|
||||
*/
|
||||
|
||||
const instanceOwner = {
|
||||
email: `${DEFAULT_USER_EMAIL}A`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'A',
|
||||
};
|
||||
|
||||
const users = [
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}B`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'B',
|
||||
},
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}C`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'C',
|
||||
},
|
||||
];
|
||||
|
||||
const updatedPersonalData = {
|
||||
newFirstName: 'Something',
|
||||
newLastName: 'Else',
|
||||
|
@ -49,47 +26,38 @@ const usersSettingsPage = new SettingsUsersPage();
|
|||
const workflowPage = new WorkflowPage();
|
||||
const personalSettingsPage = new PersonalSettingsPage();
|
||||
|
||||
describe('User Management', () => {
|
||||
before(() => {
|
||||
cy.setupOwner(instanceOwner);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
expect(err.message).to.include('Not logged in');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
it(`should invite User B and User C to instance`, () => {
|
||||
cy.inviteUsers({ instanceOwner, users });
|
||||
});
|
||||
describe('User Management', { disableAutoLogin: true }, () => {
|
||||
before(() => cy.enableFeature('sharing'));
|
||||
|
||||
it('should prevent non-owners to access UM settings', () => {
|
||||
usersSettingsPage.actions.loginAndVisit(users[0].email, users[0].password, false);
|
||||
usersSettingsPage.actions.loginAndVisit(
|
||||
INSTANCE_MEMBERS[0].email,
|
||||
INSTANCE_MEMBERS[0].password,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow instance owner to access UM settings', () => {
|
||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
||||
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||
});
|
||||
|
||||
it('should properly render UM settings page for instance owners', () => {
|
||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
||||
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||
// All items in user list should be there
|
||||
usersSettingsPage.getters.userListItems().should('have.length', 3);
|
||||
// List item for current user should have the `Owner` badge
|
||||
usersSettingsPage.getters
|
||||
.userItem(instanceOwner.email)
|
||||
.userItem(INSTANCE_OWNER.email)
|
||||
.find('.n8n-badge:contains("Owner")')
|
||||
.should('exist');
|
||||
// Other users list items should contain action pop-up list
|
||||
usersSettingsPage.getters.userActionsToggle(users[0].email).should('exist');
|
||||
usersSettingsPage.getters.userActionsToggle(users[1].email).should('exist');
|
||||
usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[0].email).should('exist');
|
||||
usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[1].email).should('exist');
|
||||
});
|
||||
|
||||
it('should delete user and their data', () => {
|
||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
||||
usersSettingsPage.actions.opedDeleteDialog(users[0].email);
|
||||
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||
usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[0].email);
|
||||
usersSettingsPage.getters.deleteDataRadioButton().realClick();
|
||||
usersSettingsPage.getters.deleteDataInput().type('delete all data');
|
||||
usersSettingsPage.getters.deleteUserButton().realClick();
|
||||
|
@ -97,8 +65,8 @@ describe('User Management', () => {
|
|||
});
|
||||
|
||||
it('should delete user and transfer their data', () => {
|
||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
||||
usersSettingsPage.actions.opedDeleteDialog(users[1].email);
|
||||
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||
usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[1].email);
|
||||
usersSettingsPage.getters.transferDataRadioButton().realClick();
|
||||
usersSettingsPage.getters.userSelectDropDown().realClick();
|
||||
usersSettingsPage.getters.userSelectOptions().first().realClick();
|
||||
|
@ -107,7 +75,7 @@ describe('User Management', () => {
|
|||
});
|
||||
|
||||
it(`should allow user to change their personal data`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.actions.updateFirstAndLastName(
|
||||
updatedPersonalData.newFirstName,
|
||||
updatedPersonalData.newLastName,
|
||||
|
@ -119,14 +87,14 @@ describe('User Management', () => {
|
|||
});
|
||||
|
||||
it(`shouldn't allow user to set weak password`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
for (let weakPass of updatedPersonalData.invalidPasswords) {
|
||||
personalSettingsPage.actions.tryToSetWeakPassword(instanceOwner.password, weakPass);
|
||||
personalSettingsPage.actions.tryToSetWeakPassword(INSTANCE_OWNER.password, weakPass);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't allow user to change password if old password is wrong`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.actions.updatePassword('iCannotRemember', updatedPersonalData.newPassword);
|
||||
workflowPage.getters
|
||||
.errorToast()
|
||||
|
@ -135,21 +103,21 @@ describe('User Management', () => {
|
|||
});
|
||||
|
||||
it(`should change current user password`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.actions.updatePassword(
|
||||
instanceOwner.password,
|
||||
INSTANCE_OWNER.password,
|
||||
updatedPersonalData.newPassword,
|
||||
);
|
||||
workflowPage.getters.successToast().should('contain', 'Password updated');
|
||||
personalSettingsPage.actions.loginWithNewData(
|
||||
instanceOwner.email,
|
||||
INSTANCE_OWNER.email,
|
||||
updatedPersonalData.newPassword,
|
||||
);
|
||||
});
|
||||
|
||||
it(`shouldn't allow users to set invalid email`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(
|
||||
instanceOwner.email,
|
||||
INSTANCE_OWNER.email,
|
||||
updatedPersonalData.newPassword,
|
||||
);
|
||||
// try without @ part
|
||||
|
@ -160,7 +128,7 @@ describe('User Management', () => {
|
|||
|
||||
it(`should change user email`, () => {
|
||||
personalSettingsPage.actions.loginAndVisit(
|
||||
instanceOwner.email,
|
||||
INSTANCE_OWNER.email,
|
||||
updatedPersonalData.newPassword,
|
||||
);
|
||||
personalSettingsPage.actions.updateEmail(updatedPersonalData.newEmail);
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('Execution', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
|
|
@ -6,24 +6,14 @@ import {
|
|||
NEW_QUERY_AUTH_ACCOUNT_NAME,
|
||||
} from './../constants';
|
||||
import {
|
||||
DEFAULT_USER_EMAIL,
|
||||
DEFAULT_USER_PASSWORD,
|
||||
GMAIL_NODE_NAME,
|
||||
NEW_GOOGLE_ACCOUNT_NAME,
|
||||
NEW_TRELLO_ACCOUNT_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
TRELLO_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
import { CredentialsPage, CredentialsModal, WorkflowPage, NDV } 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';
|
||||
|
||||
const email = DEFAULT_USER_EMAIL;
|
||||
const password = DEFAULT_USER_PASSWORD;
|
||||
const firstName = randFirstName();
|
||||
const lastName = randLastName();
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
@ -32,10 +22,6 @@ const nodeDetailsView = new NDV();
|
|||
const NEW_CREDENTIAL_NAME = 'Something else';
|
||||
|
||||
describe('Credentials', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(credentialsPage.url);
|
||||
});
|
||||
|
|
|
@ -6,10 +6,6 @@ const executionsTab = new WorkflowExecutionsTab();
|
|||
|
||||
// Test suite for executions tab
|
||||
describe('Current Workflow Executions', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
||||
|
@ -36,7 +32,6 @@ describe('Current Workflow Executions', () => {
|
|||
});
|
||||
|
||||
const createMockExecutions = () => {
|
||||
workflowPage.actions.turnOnManualExecutionSaving();
|
||||
executionsTab.actions.createManualExecutions(5);
|
||||
// Make some failed executions by enabling Code node with syntax error
|
||||
executionsTab.actions.toggleNodeEnabled('Error');
|
||||
|
|
|
@ -13,9 +13,6 @@ const workflowPage = new WorkflowPage();
|
|||
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
||||
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
||||
describe('Community Nodes', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
})
|
||||
beforeEach(() => {
|
||||
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
@ -36,6 +33,7 @@ describe('Community Nodes', () => {
|
|||
credentials.push(CustomCredential);
|
||||
})
|
||||
})
|
||||
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
import { VariablesPage } from '../pages/variables';
|
||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
|
||||
const variablesPage = new VariablesPage();
|
||||
|
||||
const email = DEFAULT_USER_EMAIL;
|
||||
const password = DEFAULT_USER_PASSWORD;
|
||||
const firstName = randFirstName();
|
||||
const lastName = randLastName();
|
||||
|
||||
describe('Variables', () => {
|
||||
before(() => {
|
||||
cy.setup({ email, firstName, lastName, password });
|
||||
});
|
||||
|
||||
it('should show the unlicensed action box when the feature is disabled', () => {
|
||||
cy.disableFeature('feat:variables');
|
||||
cy.signin({ email, password });
|
||||
cy.disableFeature('variables', false);
|
||||
cy.visit(variablesPage.url);
|
||||
|
||||
variablesPage.getters.unavailableResourcesList().should('be.visible');
|
||||
|
@ -25,11 +13,10 @@ describe('Variables', () => {
|
|||
|
||||
describe('licensed', () => {
|
||||
before(() => {
|
||||
cy.enableFeature('feat:variables');
|
||||
cy.enableFeature('variables');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.signin({ email, password });
|
||||
cy.intercept('GET', '/rest/variables').as('loadVariables');
|
||||
|
||||
cy.visit(variablesPage.url);
|
||||
|
|
|
@ -5,10 +5,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('NDV', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.renameWorkflow(uuid());
|
||||
|
@ -277,7 +273,11 @@ describe('NDV', () => {
|
|||
.should('equal', 'hovering-item');
|
||||
|
||||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.openNode('Set5');
|
||||
|
||||
ndv.actions.switchInputBranch('True Branch');
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)')
|
||||
ndv.getters.outputTableRow(1)
|
||||
.should('have.text', '8888')
|
||||
.realHover();
|
||||
|
@ -288,16 +288,21 @@ describe('NDV', () => {
|
|||
.realHover();
|
||||
ndv.getters.outputHoveringItem().should('not.exist');
|
||||
|
||||
ndv.actions.switchIntputBranch('False Branch');
|
||||
ndv.actions.switchInputBranch('False Branch');
|
||||
ndv.getters.inputTableRow(1)
|
||||
.should('have.text', '8888')
|
||||
.realHover();
|
||||
|
||||
ndv.actions.changeOutputRunSelector('2 of 2 (4 items)')
|
||||
ndv.getters.outputTableRow(1)
|
||||
.should('have.text', '1111')
|
||||
.realHover();
|
||||
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)')
|
||||
ndv.getters.inputTableRow(1)
|
||||
.should('have.text', '8888')
|
||||
.realHover();
|
||||
ndv.getters.outputHoveringItem().should('have.text', '8888');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('1 of 2 (4 items)')
|
||||
ndv.getters.outputTableRow(1)
|
||||
.should('have.text', '1111')
|
||||
.realHover();
|
||||
// todo there's a bug here need to fix ADO-534
|
||||
// ndv.getters.outputHoveringItem().should('not.exist');
|
||||
});
|
||||
|
|
|
@ -15,10 +15,6 @@ function checkStickiesStyle( top: number, left: number, height: number, width: n
|
|||
}
|
||||
|
||||
describe('Canvas Actions', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
|
@ -94,66 +90,66 @@ describe('Canvas Actions', () => {
|
|||
|
||||
moveSticky({ left: 600, top: 200 });
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="left"]', [100, 100]);
|
||||
checkStickiesStyle(140, 510, 160, 150);
|
||||
checkStickiesStyle(100, 510, 160, 150);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="left"]', [-50, -50]);
|
||||
checkStickiesStyle(140, 466, 160, 194);
|
||||
checkStickiesStyle(100, 466, 160, 194);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [100, 100]); // move away from canvas button
|
||||
checkStickiesStyle(360, 620, 160, 240);
|
||||
checkStickiesStyle(300, 620, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="top"]', [100, 100]);
|
||||
checkStickiesStyle(440, 620, 80, 240);
|
||||
checkStickiesStyle(380, 620, 80, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="top"]', [-50, -50]);
|
||||
checkStickiesStyle(384, 620, 136, 240);
|
||||
checkStickiesStyle(324, 620, 136, 240);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the bottom edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [100, 100]); // move away from canvas button
|
||||
checkStickiesStyle(360, 620, 160, 240);
|
||||
checkStickiesStyle(300, 620, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottom"]', [100, 100]);
|
||||
checkStickiesStyle(360, 620, 254, 240);
|
||||
checkStickiesStyle(300, 620, 254, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottom"]', [-50, -50]);
|
||||
checkStickiesStyle(360, 620, 198, 240);
|
||||
checkStickiesStyle(300, 620, 198, 240);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the bottom right edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [-100, -100]); // move away from canvas button
|
||||
checkStickiesStyle(160, 420, 160, 240);
|
||||
checkStickiesStyle(100, 420, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottomRight"]', [100, 100]);
|
||||
checkStickiesStyle(160, 420, 254, 346);
|
||||
checkStickiesStyle(100, 420, 254, 346);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottomRight"]', [-50, -50]);
|
||||
checkStickiesStyle(160, 420, 198, 302);
|
||||
checkStickiesStyle(100, 420, 198, 302);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top right edge', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topRight"]', [100, 100]);
|
||||
checkStickiesStyle(420, 400, 80, 346);
|
||||
checkStickiesStyle(360, 400, 80, 346);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topRight"]', [-50, -50]);
|
||||
checkStickiesStyle(364, 400, 136, 302);
|
||||
checkStickiesStyle(304, 400, 136, 302);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top left edge, and reach min height/width', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [100, 100]);
|
||||
checkStickiesStyle(420, 490, 80, 150);
|
||||
checkStickiesStyle(360, 490, 80, 150);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-150, -150]);
|
||||
checkStickiesStyle(264, 346, 236, 294);
|
||||
checkStickiesStyle(204, 346, 236, 294);
|
||||
});
|
||||
|
||||
it('sets sticky behind node', () => {
|
||||
|
@ -161,7 +157,7 @@ describe('Canvas Actions', () => {
|
|||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-150, -150]);
|
||||
checkStickiesStyle(184, 256, 316, 384, -121);
|
||||
checkStickiesStyle(124, 256, 316, 384, -121);
|
||||
|
||||
workflowPage.getters.canvasNodes().eq(0)
|
||||
.should(($el) => {
|
||||
|
@ -239,7 +235,7 @@ function addDefaultSticky() {
|
|||
}
|
||||
|
||||
function stickyShouldBePositionedCorrectly(position: Position) {
|
||||
const yOffset = -60;
|
||||
const yOffset = -100;
|
||||
const xOffset = -180;
|
||||
workflowPage.getters.stickies()
|
||||
.should(($el) => {
|
||||
|
|
|
@ -8,10 +8,6 @@ const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
|
|||
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
|
||||
|
||||
describe('Resource Locator', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import {
|
||||
SettingsUsersPage,
|
||||
SignupPage,
|
||||
WorkflowsPage,
|
||||
WorkflowPage,
|
||||
CredentialsPage,
|
||||
CredentialsModal,
|
||||
MessageBox,
|
||||
} from '../pages';
|
||||
import { SettingsUsagePage } from '../pages/settings-usage';
|
||||
|
||||
import { MainSidebar, SettingsSidebar } from '../pages/sidebar';
|
||||
|
||||
const mainSidebar = new MainSidebar();
|
||||
const settingsSidebar = new SettingsSidebar();
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const signupPage = new SignupPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
||||
const settingsUsersPage = new SettingsUsersPage();
|
||||
const settingsUsagePage = new SettingsUsagePage();
|
||||
|
||||
const messageBox = new MessageBox();
|
||||
|
||||
const email = DEFAULT_USER_EMAIL;
|
||||
const password = DEFAULT_USER_PASSWORD;
|
||||
const firstName = randFirstName();
|
||||
const lastName = randLastName();
|
||||
|
||||
describe('Default owner', () => {
|
||||
it('should be able to create workflows', () => {
|
||||
cy.skipSetup();
|
||||
cy.createFixtureWorkflow('Test_workflow_1.json', `Test workflow`);
|
||||
|
||||
// reload page, ensure owner still has access
|
||||
cy.reload();
|
||||
cy.waitForLoad();
|
||||
workflowPage.getters.workflowNameInput().should('contain.value', 'Test workflow');
|
||||
});
|
||||
|
||||
it('should be able to add new credentials', () => {
|
||||
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();
|
||||
|
||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||
|
||||
credentialsModal.actions.setName('My awesome Notion account');
|
||||
credentialsModal.actions.save();
|
||||
|
||||
credentialsModal.actions.close();
|
||||
|
||||
credentialsModal.getters.newCredentialModal().should('not.exist');
|
||||
credentialsModal.getters.editCredentialModal().should('not.exist');
|
||||
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should be able to setup UM from settings', () => {
|
||||
cy.visit('/');
|
||||
mainSidebar.getters.settings().should('be.visible');
|
||||
mainSidebar.actions.goToSettings();
|
||||
cy.url().should('include', settingsUsagePage.url);
|
||||
|
||||
settingsSidebar.actions.goToUsers();
|
||||
cy.url().should('include', settingsUsersPage.url);
|
||||
|
||||
settingsUsersPage.actions.goToOwnerSetup();
|
||||
|
||||
cy.url().should('include', signupPage.url);
|
||||
});
|
||||
|
||||
it('should be able to setup instance and migrate workflows and credentials', () => {
|
||||
cy.setup({ email, firstName, lastName, password }, true);
|
||||
|
||||
messageBox.getters.content().should('contain.text', '1 existing workflow and 1 credential');
|
||||
|
||||
messageBox.actions.confirm();
|
||||
cy.wait('@setupRequest');
|
||||
cy.url().should('include', settingsUsersPage.url);
|
||||
settingsSidebar.actions.back();
|
||||
|
||||
cy.url().should('include', workflowsPage.url);
|
||||
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('can click back to main menu and have migrated credential after setup', () => {
|
||||
cy.signin({ email, password });
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
mainSidebar.actions.goToCredentials();
|
||||
|
||||
cy.url().should('include', credentialsPage.url);
|
||||
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
});
|
||||
});
|
|
@ -7,10 +7,6 @@ const WorkflowPage = new WorkflowPageClass();
|
|||
const NDVModal = new NDV();
|
||||
|
||||
describe('Node Creator', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
@ -271,7 +267,7 @@ describe('Node Creator', () => {
|
|||
NDVModal.actions.close();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.addNodeBetweenNodes('n8n', 'n8n1', 'Item Lists')
|
||||
WorkflowPage.actions.addNodeBetweenNodes('n8n', 'n8n1', 'Item Lists', 'Summarize')
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
})
|
||||
});
|
||||
|
|
|
@ -5,10 +5,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('NDV', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.renameWorkflow(uuid());
|
||||
|
@ -68,15 +64,15 @@ describe('NDV', () => {
|
|||
|
||||
it('should show validation errors only after blur or re-opening of NDV', () => {
|
||||
workflowPage.actions.addNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('Airtable', true, true, 'Read data from a table');
|
||||
workflowPage.actions.addNodeToCanvas('Airtable', true, true, 'Search records');
|
||||
ndv.getters.container().should('be.visible');
|
||||
cy.get('.has-issues').should('have.length', 0);
|
||||
// cy.get('.has-issues').should('have.length', 0);
|
||||
ndv.getters.parameterInput('table').find('input').eq(1).focus().blur();
|
||||
ndv.getters.parameterInput('application').find('input').eq(1).focus().blur();
|
||||
cy.get('.has-issues').should('have.length', 2);
|
||||
ndv.getters.parameterInput('base').find('input').eq(1).focus().blur();
|
||||
cy.get('.has-issues').should('have.length', 0);
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.openNode('Airtable');
|
||||
cy.get('.has-issues').should('have.length', 3);
|
||||
cy.get('.has-issues').should('have.length', 2);
|
||||
cy.get('[class*=hasIssues]').should('have.length', 1);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ const WorkflowPage = new WorkflowPageClass();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Code node', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('should execute the placeholder in all-items mode successfully', () => {
|
||||
|
@ -20,7 +20,6 @@ describe('Code node', () => {
|
|||
});
|
||||
|
||||
it('should execute the placeholder in each-item mode successfully', () => {
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
WorkflowPage.actions.addNodeToCanvas('Code');
|
||||
WorkflowPage.actions.openNode('Code');
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||
|
||||
const NEW_WORKFLOW_NAME = 'Something else';
|
||||
const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json';
|
||||
|
@ -12,12 +13,9 @@ const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
|||
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const WorkflowPages = new WorkflowsPageClass();
|
||||
|
||||
describe('Workflow Actions', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
@ -66,6 +64,42 @@ describe('Workflow Actions', () => {
|
|||
.should('eq', NEW_WORKFLOW_NAME);
|
||||
});
|
||||
|
||||
it('should not save workflow if canvas is loading', () => {
|
||||
let interceptCalledCount = 0;
|
||||
|
||||
// There's no way in Cypress to check if intercept was not called
|
||||
// so we'll count the number of times it was called
|
||||
cy.intercept('PATCH', '/rest/workflows/*', () => {
|
||||
interceptCalledCount++;
|
||||
}).as('saveWorkflow');
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
cy.intercept(
|
||||
{
|
||||
url: '/rest/workflows/*',
|
||||
method: 'GET',
|
||||
middleware: true,
|
||||
},
|
||||
(req) => {
|
||||
// Delay the response to give time for the save to be triggered
|
||||
req.on('response', async (res) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
res.send();
|
||||
})
|
||||
}
|
||||
)
|
||||
cy.reload();
|
||||
cy.get('.el-loading-mask').should('exist');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(0));
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.wait('@saveWorkflow');
|
||||
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(1));
|
||||
})
|
||||
it('should copy nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -110,64 +144,70 @@ describe('Workflow Actions', () => {
|
|||
});
|
||||
|
||||
it('should update workflow settings', () => {
|
||||
WorkflowPage.actions.visit();
|
||||
// Open settings dialog
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
WorkflowPage.getters.workflowMenu().should('be.visible');
|
||||
WorkflowPage.getters.workflowMenu().click();
|
||||
WorkflowPage.getters.workflowMenuItemSettings().should('be.visible');
|
||||
WorkflowPage.getters.workflowMenuItemSettings().click();
|
||||
// Change all settings
|
||||
WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', 7);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsErrorWorkflowSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist');
|
||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveFiledExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveFiledExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveExecutionProgressSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveExecutionProgressSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click();
|
||||
WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1');
|
||||
// Save settings
|
||||
WorkflowPage.getters.workflowSettingsSaveButton().click();
|
||||
WorkflowPage.getters.workflowSettingsModal().should('not.exist');
|
||||
WorkflowPage.getters.successToast().should('exist');
|
||||
cy.visit(WorkflowPages.url);
|
||||
WorkflowPages.getters.workflowCards().then((cards) => {
|
||||
const totalWorkflows = cards.length;
|
||||
|
||||
WorkflowPage.actions.visit();
|
||||
// Open settings dialog
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
WorkflowPage.getters.workflowMenu().should('be.visible');
|
||||
WorkflowPage.getters.workflowMenu().click();
|
||||
WorkflowPage.getters.workflowMenuItemSettings().should('be.visible');
|
||||
WorkflowPage.getters.workflowMenuItemSettings().click();
|
||||
// Change all settings
|
||||
// totalWorkflows + 1 (current workflow) + 1 (no workflow option)
|
||||
WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', totalWorkflows + 2);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsErrorWorkflowSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist');
|
||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveFiledExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveFiledExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveExecutionProgressSelect()
|
||||
.find('li')
|
||||
.should('have.length', 3);
|
||||
WorkflowPage.getters
|
||||
.workflowSettingsSaveExecutionProgressSelect()
|
||||
.find('li')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click();
|
||||
WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1');
|
||||
// Save settings
|
||||
WorkflowPage.getters.workflowSettingsSaveButton().click();
|
||||
WorkflowPage.getters.workflowSettingsModal().should('not.exist');
|
||||
WorkflowPage.getters.successToast().should('exist');
|
||||
})
|
||||
});
|
||||
|
||||
it('should not be able to delete unsaved workflow', () => {
|
||||
|
|
|
@ -4,8 +4,8 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('HTTP Request node', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('should make a request with a URL and receive a response', () => {
|
||||
|
|
|
@ -3,16 +3,14 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('Expression editor modal', () => {
|
||||
before(() => {
|
||||
cy.skipSetup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||
WorkflowPage.actions.openNode('Hacker News');
|
||||
WorkflowPage.actions.openExpressionEditorModal();
|
||||
|
||||
cy.on('uncaught:exception', (err) => err.name !== 'ExpressionError');
|
||||
});
|
||||
|
||||
it('should resolve primitive resolvables', () => {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
export * from './base';
|
||||
export * from './credentials';
|
||||
export * from './signin';
|
||||
export * from './signup';
|
||||
export * from './workflows';
|
||||
export * from './workflow';
|
||||
export * from './modals';
|
||||
|
|
|
@ -154,7 +154,7 @@ export class NDV extends BasePage {
|
|||
switchOutputBranch: (name: string) => {
|
||||
this.getters.outputBranches().get('span').contains(name).click();
|
||||
},
|
||||
switchIntputBranch: (name: string) => {
|
||||
switchInputBranch: (name: string) => {
|
||||
this.getters.inputBranches().get('span').contains(name).click();
|
||||
},
|
||||
setRLCValue: (paramName: string, value: string) => {
|
||||
|
|
|
@ -26,9 +26,5 @@ export class MainSidebar extends BasePage {
|
|||
openUserMenu: () => {
|
||||
this.getters.userMenu().find('[role="button"]').last().click();
|
||||
},
|
||||
signout: () => {
|
||||
this.actions.openUserMenu();
|
||||
cy.getByTestId('workflow-menu-item-logout').click();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
export class SigninPage extends BasePage {
|
||||
url = '/signin';
|
||||
getters = {
|
||||
form: () => cy.getByTestId('auth-form'),
|
||||
email: () => cy.getByTestId('email'),
|
||||
password: () => cy.getByTestId('password'),
|
||||
submit: () => cy.get('button'),
|
||||
};
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
// todo rename to setup
|
||||
export class SignupPage extends BasePage {
|
||||
url = '/setup';
|
||||
getters = {
|
||||
form: () => cy.getByTestId('auth-form'),
|
||||
email: () => cy.getByTestId('email'),
|
||||
firstName: () => cy.getByTestId('firstName'),
|
||||
lastName: () => cy.getByTestId('lastName'),
|
||||
password: () => cy.getByTestId('password'),
|
||||
submit: () => cy.get('button'),
|
||||
skip: () => cy.get('a'),
|
||||
};
|
||||
}
|
|
@ -178,7 +178,7 @@ export class WorkflowPage extends BasePage {
|
|||
},
|
||||
saveWorkflowUsingKeyboardShortcut: () => {
|
||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||
cy.get('body').type('{meta}', { release: false }).type('s');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
},
|
||||
deleteNode: (name: string) => {
|
||||
this.getters.canvasNodeByName(name).first().click();
|
||||
|
@ -242,14 +242,15 @@ export class WorkflowPage extends BasePage {
|
|||
executeWorkflow: () => {
|
||||
this.getters.executeWorkflowButton().click();
|
||||
},
|
||||
addNodeBetweenNodes: (sourceNodeName: string, targetNodeName: string, newNodeName: string) => {
|
||||
addNodeBetweenNodes: (sourceNodeName: string, targetNodeName: string, newNodeName: string, action?: string) => {
|
||||
this.getters.getConnectionBetweenNodes(sourceNodeName, targetNodeName).first().realHover();
|
||||
this.getters
|
||||
.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName)
|
||||
.find('.add')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
this.actions.addNodeToCanvas(newNodeName, false);
|
||||
|
||||
this.actions.addNodeToCanvas(newNodeName, false, false, action);
|
||||
},
|
||||
deleteNodeBetweenNodes: (
|
||||
sourceNodeName: string,
|
||||
|
@ -281,23 +282,5 @@ export class WorkflowPage extends BasePage {
|
|||
.type(content)
|
||||
.type('{esc}');
|
||||
},
|
||||
turnOnManualExecutionSaving: () => {
|
||||
this.getters.workflowMenu().click();
|
||||
this.getters.workflowMenuItemSettings().click();
|
||||
cy.get('.el-loading-mask').should('not.be.visible');
|
||||
this.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li:contains("Yes")')
|
||||
.click({ force: true });
|
||||
|
||||
this.getters.workflowSettingsSaveManualExecutionsSelect().should('contain', 'Yes');
|
||||
this.getters.workflowSettingsSaveButton().click();
|
||||
this.getters.successToast().should('exist');
|
||||
|
||||
this.getters.workflowMenu().click();
|
||||
this.getters.workflowMenuItemSettings().click();
|
||||
this.getters.workflowSettingsSaveManualExecutionsSelect().should('contain', 'Yes');
|
||||
this.getters.workflowSettingsSaveButton().click();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,8 +36,10 @@ export class WorkflowsPage extends BasePage {
|
|||
cy.visit(this.url);
|
||||
this.getters.workflowCardActions(name).click();
|
||||
this.getters.workflowDeleteButton().click();
|
||||
cy.intercept('DELETE', '/rest/workflows/*').as('deleteWorkflow');
|
||||
|
||||
cy.get('button').contains('delete').click();
|
||||
cy.wait('@deleteWorkflow');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,32 +1,6 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
import 'cypress-real-events';
|
||||
import { WorkflowsPage, SigninPage, SignupPage, SettingsUsersPage, WorkflowPage } from '../pages';
|
||||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
import { MessageBox } from '../pages/modals/message-box';
|
||||
import { WorkflowPage } from '../pages';
|
||||
import { BASE_URL, N8N_AUTH_COOKIE } from '../constants';
|
||||
|
||||
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
||||
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
||||
|
@ -59,164 +33,35 @@ Cypress.Commands.add('waitForLoad', (waitForIntercepts = true) => {
|
|||
// we can't set them up here because at this point it would be too late
|
||||
// and the requests would already have been made
|
||||
if (waitForIntercepts) {
|
||||
cy.wait(['@loadSettings', '@loadLogin']);
|
||||
cy.wait(['@loadSettings']);
|
||||
}
|
||||
cy.getByTestId('node-view-loader', { timeout: 20000 }).should('not.exist');
|
||||
cy.get('.el-loading-mask', { timeout: 20000 }).should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signin', ({ email, password }) => {
|
||||
const signinPage = new SigninPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
cy.session(
|
||||
[email, password],
|
||||
() => {
|
||||
cy.visit(signinPage.url);
|
||||
|
||||
signinPage.getters.form().within(() => {
|
||||
signinPage.getters.email().type(email);
|
||||
signinPage.getters.password().type(password);
|
||||
signinPage.getters.submit().click();
|
||||
});
|
||||
|
||||
// we should be redirected to /workflows
|
||||
cy.url().should('include', workflowsPage.url);
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
cy.session([email, password], () => cy.request('POST', '/rest/login', { email, password }), {
|
||||
validate() {
|
||||
cy.getCookie(N8N_AUTH_COOKIE).should('exist');
|
||||
},
|
||||
{
|
||||
validate() {
|
||||
cy.getCookie(N8N_AUTH_COOKIE).should('exist');
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signout', () => {
|
||||
cy.visit('/signout');
|
||||
cy.waitForLoad();
|
||||
cy.url().should('include', '/signin');
|
||||
cy.request('POST', '/rest/logout');
|
||||
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signup', ({ firstName, lastName, password, url }) => {
|
||||
const signupPage = new SignupPage();
|
||||
|
||||
cy.visit(url);
|
||||
|
||||
signupPage.getters.form().within(() => {
|
||||
cy.url().then((url) => {
|
||||
cy.intercept('/rest/users/*').as('userSignup')
|
||||
signupPage.getters.firstName().type(firstName);
|
||||
signupPage.getters.lastName().type(lastName);
|
||||
signupPage.getters.password().type(password);
|
||||
signupPage.getters.submit().click();
|
||||
cy.wait('@userSignup');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setup', ({ email, firstName, lastName, password }, skipIntercept = false) => {
|
||||
const signupPage = new SignupPage();
|
||||
|
||||
cy.intercept('GET', signupPage.url).as('setupPage');
|
||||
cy.visit(signupPage.url);
|
||||
cy.wait('@setupPage');
|
||||
|
||||
signupPage.getters.form().within(() => {
|
||||
cy.url().then((url) => {
|
||||
if (url.includes(signupPage.url)) {
|
||||
signupPage.getters.email().type(email);
|
||||
signupPage.getters.firstName().type(firstName);
|
||||
signupPage.getters.lastName().type(lastName);
|
||||
signupPage.getters.password().type(password);
|
||||
|
||||
cy.intercept('POST', '/rest/owner/setup').as('setupRequest');
|
||||
signupPage.getters.submit().click();
|
||||
|
||||
if(!skipIntercept) {
|
||||
cy.wait('@setupRequest');
|
||||
}
|
||||
} else {
|
||||
cy.log('User already signed up');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('interceptREST', (method, url) => {
|
||||
cy.intercept(method, `http://localhost:5678/rest${url}`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('inviteUsers', ({ instanceOwner, users }) => {
|
||||
const settingsUsersPage = new SettingsUsersPage();
|
||||
const setFeature = (feature: string, enabled: boolean) =>
|
||||
cy.request('PATCH', `${BASE_URL}/rest/e2e/feature`, { feature: `feat:${feature}`, enabled });
|
||||
|
||||
cy.signin(instanceOwner);
|
||||
|
||||
users.forEach((user) => {
|
||||
cy.signin(instanceOwner);
|
||||
cy.visit(settingsUsersPage.url);
|
||||
|
||||
cy.interceptREST('POST', '/users').as('inviteUser');
|
||||
|
||||
settingsUsersPage.getters.inviteButton().click();
|
||||
settingsUsersPage.getters.inviteUsersModal().within((modal) => {
|
||||
settingsUsersPage.getters.inviteUsersModalEmailsInput().type(user.email).type('{enter}');
|
||||
});
|
||||
|
||||
cy.wait('@inviteUser').then((interception) => {
|
||||
const inviteLink = interception.response!.body.data[0].user.inviteAcceptUrl;
|
||||
cy.log(JSON.stringify(interception.response!.body.data[0].user));
|
||||
cy.log(inviteLink);
|
||||
cy.signout();
|
||||
cy.signup({ ...user, url: inviteLink });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('skipSetup', () => {
|
||||
const signupPage = new SignupPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const Confirmation = new MessageBox();
|
||||
|
||||
cy.intercept('GET', signupPage.url).as('setupPage');
|
||||
cy.visit(signupPage.url);
|
||||
cy.wait('@setupPage');
|
||||
|
||||
signupPage.getters.form().within(() => {
|
||||
cy.url().then((url) => {
|
||||
if (url.endsWith(signupPage.url)) {
|
||||
signupPage.getters.skip().click();
|
||||
|
||||
Confirmation.getters.header().should('contain.text', 'Skip owner account setup?');
|
||||
Confirmation.actions.confirm();
|
||||
|
||||
// we should be redirected to empty canvas
|
||||
cy.intercept('GET', '/rest/workflows/new').as('loading');
|
||||
cy.url().should('include', workflowPage.url);
|
||||
cy.wait('@loading');
|
||||
} else {
|
||||
cy.log('User already signed up');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('resetAll', () => {
|
||||
cy.task('reset');
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setupOwner', (payload) => {
|
||||
cy.task('setup-owner', payload);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('enableFeature', (feature) => {
|
||||
cy.task('set-feature', { feature, enabled: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add('disableFeature', (feature) => {
|
||||
cy.task('set-feature', { feature, enabled: false });
|
||||
});
|
||||
Cypress.Commands.add('enableFeature', (feature: string) => setFeature(feature, true));
|
||||
Cypress.Commands.add('disableFeature', (feature): string => setFeature(feature, false));
|
||||
|
||||
Cypress.Commands.add('grantBrowserPermissions', (...permissions: string[]) => {
|
||||
if (Cypress.isBrowser('chrome')) {
|
||||
|
@ -256,7 +101,7 @@ Cypress.Commands.add('drag', (selector, pos, options) => {
|
|||
|
||||
const originalLocation = Cypress.$(selector)[index].getBoundingClientRect();
|
||||
|
||||
element.trigger('mousedown');
|
||||
element.trigger('mousedown', { force: true });
|
||||
element.trigger('mousemove', {
|
||||
which: 1,
|
||||
pageX: options?.abs ? xDiff : originalLocation.right + xDiff,
|
||||
|
|
|
@ -1,28 +1,19 @@
|
|||
// ***********************************************************
|
||||
// This example support/e2e.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
import { BASE_URL, INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||
import './commands';
|
||||
|
||||
before(() => {
|
||||
cy.resetAll();
|
||||
cy.request('POST', `${BASE_URL}/rest/e2e/reset`, {
|
||||
owner: INSTANCE_OWNER,
|
||||
members: INSTANCE_MEMBERS,
|
||||
});
|
||||
});
|
||||
|
||||
// Load custom nodes and credentials fixtures
|
||||
beforeEach(() => {
|
||||
if (!cy.config('disableAutoLogin')) {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
}
|
||||
|
||||
cy.intercept('GET', '/rest/settings').as('loadSettings');
|
||||
cy.intercept('GET', '/rest/login').as('loadLogin');
|
||||
|
||||
// Always intercept the request to test credentials and return a success
|
||||
cy.intercept('POST', '/rest/credentials/test', {
|
||||
|
|
|
@ -8,25 +8,14 @@ interface SigninPayload {
|
|||
password: string;
|
||||
}
|
||||
|
||||
interface SetupPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
interface SignupPayload extends SetupPayload {
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface InviteUsersPayload {
|
||||
instanceOwner: SigninPayload;
|
||||
users: SetupPayload[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface SuiteConfigOverrides {
|
||||
disableAutoLogin: boolean;
|
||||
}
|
||||
|
||||
interface Chainable {
|
||||
config(key: keyof SuiteConfigOverrides): boolean;
|
||||
getByTestId(
|
||||
selector: string,
|
||||
...args: (Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined)[]
|
||||
|
@ -35,13 +24,7 @@ declare global {
|
|||
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
||||
signin(payload: SigninPayload): void;
|
||||
signout(): void;
|
||||
signup(payload: SignupPayload): void;
|
||||
setup(payload: SetupPayload, skipIntercept?: boolean): void;
|
||||
setupOwner(payload: SetupPayload): void;
|
||||
inviteUsers(payload: InviteUsersPayload): void;
|
||||
interceptREST(method: string, url: string): Chainable<Interception>;
|
||||
skipSetup(): void;
|
||||
resetAll(): void;
|
||||
enableFeature(feature: string): void;
|
||||
disableFeature(feature: string): void;
|
||||
waitForLoad(waitForIntercepts?: boolean): void;
|
||||
|
|
|
@ -11,12 +11,6 @@ N8N_PATH=/app1/
|
|||
# DOMAIN_NAME and SUBDOMAIN combined decide where n8n will be reachable from
|
||||
# above example would result in: https://example.com/n8n/
|
||||
|
||||
# The user name to use for autentication - IMPORTANT ALWAYS CHANGE!
|
||||
N8N_BASIC_AUTH_USER=user
|
||||
|
||||
# The password to use for autentication - IMPORTANT ALWAYS CHANGE!
|
||||
N8N_BASIC_AUTH_PASSWORD=password
|
||||
|
||||
# Optional timezone to set which gets used by Cron-Node by default
|
||||
# If not set New York time will be used
|
||||
GENERIC_TIMEZONE=Europe/Berlin
|
||||
|
|
|
@ -41,9 +41,6 @@ services:
|
|||
- traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
|
||||
- traefik.http.middlewares.n8n.headers.STSPreload=true
|
||||
environment:
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER
|
||||
- N8N_BASIC_AUTH_PASSWORD
|
||||
- N8N_HOST=${DOMAIN_NAME}
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=https
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
MARIADB_ROOT_PASSWORD=changePassword
|
||||
|
||||
MARIADB_DATABASE=n8n
|
||||
MARIADB_USER=changeUser
|
||||
MARIADB_PASSWORD=changePassword
|
||||
|
||||
N8N_BASIC_AUTH_USER=changeUser
|
||||
N8N_BASIC_AUTH_PASSWORD=changePassword
|
|
@ -1,24 +0,0 @@
|
|||
# n8n with MariaDB
|
||||
|
||||
Starts n8n with MariaDB as database.
|
||||
|
||||
## Start
|
||||
|
||||
To start n8n with MariaDB simply start docker-compose by executing the following
|
||||
command in the current folder.
|
||||
|
||||
**IMPORTANT:** But before you do that change the default users and passwords in the [`.env`](.env) file!
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To stop it execute:
|
||||
|
||||
```
|
||||
docker-compose stop
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The default name of the database, user and password for MariaDB can be changed in the [`.env`](.env) file in the current directory.
|
|
@ -1,43 +0,0 @@
|
|||
version: '3.8'
|
||||
|
||||
volumes:
|
||||
db_storage:
|
||||
n8n_storage:
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10.7
|
||||
restart: always
|
||||
environment:
|
||||
- MARIADB_ROOT_PASSWORD
|
||||
- MARIADB_DATABASE
|
||||
- MARIADB_USER
|
||||
- MARIADB_PASSWORD
|
||||
- MARIADB_MYSQL_LOCALHOST_USER=true
|
||||
volumes:
|
||||
- db_storage:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: "/usr/bin/mysql --user=${MARIADB_USER} --password=${MARIADB_PASSWORD} --execute 'SELECT 1;'"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
restart: always
|
||||
environment:
|
||||
- DB_TYPE=mariadb
|
||||
- DB_MYSQLDB_HOST=db
|
||||
- DB_MYSQLDB_DATABASE=${MARIADB_DATABASE}
|
||||
- DB_MYSQLDB_USER=${MARIADB_USER}
|
||||
- DB_MYSQLDB_PASSWORD=${MARIADB_PASSWORD}
|
||||
ports:
|
||||
- 5678:5678
|
||||
links:
|
||||
- db
|
||||
volumes:
|
||||
- n8n_storage:/home/node/.n8n
|
||||
command: n8n start --tunnel
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
|
@ -4,6 +4,3 @@ POSTGRES_DB=n8n
|
|||
|
||||
POSTGRES_NON_ROOT_USER=changeUser
|
||||
POSTGRES_NON_ROOT_PASSWORD=changePassword
|
||||
|
||||
N8N_BASIC_AUTH_USER=changeUser
|
||||
N8N_BASIC_AUTH_PASSWORD=changePassword
|
||||
|
|
|
@ -33,16 +33,12 @@ services:
|
|||
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
||||
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
|
||||
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER
|
||||
- N8N_BASIC_AUTH_PASSWORD
|
||||
ports:
|
||||
- 5678:5678
|
||||
links:
|
||||
- postgres
|
||||
volumes:
|
||||
- n8n_storage:/home/node/.n8n
|
||||
command: /bin/sh -c "n8n start --tunnel"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
|
@ -4,6 +4,3 @@ POSTGRES_DB=n8n
|
|||
|
||||
POSTGRES_NON_ROOT_USER=changeUser
|
||||
POSTGRES_NON_ROOT_PASSWORD=changePassword
|
||||
|
||||
N8N_BASIC_AUTH_USER=changeUser
|
||||
N8N_BASIC_AUTH_PASSWORD=changePassword
|
||||
|
|
|
@ -7,6 +7,7 @@ volumes:
|
|||
|
||||
x-shared: &shared
|
||||
restart: always
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
environment:
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=postgres
|
||||
|
@ -17,9 +18,6 @@ x-shared: &shared
|
|||
- EXECUTIONS_MODE=queue
|
||||
- QUEUE_BULL_REDIS_HOST=redis
|
||||
- QUEUE_HEALTH_CHECK_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER
|
||||
- N8N_BASIC_AUTH_PASSWORD
|
||||
links:
|
||||
- postgres
|
||||
- redis
|
||||
|
@ -63,14 +61,11 @@ services:
|
|||
|
||||
n8n:
|
||||
<<: *shared
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
command: /bin/sh -c "n8n start --tunnel"
|
||||
ports:
|
||||
- 5678:5678
|
||||
|
||||
n8n-worker:
|
||||
<<: *shared
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
command: /bin/sh -c "sleep 5; n8n worker"
|
||||
command: worker
|
||||
depends_on:
|
||||
- n8n
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
ARG NODE_VERSION=16
|
||||
ARG NODE_VERSION=18
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
|
||||
WORKDIR /home/node
|
||||
COPY .npmrc /usr/local/etc/npmrc
|
||||
|
||||
RUN \
|
||||
apk add --update git openssh graphicsmagick tini tzdata ca-certificates && \
|
||||
npm install -g npm@8.19.2 full-icu && \
|
||||
apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc6-compat && \
|
||||
npm install -g npm@9.5.1 full-icu && \
|
||||
rm -rf /var/cache/apk/* /root/.npm /tmp/* && \
|
||||
# Install fonts
|
||||
apk --no-cache add --virtual fonts msttcorefonts-installer fontconfig && \
|
||||
|
|
|
@ -8,7 +8,7 @@ COPY --chown=node:node scripts ./scripts
|
|||
COPY --chown=node:node packages ./packages
|
||||
COPY --chown=node:node patches ./patches
|
||||
|
||||
RUN apk add --update libc6-compat jq
|
||||
RUN apk add --update jq
|
||||
RUN corepack enable && corepack prepare --activate
|
||||
USER node
|
||||
|
||||
|
@ -28,7 +28,8 @@ RUN rm -rf patches .npmrc *.yaml node_modules/.cache packages/**/node_modules/.c
|
|||
FROM n8nio/base:${NODE_VERSION}
|
||||
COPY --from=builder /home/node /usr/local/lib/node_modules/n8n
|
||||
RUN ln -s /usr/local/lib/node_modules/n8n/packages/cli/bin/n8n /usr/local/bin/n8n
|
||||
COPY docker/images/n8n-custom/docker-entrypoint.sh /
|
||||
|
||||
COPY docker/images/n8n/docker-entrypoint.sh /
|
||||
|
||||
RUN \
|
||||
mkdir .n8n && \
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# Got started with arguments
|
||||
node "$@"
|
||||
else
|
||||
# Got started without arguments
|
||||
n8n
|
||||
fi
|
|
@ -1,24 +0,0 @@
|
|||
FROM node:16
|
||||
|
||||
ARG N8N_VERSION
|
||||
|
||||
RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi
|
||||
|
||||
ENV N8N_VERSION=${N8N_VERSION}
|
||||
RUN \
|
||||
apt-get update && \
|
||||
apt-get -y install graphicsmagick gosu git
|
||||
|
||||
# Set a custom user to not have n8n run as root
|
||||
USER root
|
||||
|
||||
RUN npm_config_user=root npm install -g npm@8.19.2 full-icu n8n@${N8N_VERSION}
|
||||
|
||||
ENV NODE_ICU_DATA /usr/local/lib/node_modules/full-icu
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
EXPOSE 5678/tcp
|
|
@ -1,20 +0,0 @@
|
|||
## n8n - Debian Docker Image
|
||||
|
||||
Dockerfile to build n8n with Debian.
|
||||
|
||||
For information about how to run n8n with Docker check the generic
|
||||
[Docker-Readme](https://github.com/n8n-io/n8n/tree/master/docker/images/n8n/README.md)
|
||||
|
||||
```
|
||||
docker build --build-arg N8N_VERSION=<VERSION> -t n8nio/n8n:<VERSION> .
|
||||
|
||||
# For example:
|
||||
docker build --build-arg N8N_VERSION=0.43.0 -t n8nio/n8n:0.43.0-debian .
|
||||
```
|
||||
|
||||
```
|
||||
docker run -it --rm \
|
||||
--name n8n \
|
||||
-p 5678:5678 \
|
||||
n8nio/n8n:0.43.0-debian
|
||||
```
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ -d /root/.n8n ] ; then
|
||||
chmod o+rx /root
|
||||
chown -R node /root/.n8n
|
||||
ln -s /root/.n8n /home/node/
|
||||
fi
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# Got started with arguments
|
||||
exec gosu node "$@"
|
||||
else
|
||||
# Got started without arguments
|
||||
exec gosu node n8n
|
||||
fi
|
|
@ -1,24 +0,0 @@
|
|||
FROM richxsl/rhel7
|
||||
|
||||
ARG N8N_VERSION
|
||||
|
||||
RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi
|
||||
|
||||
ENV N8N_VERSION=${N8N_VERSION}
|
||||
RUN \
|
||||
yum install -y gcc-c++ make
|
||||
|
||||
RUN \
|
||||
curl -sL https://rpm.nodesource.com/setup_12.x | sudo -E bash -
|
||||
|
||||
RUN \
|
||||
sudo yum install nodejs
|
||||
|
||||
# Set a custom user to not have n8n run as root
|
||||
USER root
|
||||
|
||||
RUN npm_config_user=root npm install -g npm@8.19.2 n8n@${N8N_VERSION}
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
CMD "n8n"
|
|
@ -1,15 +0,0 @@
|
|||
## Build Docker-Image
|
||||
|
||||
```
|
||||
docker build --build-arg N8N_VERSION=<VERSION> -t n8nio/n8n:<VERSION> .
|
||||
|
||||
# For example:
|
||||
docker build --build-arg N8N_VERSION=0.36.1 -t n8nio/n8n:0.36.1-rhel7 .
|
||||
```
|
||||
|
||||
```
|
||||
docker run -it --rm \
|
||||
--name n8n \
|
||||
-p 5678:5678 \
|
||||
n8nio/n8n:0.25.0-ubuntu
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
ARG NODE_VERSION=16
|
||||
ARG NODE_VERSION=18
|
||||
FROM n8nio/base:${NODE_VERSION}
|
||||
|
||||
ARG N8N_VERSION
|
||||
|
@ -18,9 +18,10 @@ RUN set -eux; \
|
|||
find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm && \
|
||||
rm -rf /root/.npm
|
||||
|
||||
# Set a custom user to not have n8n run as root
|
||||
USER root
|
||||
WORKDIR /data
|
||||
RUN apk --no-cache add su-exec
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY docker-entrypoint.sh /
|
||||
|
||||
RUN \
|
||||
mkdir .n8n && \
|
||||
chown node:node .n8n
|
||||
USER node
|
||||
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
||||
|
|
|
@ -8,21 +8,27 @@ n8n is an extendable workflow automation tool. With a [fair-code](http://faircod
|
|||
|
||||
## Contents
|
||||
|
||||
- [Demo](#demo)
|
||||
- [Available integrations](#available-integrations)
|
||||
- [Documentation](#documentation)
|
||||
- [Start n8n in Docker](#start-n8n-in-docker)
|
||||
- [Start with tunnel](#start-with-tunnel)
|
||||
- [Securing n8n](#securing-n8n)
|
||||
- [Persist data](#persist-data)
|
||||
- [Passing Sensitive Data via File](#passing-sensitive-data-via-file)
|
||||
- [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance)
|
||||
- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt)
|
||||
- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
|
||||
- [Support](#support)
|
||||
- [Jobs](#jobs)
|
||||
- [Upgrading](#upgrading)
|
||||
- [License](#license)
|
||||
- [n8n - Workflow automation tool](#n8n---workflow-automation-tool)
|
||||
- [Contents](#contents)
|
||||
- [Demo](#demo)
|
||||
- [Available integrations](#available-integrations)
|
||||
- [Documentation](#documentation)
|
||||
- [Start n8n in Docker](#start-n8n-in-docker)
|
||||
- [Start with tunnel](#start-with-tunnel)
|
||||
- [Persist data](#persist-data)
|
||||
- [Start with other Database](#start-with-other-database)
|
||||
- [Use with PostgresDB](#use-with-postgresdb)
|
||||
- [Use with MySQL](#use-with-mysql)
|
||||
- [Passing Sensitive Data via File](#passing-sensitive-data-via-file)
|
||||
- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt)
|
||||
- [Updating a running docker-compose instance](#updating-a-running-docker-compose-instance)
|
||||
- [Setting Timezone](#setting-timezone)
|
||||
- [Build Docker-Image](#build-docker-image)
|
||||
- [What does n8n mean and how do you pronounce it?](#what-does-n8n-mean-and-how-do-you-pronounce-it)
|
||||
- [Support](#support)
|
||||
- [Jobs](#jobs)
|
||||
- [Upgrading](#upgrading)
|
||||
- [License](#license)
|
||||
|
||||
## Demo
|
||||
|
||||
|
@ -71,20 +77,6 @@ docker run -it --rm \
|
|||
n8n start --tunnel
|
||||
```
|
||||
|
||||
## Securing n8n
|
||||
|
||||
By default n8n can be accessed by everybody. This is OK if you have it only running
|
||||
locally but if you deploy it on a server which is accessible from the web you have
|
||||
to make sure that n8n is protected!
|
||||
Right now we have very basic protection via basic-auth in place. It can be activated
|
||||
by setting the following environment variables:
|
||||
|
||||
```text
|
||||
N8N_BASIC_AUTH_ACTIVE=true
|
||||
N8N_BASIC_AUTH_USER=<USER>
|
||||
N8N_BASIC_AUTH_PASSWORD=<PASSWORD>
|
||||
```
|
||||
|
||||
## Persist data
|
||||
|
||||
The workflow data gets by default saved in an SQLite database in the user
|
||||
|
@ -171,7 +163,7 @@ docker run -it --rm \
|
|||
To avoid passing sensitive information via environment variables "\_FILE" may be
|
||||
appended to some environment variables. It will then load the data from a file
|
||||
with the given name. That makes it possible to load data easily from
|
||||
Docker- and Kubernetes-Secrets.
|
||||
Docker and Kubernetes secrets.
|
||||
|
||||
The following environment variables support file input:
|
||||
|
||||
|
@ -181,8 +173,6 @@ The following environment variables support file input:
|
|||
- DB_POSTGRESDB_PORT_FILE
|
||||
- DB_POSTGRESDB_USER_FILE
|
||||
- DB_POSTGRESDB_SCHEMA_FILE
|
||||
- N8N_BASIC_AUTH_PASSWORD_FILE
|
||||
- N8N_BASIC_AUTH_USER_FILE
|
||||
|
||||
## Example Setup with Lets Encrypt
|
||||
|
||||
|
@ -193,26 +183,25 @@ A basic step by step example setup of n8n with docker-compose and Lets Encrypt i
|
|||
|
||||
1. Pull the latest version from the registry
|
||||
|
||||
`docker pull docker.n8n.io/n8nio/n8n`
|
||||
`docker pull docker.n8n.io/n8nio/n8n`
|
||||
|
||||
2. Stop the current setup
|
||||
|
||||
`sudo docker-compose stop`
|
||||
`sudo docker-compose stop`
|
||||
|
||||
3. Delete it (will only delete the docker-containers, data is stored separately)
|
||||
|
||||
`sudo docker-compose rm`
|
||||
`sudo docker-compose rm`
|
||||
|
||||
4. Then start it again
|
||||
|
||||
`sudo docker-compose up -d`
|
||||
`sudo docker-compose up -d`
|
||||
|
||||
## Setting Timezone
|
||||
|
||||
To define the timezone n8n should use, the environment variable `GENERIC_TIMEZONE` can
|
||||
be set. This gets used by for example the Cron-Node.
|
||||
Apart from that can also the timezone of the system be set separately. Which controls what
|
||||
some scripts and commands return like `$ date`. The system timezone can be set via
|
||||
be set. One instance where this variable is implemented is in the Schedule node. Furthermore, the system's timezone can be set separately,
|
||||
which controls the output of certain scripts and commands such as `$ date`. The system timezone can be set via
|
||||
the environment variable `TZ`.
|
||||
|
||||
Example to use the same timezone for both:
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ -d /root/.n8n ] ; then
|
||||
chmod o+rx /root
|
||||
chown -R node /root/.n8n
|
||||
ln -s /root/.n8n /home/node/
|
||||
fi
|
||||
|
||||
chown -R node /home/node
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# Got started with arguments
|
||||
exec su-exec node "$@"
|
||||
n8n "$@"
|
||||
else
|
||||
# Got started without arguments
|
||||
exec su-exec node n8n
|
||||
n8n
|
||||
fi
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
docker build --build-arg N8N_VERSION=$DOCKER_TAG -f $DOCKERFILE_PATH -t $IMAGE_NAME .
|
11
package.json
11
package.json
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.234.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"homepage": "https://n8n.io",
|
||||
"engines": {
|
||||
"node": ">=16.9",
|
||||
"node": ">=18.10",
|
||||
"pnpm": ">=8.6"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.1",
|
||||
|
@ -30,7 +30,6 @@
|
|||
"cypress:open": "CYPRESS_BASE_URL=http://localhost:8080 cypress open",
|
||||
"test:e2e:ui": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress open'",
|
||||
"test:e2e:dev": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first CYPRESS_BASE_URL=http://localhost:8080 start-server-and-test dev http://localhost:8080/favicon.ico 'cypress open'",
|
||||
"test:e2e:smoke": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless --spec \"cypress/e2e/0-smoke.cy.ts\"'",
|
||||
"test:e2e:all": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -52,7 +51,6 @@
|
|||
"jest-mock": "^29.5.0",
|
||||
"jest-mock-extended": "^3.0.4",
|
||||
"nock": "^13.2.9",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"rimraf": "^3.0.2",
|
||||
|
@ -82,6 +80,7 @@
|
|||
"http-cache-semantics": "4.1.1",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"prettier": "^2.8.3",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tslib": "^2.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3",
|
||||
|
@ -93,8 +92,8 @@
|
|||
"typedi@0.10.0": "patches/typedi@0.10.0.patch",
|
||||
"@sentry/cli@2.17.0": "patches/@sentry__cli@2.17.0.patch",
|
||||
"pkce-challenge@3.0.0": "patches/pkce-challenge@3.0.0.patch",
|
||||
"typeorm@0.3.12": "patches/typeorm@0.3.12.patch",
|
||||
"element-plus@2.3.6": "patches/element-plus@2.3.6.patch"
|
||||
"element-plus@2.3.6": "patches/element-plus@2.3.6.patch",
|
||||
"pyodide@0.23.4": "patches/pyodide@0.23.4.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/client-oauth2",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"dev": "pnpm watch",
|
||||
|
|
|
@ -57,7 +57,6 @@ export class CodeFlow {
|
|||
opts?: Partial<ClientOAuth2Options>,
|
||||
): Promise<ClientOAuth2Token> {
|
||||
const options = { ...this.client.options, ...opts };
|
||||
|
||||
expects(options, 'clientId', 'accessTokenUri');
|
||||
|
||||
const url = uri instanceof URL ? uri : new URL(uri, DEFAULT_URL_BASE);
|
||||
|
|
|
@ -21,7 +21,6 @@ export class CredentialsFlow {
|
|||
*/
|
||||
async getToken(opts?: Partial<ClientOAuth2Options>): Promise<ClientOAuth2Token> {
|
||||
const options = { ...this.client.options, ...opts };
|
||||
|
||||
expects(options, 'clientId', 'clientSecret', 'accessTokenUri');
|
||||
|
||||
const body: CredentialsFlowBody = {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
dist/ReloadNodesAndCredentials.*
|
|
@ -2,6 +2,33 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### What changed?
|
||||
|
||||
The minimum Node.js version required for n8n is now v18.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you're using n8n via npm or PM2 or if you're contributing to n8n.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Update the Node.js version to v18 or above.
|
||||
|
||||
## 0.234.0
|
||||
|
||||
### What changed?
|
||||
|
||||
This release introduces two irreversible changes:
|
||||
|
||||
* The n8n database will use strings instead of numeric values to identify workflows and credentials
|
||||
* Execution data is split into a separate database table
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
It will not be possible to read a n8n@0.234.0 database with older versions of n8n, so we recommend that you take a full backup before migrating.
|
||||
|
||||
## 0.232.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -21,10 +21,10 @@ if (process.argv.length === 2) {
|
|||
const nodeVersion = process.versions.node;
|
||||
const nodeVersionMajor = require('semver').major(nodeVersion);
|
||||
|
||||
if (![16, 18].includes(nodeVersionMajor)) {
|
||||
if (![18, 20].includes(nodeVersionMajor)) {
|
||||
console.log(`
|
||||
Your Node.js version (${nodeVersion}) is currently not supported by n8n.
|
||||
Please use Node.js v16 (recommended), or v18 instead!
|
||||
Please use Node.js v18 (recommended), or v20 instead!
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.234.0",
|
||||
"version": "1.0.1",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -53,13 +53,15 @@
|
|||
"workflow"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.9"
|
||||
"node": ">=18.10"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"templates",
|
||||
"dist",
|
||||
"oclif.manifest.json"
|
||||
"oclif.manifest.json",
|
||||
"!dist/**/e2e.*",
|
||||
"!dist/ReloadNodesAndCredentials.*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-cli": "4.0.0",
|
||||
|
@ -72,7 +74,7 @@
|
|||
"@types/convict": "^6.1.1",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/json-diff": "^0.5.1",
|
||||
"@types/json-diff": "^1.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/localtunnel": "^1.9.0",
|
||||
"@types/lodash": "^4.14.195",
|
||||
|
@ -137,7 +139,7 @@
|
|||
"handlebars": "4.7.7",
|
||||
"inquirer": "^7.0.1",
|
||||
"ioredis": "^5.2.4",
|
||||
"json-diff": "^0.5.4",
|
||||
"json-diff": "^1.0.6",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwks-rsa": "^3.0.1",
|
||||
|
|
|
@ -9,12 +9,9 @@ const ROOT_DIR = path.resolve(__dirname, '..');
|
|||
const SPEC_FILENAME = 'openapi.yml';
|
||||
const SPEC_THEME_FILENAME = 'swaggerTheme.css';
|
||||
|
||||
const userManagementEnabled = process.env.N8N_USER_MANAGEMENT_DISABLED !== 'true';
|
||||
const publicApiEnabled = process.env.N8N_PUBLIC_API_DISABLED !== 'true';
|
||||
|
||||
if (userManagementEnabled) {
|
||||
copyUserManagementEmailTemplates();
|
||||
}
|
||||
copyUserManagementEmailTemplates();
|
||||
|
||||
if (publicApiEnabled) {
|
||||
copySwaggerTheme();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { Container, Service } from 'typedi';
|
||||
import type {
|
||||
IDeferredPromise,
|
||||
IExecuteResponsePromiseData,
|
||||
|
@ -19,8 +20,7 @@ import type {
|
|||
IWorkflowExecutionDataProcess,
|
||||
} from '@/Interfaces';
|
||||
import { isWorkflowIdValid } from '@/utils';
|
||||
import Container, { Service } from 'typedi';
|
||||
import { ExecutionRepository } from './databases/repositories';
|
||||
import { ExecutionRepository } from '@db/repositories';
|
||||
|
||||
@Service()
|
||||
export class ActiveExecutions {
|
||||
|
|
|
@ -26,16 +26,13 @@ export class CredentialTypes implements ICredentialTypes {
|
|||
* Returns all parent types of the given credential type
|
||||
*/
|
||||
getParentTypes(typeName: string): string[] {
|
||||
const credentialType = this.getByName(typeName);
|
||||
if (credentialType?.extends === undefined) return [];
|
||||
|
||||
const types: string[] = [];
|
||||
credentialType.extends.forEach((type: string) => {
|
||||
types.push(type);
|
||||
types.push(...this.getParentTypes(type));
|
||||
});
|
||||
|
||||
return types;
|
||||
const extendsArr = this.knownCredentials[typeName]?.extends ?? [];
|
||||
if (extendsArr.length) {
|
||||
extendsArr.forEach((type) => {
|
||||
extendsArr.push(...this.getParentTypes(type));
|
||||
});
|
||||
}
|
||||
return extendsArr;
|
||||
}
|
||||
|
||||
private getCredential(type: string): LoadedClass<ICredentialType> {
|
||||
|
|
|
@ -46,7 +46,6 @@ export const initErrorHandling = async () => {
|
|||
|
||||
process.on('uncaughtException', (error) => {
|
||||
ErrorReporterProxy.error(error);
|
||||
if (error.constructor?.name !== 'AxiosError') throw error;
|
||||
});
|
||||
|
||||
ErrorReporterProxy.init({
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
import { validate } from 'class-validator';
|
||||
import { Container } from 'typedi';
|
||||
import { Like } from 'typeorm';
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
|
@ -23,8 +24,7 @@ import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
|||
import type { TagEntity } from '@db/entities/TagEntity';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { UserUpdatePayload } from '@/requests';
|
||||
import Container from 'typedi';
|
||||
import { ExecutionRepository } from './databases/repositories';
|
||||
import { ExecutionRepository } from '@db/repositories';
|
||||
|
||||
/**
|
||||
* Returns the base URL n8n is reachable from
|
||||
|
|
|
@ -61,6 +61,7 @@ import type {
|
|||
WorkflowStatisticsRepository,
|
||||
WorkflowTagMappingRepository,
|
||||
} from '@db/repositories';
|
||||
import type { LICENSE_FEATURES, LICENSE_QUOTAS } from './constants';
|
||||
|
||||
export interface IActivationError {
|
||||
time: number;
|
||||
|
@ -306,7 +307,6 @@ export interface IDiagnosticInfo {
|
|||
databaseType: DatabaseType;
|
||||
notificationsEnabled: boolean;
|
||||
disableProductionWebhooksOnMainProcess: boolean;
|
||||
basicAuthActive: boolean;
|
||||
systemInfo: {
|
||||
os: {
|
||||
type?: string;
|
||||
|
@ -324,7 +324,6 @@ export interface IDiagnosticInfo {
|
|||
};
|
||||
deploymentType: string;
|
||||
binaryDataMode: string;
|
||||
n8n_multi_user_allowed: boolean;
|
||||
smtp_set_up: boolean;
|
||||
ldap_allowed: boolean;
|
||||
saml_enabled: boolean;
|
||||
|
@ -718,6 +717,11 @@ export interface IExecutionTrackProperties extends ITelemetryTrackProperties {
|
|||
// license
|
||||
// ----------------------------------
|
||||
|
||||
type ValuesOf<T> = T[keyof T];
|
||||
|
||||
export type BooleanLicenseFeature = ValuesOf<typeof LICENSE_FEATURES>;
|
||||
export type NumericLicenseFeature = ValuesOf<typeof LICENSE_QUOTAS>;
|
||||
|
||||
export interface ILicenseReadResponse {
|
||||
usage: {
|
||||
executions: {
|
||||
|
|
|
@ -30,8 +30,8 @@ import { eventBus } from './eventbus';
|
|||
import type { User } from '@db/entities/User';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { NodeTypes } from './NodeTypes';
|
||||
import type { ExecutionMetadata } from './databases/entities/ExecutionMetadata';
|
||||
import { ExecutionRepository } from './databases/repositories';
|
||||
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
|
||||
import { ExecutionRepository } from '@db/repositories';
|
||||
|
||||
function userToPayload(user: User): {
|
||||
userId: string;
|
||||
|
@ -75,12 +75,10 @@ export class InternalHooks implements IInternalHooksClass {
|
|||
db_type: diagnosticInfo.databaseType,
|
||||
n8n_version_notifications_enabled: diagnosticInfo.notificationsEnabled,
|
||||
n8n_disable_production_main_process: diagnosticInfo.disableProductionWebhooksOnMainProcess,
|
||||
n8n_basic_auth_active: diagnosticInfo.basicAuthActive,
|
||||
system_info: diagnosticInfo.systemInfo,
|
||||
execution_variables: diagnosticInfo.executionVariables,
|
||||
n8n_deployment_type: diagnosticInfo.deploymentType,
|
||||
n8n_binary_data_mode: diagnosticInfo.binaryDataMode,
|
||||
n8n_multi_user_allowed: diagnosticInfo.n8n_multi_user_allowed,
|
||||
smtp_set_up: diagnosticInfo.smtp_set_up,
|
||||
ldap_allowed: diagnosticInfo.ldap_allowed,
|
||||
saml_enabled: diagnosticInfo.saml_enabled,
|
||||
|
|
|
@ -12,7 +12,6 @@ import { User } from '@db/entities/User';
|
|||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||
import { RoleRepository } from '@db/repositories';
|
||||
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
||||
import { isUserManagementEnabled } from '@/UserManagement/UserManagementHelper';
|
||||
import { LdapManager } from './LdapManager.ee';
|
||||
|
||||
import {
|
||||
|
@ -37,9 +36,8 @@ import { InternalServerError } from '../ResponseHelper';
|
|||
/**
|
||||
* Check whether the LDAP feature is disabled in the instance
|
||||
*/
|
||||
export const isLdapEnabled = (): boolean => {
|
||||
const license = Container.get(License);
|
||||
return isUserManagementEnabled() && license.isLdapEnabled();
|
||||
export const isLdapEnabled = () => {
|
||||
return Container.get(License).isLdapEnabled();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,8 +9,16 @@ import {
|
|||
LICENSE_QUOTAS,
|
||||
N8N_VERSION,
|
||||
SETTINGS_LICENSE_CERT_KEY,
|
||||
UNLIMITED_LICENSE_QUOTA,
|
||||
} from './constants';
|
||||
import { Service } from 'typedi';
|
||||
import type { BooleanLicenseFeature, NumericLicenseFeature } from './Interfaces';
|
||||
|
||||
type FeatureReturnType = Partial<
|
||||
{
|
||||
planName: string;
|
||||
} & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean }
|
||||
>;
|
||||
|
||||
@Service()
|
||||
export class License {
|
||||
|
@ -96,13 +104,8 @@ export class License {
|
|||
await this.manager.renew();
|
||||
}
|
||||
|
||||
isFeatureEnabled(feature: string): boolean {
|
||||
if (!this.manager) {
|
||||
getLogger().warn('License manager not initialized');
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.manager.hasFeatureEnabled(feature);
|
||||
isFeatureEnabled(feature: BooleanLicenseFeature) {
|
||||
return this.manager?.hasFeatureEnabled(feature) ?? false;
|
||||
}
|
||||
|
||||
isSharingEnabled() {
|
||||
|
@ -141,15 +144,8 @@ export class License {
|
|||
return this.manager?.getCurrentEntitlements() ?? [];
|
||||
}
|
||||
|
||||
getFeatureValue(
|
||||
feature: string,
|
||||
requireValidCert?: boolean,
|
||||
): undefined | boolean | number | string {
|
||||
if (!this.manager) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.manager.getFeatureValue(feature, requireValidCert);
|
||||
getFeatureValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
|
||||
return this.manager?.getFeatureValue(feature) as FeatureReturnType[T];
|
||||
}
|
||||
|
||||
getManagementJwt(): string {
|
||||
|
@ -178,20 +174,20 @@ export class License {
|
|||
}
|
||||
|
||||
// Helper functions for computed data
|
||||
getTriggerLimit(): number {
|
||||
return (this.getFeatureValue(LICENSE_QUOTAS.TRIGGER_LIMIT) ?? -1) as number;
|
||||
getUsersLimit() {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.USERS_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
|
||||
getVariablesLimit(): number {
|
||||
return (this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? -1) as number;
|
||||
getTriggerLimit() {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.TRIGGER_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
|
||||
getUsersLimit(): number {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.USERS_LIMIT) as number;
|
||||
getVariablesLimit() {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
|
||||
getPlanName(): string {
|
||||
return (this.getFeatureValue('planName') ?? 'Community') as string;
|
||||
return this.getFeatureValue('planName') ?? 'Community';
|
||||
}
|
||||
|
||||
getInfo(): string {
|
||||
|
@ -201,4 +197,8 @@ export class License {
|
|||
|
||||
return this.manager.toString();
|
||||
}
|
||||
|
||||
isWithinUsersLimit() {
|
||||
return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,21 +66,23 @@ export class LoadNodesAndCredentials implements INodesAndCredentials {
|
|||
|
||||
this.downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();
|
||||
|
||||
// Load nodes from `n8n-nodes-base` and any other `n8n-nodes-*` package in the main `node_modules`
|
||||
const pathsToScan = [
|
||||
// Load nodes from `n8n-nodes-base`
|
||||
const basePathsToScan = [
|
||||
// In case "n8n" package is in same node_modules folder.
|
||||
path.join(CLI_DIR, '..'),
|
||||
// In case "n8n" package is the root and the packages are
|
||||
// in the "node_modules" folder underneath it.
|
||||
path.join(CLI_DIR, 'node_modules'),
|
||||
// Path where all community nodes are installed
|
||||
path.join(this.downloadFolder, 'node_modules'),
|
||||
];
|
||||
|
||||
for (const nodeModulesDir of pathsToScan) {
|
||||
await this.loadNodesFromNodeModules(nodeModulesDir);
|
||||
for (const nodeModulesDir of basePathsToScan) {
|
||||
await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-base');
|
||||
}
|
||||
|
||||
// Load nodes from any other `n8n-nodes-*` packages in the download directory
|
||||
// This includes the community nodes
|
||||
await this.loadNodesFromNodeModules(path.join(this.downloadFolder, 'node_modules'));
|
||||
|
||||
await this.loadNodesFromCustomDirectories();
|
||||
await this.postProcessLoaders();
|
||||
this.injectCustomApiCallOptions();
|
||||
|
@ -127,12 +129,15 @@ export class LoadNodesAndCredentials implements INodesAndCredentials {
|
|||
await writeStaticJSON('credentials', this.types.credentials);
|
||||
}
|
||||
|
||||
private async loadNodesFromNodeModules(nodeModulesDir: string): Promise<void> {
|
||||
const globOptions = { cwd: nodeModulesDir, onlyDirectories: true };
|
||||
const installedPackagePaths = [
|
||||
...(await glob('n8n-nodes-*', { ...globOptions, deep: 1 })),
|
||||
...(await glob('@*/n8n-nodes-*', { ...globOptions, deep: 2 })),
|
||||
];
|
||||
private async loadNodesFromNodeModules(
|
||||
nodeModulesDir: string,
|
||||
packageName?: string,
|
||||
): Promise<void> {
|
||||
const installedPackagePaths = await glob(packageName ?? ['n8n-nodes-*', '@*/n8n-nodes-*'], {
|
||||
cwd: nodeModulesDir,
|
||||
onlyDirectories: true,
|
||||
deep: 1,
|
||||
});
|
||||
|
||||
for (const packagePath of installedPackagePaths) {
|
||||
try {
|
||||
|
@ -326,7 +331,7 @@ export class LoadNodesAndCredentials implements INodesAndCredentials {
|
|||
|
||||
for (const loader of Object.values(this.loaders)) {
|
||||
// list of node & credential types that will be sent to the frontend
|
||||
const { types, directory } = loader;
|
||||
const { known, types, directory } = loader;
|
||||
this.types.nodes = this.types.nodes.concat(types.nodes);
|
||||
this.types.credentials = this.types.credentials.concat(types.credentials);
|
||||
|
||||
|
@ -339,26 +344,30 @@ export class LoadNodesAndCredentials implements INodesAndCredentials {
|
|||
this.loaded.credentials[credentialTypeName] = loader.credentialTypes[credentialTypeName];
|
||||
}
|
||||
|
||||
// Nodes and credentials that will be lazy loaded
|
||||
if (loader instanceof PackageDirectoryLoader) {
|
||||
const { packageName, known } = loader;
|
||||
for (const type in known.nodes) {
|
||||
const { className, sourcePath } = known.nodes[type];
|
||||
this.known.nodes[type] = {
|
||||
className,
|
||||
sourcePath: path.join(directory, sourcePath),
|
||||
};
|
||||
}
|
||||
|
||||
for (const type in known.nodes) {
|
||||
const { className, sourcePath } = known.nodes[type];
|
||||
this.known.nodes[type] = {
|
||||
className,
|
||||
sourcePath: path.join(directory, sourcePath),
|
||||
};
|
||||
}
|
||||
|
||||
for (const type in known.credentials) {
|
||||
const { className, sourcePath, nodesToTestWith } = known.credentials[type];
|
||||
this.known.credentials[type] = {
|
||||
className,
|
||||
sourcePath: path.join(directory, sourcePath),
|
||||
nodesToTestWith: nodesToTestWith?.map((nodeName) => `${packageName}.${nodeName}`),
|
||||
};
|
||||
}
|
||||
for (const type in known.credentials) {
|
||||
const {
|
||||
className,
|
||||
sourcePath,
|
||||
nodesToTestWith,
|
||||
extends: extendsArr,
|
||||
} = known.credentials[type];
|
||||
this.known.credentials[type] = {
|
||||
className,
|
||||
sourcePath: path.join(directory, sourcePath),
|
||||
nodesToTestWith:
|
||||
loader instanceof PackageDirectoryLoader
|
||||
? nodesToTestWith?.map((nodeName) => `${loader.packageName}.${nodeName}`)
|
||||
: undefined,
|
||||
extends: extendsArr,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,13 +138,13 @@ export type OperationID = 'getUsers' | 'getUser';
|
|||
|
||||
type PaginationBase = { limit: number };
|
||||
|
||||
type PaginationOffsetDecoded = PaginationBase & { offset: number };
|
||||
export type PaginationOffsetDecoded = PaginationBase & { offset: number };
|
||||
|
||||
type PaginationCursorDecoded = PaginationBase & { lastId: string };
|
||||
export type PaginationCursorDecoded = PaginationBase & { lastId: string };
|
||||
|
||||
type OffsetPagination = PaginationBase & { offset: number; numberOfTotalRecords: number };
|
||||
export type OffsetPagination = PaginationBase & { offset: number; numberOfTotalRecords: number };
|
||||
|
||||
type CursorPagination = PaginationBase & { lastId: string; numberOfNextRecords: number };
|
||||
export type CursorPagination = PaginationBase & { lastId: string; numberOfNextRecords: number };
|
||||
export interface IRequired {
|
||||
required?: string[];
|
||||
not?: { required?: string[] };
|
|
@ -37,7 +37,7 @@ export = {
|
|||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
await BinaryDataManager.getInstance().deleteBinaryDataByExecutionId(execution.id!);
|
||||
await BinaryDataManager.getInstance().deleteBinaryDataByExecutionIds([execution.id!]);
|
||||
|
||||
await deleteExecution(execution);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue