diff --git a/.dockerignore b/.dockerignore index d92acf83baddd..fe841322cc28f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,12 @@ packages/node-dev packages/**/node_modules packages/**/dist packages/**/.turbo +packages/**/*.test.* .git .github *.tsbuildinfo +packages/cli/dist/**/e2e.* +docker/compose +docker/**/Dockerfile +.vscode +cypress diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..a4f667ac4600a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,13 @@ +# Commits of large-scale changes to exclude from `git blame` results + +# Set up linting and formatting (#2120) + +56c4c6991fb21ba4b7bdcd22c929f63cc1d1defe + +# refactor(editor): Apply Prettier (no-changelog) #4920 + +5ca2148c7ed06c90f999508928b7a51f9ac7a788 + +# refactor: Run lintfix (no-changelog) (#7537) + +62c096710fab2f7e886518abdbded34b55e93f62 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000..dfdb8b771ce07 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.github/ISSUE_TEMPLATE/01-bug.yml b/.github/ISSUE_TEMPLATE/01-bug.yml new file mode 100644 index 0000000000000..63b0634e6facd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug.yml @@ -0,0 +1,82 @@ +name: Bug Report +description: Create a bug report to help us improve +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: description + attributes: + label: Bug Description + description: A clear and concise description of what the bug is + placeholder: Tell us what you see! + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: To Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen + validations: + required: true + - type: markdown + attributes: + value: '## Environment' + - type: input + id: os + attributes: + label: Operating System + placeholder: ex. Ubuntu Linux 22.04 + validations: + required: true + - type: input + id: n8n-version + attributes: + label: n8n Version + placeholder: ex. 1.25.0 + validations: + required: true + - type: input + id: nodejs-version + attributes: + label: Node.js Version + placeholder: ex. 18.16.0 + validations: + required: true + - type: dropdown + id: db + attributes: + label: Database + options: + - SQLite (default) + - PostgreSQL + - MySQL + - MariaDB + default: 0 + validations: + required: true + - type: dropdown + id: execution-mode + attributes: + label: Execution mode + description: '[Info](https://docs.n8n.io/hosting/scaling/execution-modes-processes/)' + options: + - main (default) + - queue + - own (deprecated) + default: 0 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index fdc96ff873b92..0000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -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] -- 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`] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c2caf18ab3202..8b62ca7bbbffc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,3 +6,6 @@ contact_links: - name: Question / Problem url: https://community.n8n.io about: Questions and problems with n8n + - name: n8n Security Vulnerability + url: https://n8n.io/legal/#vulnerability + about: Learn about our Vulnerability Disclosure Policy diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml index 4482ab62222f8..84a1b9c9610b8 100644 --- a/.github/docker-compose.yml +++ b/.github/docker-compose.yml @@ -2,18 +2,24 @@ version: '3.9' services: mysql: - image: mysql:5.7-debian + image: mysql:5.7 environment: - MYSQL_DATABASE=n8n - MYSQL_ROOT_PASSWORD=password ports: - 3306:3306 + ulimits: + nproc: 65535 + nofile: + soft: 26677 + hard: 46677 postgres: - image: postgres:11 + image: postgres:16 + restart: always environment: - POSTGRES_DB=n8n - - POSTGRES_USER=root + - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password ports: - 5432:5432 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bd399104db8d6..dfcc0615a7b4d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1 +1,16 @@ -Github issue / Community forum post (link here to close automatically): +## Summary +> Describe what the PR does and how to test. Photos and videos are recommended. + + + +## Related tickets and issues +> Include links to **Linear ticket** or Github issue or Community forum post. Important in order to close *automatically* and provide context to reviewers. + + + +## Review / Merge checklist +- [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) +- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. +- [ ] Tests included. + > A bug is not considered fixed, unless a test is added to prevent it from happening again. + > A feature is not complete without tests. \ No newline at end of file diff --git a/.github/pull_request_title_conventions.md b/.github/pull_request_title_conventions.md new file mode 100644 index 0000000000000..8808000e3b330 --- /dev/null +++ b/.github/pull_request_title_conventions.md @@ -0,0 +1,112 @@ +# PR Title Convention + +We have very precise rules over how Pull Requests (to the `master` branch) must be formatted. This format basically follows the [Angular Commit Message Convention](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). It leads to easier to read commit history and allows for automated generation of release notes: + +A PR title consists of these elements: + +``` +(): + │ │ │ + │ │ └─⫸ Summary: In imperative present tense. + | | Capitalized + | | No period at the end. + │ │ + │ └─⫸ Scope: API|core|editor|* Node + │ + └─⫸ Type: build|ci|docs|feat|fix|perf|refactor|test +``` + +- PR title + - type + - scope (*optional*) + - summary +- PR description + - body (optional) + - blank line + - footer (optional) + +The structure looks like this: + +### **Type** + +Must be one of the following: + +- `feat` - A new feature +- `fix` - A bug fix +- `perf` - A code change that improves performance +- `test` - Adding missing tests or correcting existing tests +- `docs` - Documentation only changes +- `refactor` - A code change that neither fixes a bug nor adds a feature +- `build` - Changes that affect the build system or external dependencies (example scopes: broccoli, npm) +- `ci` - Changes to our CI configuration files and scripts (e.g. Github actions) + +If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However if there is any BREAKING CHANGE (see Footer section below), the commit will always appear in the changelog. + +### **Scope (optional)** + +The scope should specify the place of the commit change as long as the commit clearly addresses one of the following supported scopes. (Otherwise, omit the scope!) + +- `API` - changes to the *public* API +- `core` - changes to the core / private API / backend of n8n +- `editor` - changes to the Editor UI +- `* Node` - changes to a specific node or trigger node (”`*`” to be replaced with the node name, not its display name), e.g. + - mattermost → Mattermost Node + - microsoftToDo → Microsoft To Do Node + - n8n → n8n Node + +### **Summary** + +The summary contains succinct description of the change: + +- use the imperative, present tense: "change" not "changed" nor "changes" +- capitalize the first letter +- *no* dot (.) at the end +- do *not* include Linear ticket IDs etc. (e.g. N8N-1234) +- suffix with “(no-changelog)” for commits / PRs that should not get mentioned in the changelog. + +### **Body (optional)** + +Just as in the **summary**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior. + +### **Footer (optional)** + +The footer can contain information about breaking changes and deprecations and is also the place to [reference GitHub issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), Linear tickets, and other PRs that this commit closes or is related to. For example: + +``` +BREAKING CHANGE: + + + + +Fixes # +``` + +or + +``` +DEPRECATED: + + + + +Closes # +``` + +A Breaking Change section should start with the phrase "`BREAKING CHANGE:` " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions. + +> 💡 A breaking change can additionally also be marked by adding a “`!`” to the header, right before the “`:`”, e.g. `feat(editor)!: Remove support for dark mode` +> +> This makes locating breaking changes easier when just skimming through commit messages. + +> 💡 The breaking changes must also be added to the [packages/cli/BREAKING-CHANGES.md](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) file located in the n8n repository. + +Similarly, a Deprecation section should start with "`DEPRECATED:` " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path. + +### **Revert commits** + +If the commit reverts a previous commit, it should begin with `revert:` , followed by the header of the reverted commit. + +The content of the commit message body should contain: + +- information about the SHA of the commit being reverted in the following format: `This reverts commit `, +- a clear description of the reason for reverting the commit message. \ No newline at end of file diff --git a/.github/scripts/bump-versions.mjs b/.github/scripts/bump-versions.mjs index c1c110272cb9a..8e7acc21ebcfc 100644 --- a/.github/scripts/bump-versions.mjs +++ b/.github/scripts/bump-versions.mjs @@ -43,7 +43,7 @@ for (const packageName in packageMap) { packageJson.version = packageMap[packageName].nextVersion = isDirty || - Object.keys(packageJson.dependencies).some( + Object.keys(packageJson.dependencies || {}).some( (dependencyName) => packageMap[dependencyName]?.isDirty, ) ? semver.inc(version, releaseType) diff --git a/.github/scripts/check-tests.mjs b/.github/scripts/check-tests.mjs new file mode 100644 index 0000000000000..1e2e38e05b064 --- /dev/null +++ b/.github/scripts/check-tests.mjs @@ -0,0 +1,103 @@ +import { readFile } from 'fs/promises'; +import path from 'path'; +import util from 'util'; +import { exec } from 'child_process'; +import { glob } from 'glob'; +import ts from 'typescript'; + +const execAsync = util.promisify(exec); + +const filterAsync = async (asyncPredicate, arr) => { + const filterResults = await Promise.all( + arr.map(async (item) => ({ + item, + shouldKeep: await asyncPredicate(item), + })), + ); + + return filterResults.filter(({ shouldKeep }) => shouldKeep).map(({ item }) => item); +}; + +const isAbstractClass = (node) => { + if (ts.isClassDeclaration(node)) { + return ( + node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword) || false + ); + } + return false; +}; + +const isAbstractMethod = (node) => { + return ( + ts.isMethodDeclaration(node) && + Boolean(node.modifiers?.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword)) + ); +}; + +// Function to check if a file has a function declaration, function expression, object method or class +const hasFunctionOrClass = async (filePath) => { + const fileContent = await readFile(filePath, 'utf-8'); + const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true); + + let hasFunctionOrClass = false; + const visit = (node) => { + if ( + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || + (ts.isMethodDeclaration(node) && !isAbstractMethod(node)) || + (ts.isClassDeclaration(node) && !isAbstractClass(node)) + ) { + hasFunctionOrClass = true; + } + node.forEachChild(visit); + }; + + visit(sourceFile); + + return hasFunctionOrClass; +}; + +const main = async () => { + // Run a git command to get a list of all changed files in the branch (branch has to be up to date with master) + const changedFiles = await execAsync( + 'git diff --name-only --diff-filter=d origin/master..HEAD', + ).then(({ stdout }) => stdout.trim().split('\n').filter(Boolean)); + + // Get all .spec.ts and .test.ts files from the packages + const specAndTestTsFiles = await glob('packages/*/**/{test,__tests__}/**/*.{spec,test}.ts'); + const specAndTestTsFilesNames = specAndTestTsFiles.map((file) => + path.parse(file).name.replace(/\.(test|spec)/, ''), + ); + + // Filter out the .ts and .vue files from the changed files + const changedVueFiles = changedFiles.filter((file) => file.endsWith('.vue')); + // .ts files with any kind of function declaration or class and not in any of the test folders + const changedTsFilesWithFunction = await filterAsync( + async (filePath) => + filePath.endsWith('.ts') && + !(await glob('packages/*/**/{test,__tests__}/*.ts')).includes(filePath) && + (await hasFunctionOrClass(filePath)), + changedFiles, + ); + + // For each .ts or .vue file, check if there's a corresponding .test.ts or .spec.ts file in the repository + const missingTests = changedVueFiles + .concat(changedTsFilesWithFunction) + .reduce((filesList, nextFile) => { + const fileName = path.parse(nextFile).name; + + if (!specAndTestTsFilesNames.includes(fileName)) { + filesList.push(nextFile); + } + + return filesList; + }, []); + + if (missingTests.length) { + console.error(`Missing tests for:\n${missingTests.join('\n')}`); + process.exit(1); + } +}; + +main(); diff --git a/.github/scripts/package.json b/.github/scripts/package.json index 60f945f3f507b..cd67711fa4c29 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -1,6 +1,13 @@ { "dependencies": { - "semver": "^7.3.8", - "conventional-changelog-cli": "^2.2.2" + "cacheable-lookup": "6.1.0", + "conventional-changelog": "^4.0.0", + "debug": "4.3.4", + "glob": "10.3.10", + "p-limit": "3.1.0", + "picocolors": "1.0.0", + "semver": "7.5.4", + "tempfile": "5.0.0", + "typescript": "*" } } diff --git a/.github/scripts/update-changelog.mjs b/.github/scripts/update-changelog.mjs new file mode 100644 index 0000000000000..c0cce214a9777 --- /dev/null +++ b/.github/scripts/update-changelog.mjs @@ -0,0 +1,35 @@ +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 { pipeline } from 'stream/promises'; +import packageJson from '../../package.json' assert { type: 'json' }; + +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); +}); + +// Write the new changelog to a new temporary file, so that the contents can be used in the PR description +await pipeline(changelogStream, 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(); +const tmpStream = createWriteStream(tmpFile); +await pipeline(createReadStream(versionChangelogFile), tmpStream, { end: false }); +await pipeline(createReadStream(fullChangelogFile), tmpStream); +await pipeline(createReadStream(tmpFile), createWriteStream(fullChangelogFile)); diff --git a/.github/scripts/validate-docs-links.js b/.github/scripts/validate-docs-links.js new file mode 100644 index 0000000000000..1c11e75f7f2c3 --- /dev/null +++ b/.github/scripts/validate-docs-links.js @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +const packages = ['nodes-base', '@n8n/nodes-langchain']; +const concurrency = 20; +let exitCode = 0; + +const debug = require('debug')('n8n'); +const path = require('path'); +const https = require('https'); +const glob = require('glob'); +const pLimit = require('p-limit'); +const picocolors = require('picocolors'); +const Lookup = require('cacheable-lookup').default; + +const agent = new https.Agent({ keepAlive: true, keepAliveMsecs: 5000 }); +new Lookup().install(agent); +const limiter = pLimit(concurrency); + +const validateUrl = async (packageName, kind, type) => + new Promise((resolve, reject) => { + const name = type.displayName; + const documentationUrl = + kind === 'credentials' + ? type.documentationUrl + : type.codex?.resources?.primaryDocumentation?.[0]?.url; + if (!documentationUrl) resolve([name, null]); + + const url = new URL( + /^https?:\/\//.test(documentationUrl) + ? documentationUrl + : `https://docs.n8n.io/integrations/builtin/${kind}/${documentationUrl.toLowerCase()}/`, + ); + https + .request( + { + hostname: url.hostname, + port: 443, + path: url.pathname, + method: 'HEAD', + agent, + }, + (res) => { + debug(picocolors.green('✓'), packageName, kind, name); + resolve([name, res.statusCode]); + }, + ) + .on('error', (e) => { + debug(picocolors.red('✘'), packageName, kind, name); + reject(e); + }) + .end(); + }); + +const checkLinks = async (packageName, kind) => { + const baseDir = path.resolve(__dirname, '../../packages', packageName); + let types = require(path.join(baseDir, `dist/types/${kind}.json`)); + if (kind === 'nodes') + types = types.filter( + ({ codex, hidden }) => !!codex?.resources?.primaryDocumentation && !hidden, + ); + debug(packageName, kind, types.length); + + const statuses = await Promise.all( + types.map((type) => + limiter(() => { + return validateUrl(packageName, kind, type); + }), + ), + ); + + const missingDocs = []; + const invalidUrls = []; + for (const [name, statusCode] of statuses) { + if (statusCode === null) missingDocs.push(name); + if (statusCode !== 200) invalidUrls.push(name); + } + + if (missingDocs.length) + console.log('Documentation URL missing in %s for %s', packageName, kind, missingDocs); + if (invalidUrls.length) + console.log('Documentation URL invalid in %s for %s', packageName, kind, invalidUrls); + if (missingDocs.length || invalidUrls.length) exitCode = 1; +}; + +(async () => { + for (const packageName of packages) { + await Promise.all([checkLinks(packageName, 'credentials'), checkLinks(packageName, 'nodes')]); + if (exitCode !== 0) process.exit(exitCode); + } +})(); diff --git a/.github/workflows/units-tests-dispatch.yml b/.github/workflows/units-tests-dispatch.yml new file mode 100644 index 0000000000000..5ad63f000e1fb --- /dev/null +++ b/.github/workflows/units-tests-dispatch.yml @@ -0,0 +1,39 @@ +name: Runs unit tests for a given ref + +on: + workflow_dispatch: + inputs: + ref: + description: 'GitHub ref to test.' + required: false + default: 'master' + type: string + prNumber: + description: 'PR number to run tests for.' + required: false + type: number + +jobs: + prepare: + name: Prepare + runs-on: ubuntu-latest + outputs: + branch: ${{ steps.compute-branch.outputs.branch }} + steps: + - name: Compute branch + id: compute-branch + run: | + BRANCH_NAME="" + if [[ "${{ inputs.prNumber }}" != "" && "${{ inputs.prNumber }}" != "null" ]]; then + BRANCH_NAME="refs/pull/${{ inputs.prNumber }}/merge" + else + BRANCH_NAME="${{ inputs.ref }}" + fi + echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT + + unit-test: + name: Unit tests + needs: prepare + uses: ./.github/workflows/units-tests-reusable.yml + with: + ref: ${{ needs.prepare.outputs.branch }} diff --git a/.github/workflows/units-tests-reusable.yml b/.github/workflows/units-tests-reusable.yml new file mode 100644 index 0000000000000..386612678c1e5 --- /dev/null +++ b/.github/workflows/units-tests-reusable.yml @@ -0,0 +1,72 @@ +name: Reusable units test workflow + +on: + workflow_call: + inputs: + ref: + description: 'GitHub ref to test.' + required: false + type: string + default: 'master' + nodeVersion: + description: 'Version of node to use.' + required: false + type: string + default: '18.x' + cacheKey: + description: 'Cache key for modules and build artifacts.' + required: false + default: '' + type: string + collectCoverage: + required: false + default: 'false' + type: string + +jobs: + unit-test: + name: Unit tests + runs-on: ubuntu-latest + env: + COVERAGE_ENABLED: ${{ inputs.collectCoverage }} + steps: + - uses: actions/checkout@v4.1.1 + with: + repository: n8n-io/n8n + ref: ${{ inputs.ref }} + + - run: corepack enable + - name: Use Node.js ${{ inputs.nodeVersion }} + uses: actions/setup-node@v4.0.1 + with: + node-version: ${{ inputs.nodeVersion }} + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + if: ${{ inputs.cacheKey == '' }} + run: pnpm build + + - name: Restore cached build artifacts + if: ${{ inputs.cacheKey != '' }} + uses: actions/cache/restore@v4.0.0 + with: + path: ./packages/**/dist + key: ${{ inputs.cacheKey }} + + - name: Test Backend + run: pnpm test:backend + + - name: Test Nodes + run: pnpm test:nodes + + - name: Test Frontend + run: pnpm test:frontend + + - name: Upload coverage to Codecov + if: ${{ inputs.collectCoverage == 'true' }} + uses: codecov/codecov-action@v3 + with: + files: packages/@n8n/chat/coverage/cobertura-coverage.xml,packages/@n8n/nodes-langchain/coverage/cobertura-coverage.xml,packages/@n8n/permissions/coverage/cobertura-coverage.xml,packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml diff --git a/.gitignore b/.gitignore index a62f7fdda9ca3..3ba0d8cfd14b3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,10 +16,13 @@ _START_PACKAGE nodelinter.config.json **/package-lock.json packages/**/.turbo +.turbo *.tsbuildinfo cypress/videos/* cypress/screenshots/* +cypress/downloads/* *.swp - -# Logs -packages/cli/bin/logs +CHANGELOG-*.md +*.mdx +build-storybook.log +*.log diff --git a/.npmrc b/.npmrc index ba82761a5257d..0d9bdb6234f43 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,6 @@ +audit = false +fund = false +update-notifier = false auto-install-peers = true strict-peer-dependencies = false prefer-workspace-packages = true diff --git a/.prettierignore b/.prettierignore index c5faf0aa47ee0..4ac8f0dafbf68 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,9 @@ coverage dist package.json -.pnpm-lock.yml +pnpm-lock.yaml +packages/editor-ui/index.html +packages/nodes-base/nodes/**/test +packages/cli/templates/form-trigger.handlebars +cypress/fixtures +CHANGELOG.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 877b69401a0fb..8fb03eb716f7c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,7 +5,6 @@ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", - "Vue.vscode-typescript-vue-plugin", "Vue.volar" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 6e814517875a9..448d745236cbc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -26,6 +26,7 @@ "name": "Launch n8n with debug", "program": "${workspaceFolder}/packages/cli/bin/n8n", "cwd": "${workspaceFolder}/packages/cli/bin", + // "args": ["start", "--tunnel"], "request": "launch", "skipFiles": ["/**"], "type": "node", diff --git a/.vscode/settings.default.json b/.vscode/settings.default.json index 6532df1beec88..87db2ca2ee206 100644 --- a/.vscode/settings.default.json +++ b/.vscode/settings.default.json @@ -9,7 +9,7 @@ "typescript.format.enable": false, "typescript.tsdk": "node_modules/typescript/lib", "workspace-default-settings.runOnActivation": true, - "prettier.prettierPath": "node_modules/prettier", + "prettier.prettierPath": "node_modules/prettier/index.cjs", "eslint.probe": ["javascript", "typescript", "vue"], "eslint.workingDirectories": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index 1853cad3e7e2f..33a58cc333e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2123 @@ +# [1.41.0](https://github.com/n8n-io/n8n/compare/n8n@1.40.0...n8n@1.41.0) (2024-05-08) + + +### Bug Fixes + +* Cast boolean values in filter parameter ([#9260](https://github.com/n8n-io/n8n/issues/9260)) ([30c8efc](https://github.com/n8n-io/n8n/commit/30c8efc4cc9b25fabc8d9c56e8c29e7e77c04325)) +* **core:** Prevent occassional 429s on license init in multi-main setup ([#9284](https://github.com/n8n-io/n8n/issues/9284)) ([22b6f90](https://github.com/n8n-io/n8n/commit/22b6f909505d7c3d9c0583a90599e6e9c244e21e)) +* **core:** Report missing SAML attributes early with an actionable error message ([#9316](https://github.com/n8n-io/n8n/issues/9316)) ([225fdbb](https://github.com/n8n-io/n8n/commit/225fdbb379f6dd0005bd4ccb3791c96de35b1653)) +* **core:** Webhooks responding with binary data should not prematurely end the response stream ([#9063](https://github.com/n8n-io/n8n/issues/9063)) ([23b676d](https://github.com/n8n-io/n8n/commit/23b676d7cb9708d7a99fc031cfeec22b854be1d9)) +* **editor:** Fix multi-select parameters with load options getting cleared ([#9324](https://github.com/n8n-io/n8n/issues/9324)) ([0ee4b6c](https://github.com/n8n-io/n8n/commit/0ee4b6c86000ab164211c1ebed90306cd144af1b)) +* **editor:** Fix shortcut issue on save buttons ([#9309](https://github.com/n8n-io/n8n/issues/9309)) ([e74c14f](https://github.com/n8n-io/n8n/commit/e74c14ffbe088ac74dc6358068cd54af9a850cad)) +* **editor:** Resolve `$vars` and `$secrets` in expressions in credentials fields ([#9289](https://github.com/n8n-io/n8n/issues/9289)) ([d92f994](https://github.com/n8n-io/n8n/commit/d92f994913befd31aec409ef8e40b290ac4185ba)) +* **editor:** Show MFA section to instance owner, even when external auth is enabled ([#9301](https://github.com/n8n-io/n8n/issues/9301)) ([b65e0e2](https://github.com/n8n-io/n8n/commit/b65e0e28114f576f89e271ab8ffdb8550e1be60f)) +* **Gmail Node:** Remove duplicate options when creating drafts ([#9299](https://github.com/n8n-io/n8n/issues/9299)) ([bfb0eb7](https://github.com/n8n-io/n8n/commit/bfb0eb7a06f219424486a55256ecca46c14a85ba)) +* **Linear Node:** Fix issue with data not always being returned ([#9273](https://github.com/n8n-io/n8n/issues/9273)) ([435272b](https://github.com/n8n-io/n8n/commit/435272b568826edf899dbaba9d10077fbe134ea6)) +* **n8n Form Trigger Node:** Fix missing options when using respond to webhook ([#9282](https://github.com/n8n-io/n8n/issues/9282)) ([6ab3781](https://github.com/n8n-io/n8n/commit/6ab378157041abfc918ae1d9408821f8fd5cfb34)) +* **Pipedrive Node:** Improve type-safety in custom-property handling ([#9319](https://github.com/n8n-io/n8n/issues/9319)) ([c8895c5](https://github.com/n8n-io/n8n/commit/c8895c540e5c8edfb576960a5ba4ec9ac4426d5b)) +* **Read PDF Node:** Disable JS evaluation from PDFs ([#9336](https://github.com/n8n-io/n8n/issues/9336)) ([c4bf5b2](https://github.com/n8n-io/n8n/commit/c4bf5b2b9285402ae09960eb64a5d6f20356eeaf)) + + +### Features + +* **editor:** Implement AI Assistant chat UI ([#9300](https://github.com/n8n-io/n8n/issues/9300)) ([491c6ec](https://github.com/n8n-io/n8n/commit/491c6ec546c4ec8ab4eb88d020c13820071bf6dc)) +* **editor:** Temporarily disable AI error helper ([#9329](https://github.com/n8n-io/n8n/issues/9329)) ([35b983b](https://github.com/n8n-io/n8n/commit/35b983b6dfbb6ab02367801a15581e80a2d87340)) +* **LinkedIn Node:** Upgrade LinkedIn API version ([#9307](https://github.com/n8n-io/n8n/issues/9307)) ([3860077](https://github.com/n8n-io/n8n/commit/3860077f8100fb790acf1d930839e86719a454fd)) +* **Redis Node:** Add support for TLS ([#9266](https://github.com/n8n-io/n8n/issues/9266)) ([0a2de09](https://github.com/n8n-io/n8n/commit/0a2de093c01689b8f179b3f4413a4ce29ccf279a)) +* **Send Email Node:** Add an option to customize client host-name on SMTP connections ([#9322](https://github.com/n8n-io/n8n/issues/9322)) ([d0d52de](https://github.com/n8n-io/n8n/commit/d0d52def8fb4113a7a4866d30f2e9c7bfe11075e)) +* **Slack Node:** Update to use the new API method for file uploads ([#9323](https://github.com/n8n-io/n8n/issues/9323)) ([695e762](https://github.com/n8n-io/n8n/commit/695e762663fde79b9555be8cf075ee4144f380f1)) + + + +# [1.40.0](https://github.com/n8n-io/n8n/compare/n8n@1.39.0...n8n@1.40.0) (2024-05-02) + + +### Bug Fixes + +* **Airtable Node:** Do not allow to use deprecated api keys in v1 ([#9171](https://github.com/n8n-io/n8n/issues/9171)) ([017ae6e](https://github.com/n8n-io/n8n/commit/017ae6e1025fb4ae28b46b9c411e4b5c70e280e9)) +* **core:** Add `view engine` to webhook server to support forms ([#9224](https://github.com/n8n-io/n8n/issues/9224)) ([24c3150](https://github.com/n8n-io/n8n/commit/24c3150056401ddcf49f7266897b6c73ccc06253)) +* **core:** Fix browser session refreshes not working ([#9212](https://github.com/n8n-io/n8n/issues/9212)) ([1efeecc](https://github.com/n8n-io/n8n/commit/1efeeccc5bae306a798a66a8cf3e669ad3689262)) +* **core:** Prevent node param resolution from failing telemetry graph generation ([#9257](https://github.com/n8n-io/n8n/issues/9257)) ([f6c9493](https://github.com/n8n-io/n8n/commit/f6c9493355726ddf516fb54a37adf49a2ce0efd0)) +* **core:** Stop relying on filesystem for SSH keys ([#9217](https://github.com/n8n-io/n8n/issues/9217)) ([093dcef](https://github.com/n8n-io/n8n/commit/093dcefafc5a09f7622391d8b01b9aecfa9c8f2f)) +* **Discord Node:** When using OAuth2 authentication, check if user is a guild member when sending direct message ([#9183](https://github.com/n8n-io/n8n/issues/9183)) ([00dfad3](https://github.com/n8n-io/n8n/commit/00dfad3279bd2a45a8331e734b331f4ab3fce75c)) +* **editor:** Fix read-only mode in inline expression editor ([#9232](https://github.com/n8n-io/n8n/issues/9232)) ([99f384e](https://github.com/n8n-io/n8n/commit/99f384e2cf6b16d08a8bdc150a2833463b35f14b)) +* **editor:** Prevent excess runs in manual execution with run data ([#9259](https://github.com/n8n-io/n8n/issues/9259)) ([426a12a](https://github.com/n8n-io/n8n/commit/426a12ac0ec1d637063828db008a2fb9c32ddfff)) +* **editor:** Throw expression error on attempting to set variables at runtime ([#9229](https://github.com/n8n-io/n8n/issues/9229)) ([fec04d5](https://github.com/n8n-io/n8n/commit/fec04d5f796c677b6127addcb700d6442c2c3a26)) +* Elaborate scope of Sustainable Use License ([#9233](https://github.com/n8n-io/n8n/issues/9233)) ([442aaba](https://github.com/n8n-io/n8n/commit/442aaba116cf0cfe7c1e7b8d570e321cc6a14143)) +* **Google BigQuery Node:** Better error messages, transform timestamps ([#9255](https://github.com/n8n-io/n8n/issues/9255)) ([7ff24f1](https://github.com/n8n-io/n8n/commit/7ff24f134b706d0b5b7d7c13d3e69bd1a0f4c5b8)) +* **Google Drive Node:** Create from text operation ([#9185](https://github.com/n8n-io/n8n/issues/9185)) ([d9e7494](https://github.com/n8n-io/n8n/commit/d9e74949c4db7282c3ab42bd6825aa5acc042400)) +* **Jira Trigger Node:** Update credentials UI ([#9198](https://github.com/n8n-io/n8n/issues/9198)) ([ed98ca2](https://github.com/n8n-io/n8n/commit/ed98ca2fb77fc81362e6480ee6a12a64915418f9)) +* **LangChain Code Node:** Fix execution of custom n8n tools called via LC code node ([#9265](https://github.com/n8n-io/n8n/issues/9265)) ([741e829](https://github.com/n8n-io/n8n/commit/741e8299d64cd774cc35ea312433f50d865f1318)) +* **LangChain Code Node:** Fix resolution of scoped langchain modules ([#9258](https://github.com/n8n-io/n8n/issues/9258)) ([445c05d](https://github.com/n8n-io/n8n/commit/445c05dca46225e195ab122cf77d6d1088460e20)) +* **MySQL Node:** Query to statements splitting fix ([#9207](https://github.com/n8n-io/n8n/issues/9207)) ([dc84452](https://github.com/n8n-io/n8n/commit/dc844528f4554ae41037e2c25542237a74d86f3f)) + + +### Features + +* Add Ask AI to HTTP Request Node ([#8917](https://github.com/n8n-io/n8n/issues/8917)) ([cd9bc44](https://github.com/n8n-io/n8n/commit/cd9bc44bddf7fc78acec9ee7c96a40077a07615f)) +* **Gmail Node:** Add support for creating drafts using an alias ([#8728](https://github.com/n8n-io/n8n/issues/8728)) ([3986356](https://github.com/n8n-io/n8n/commit/3986356c8995998cb6ab392ae07f41efcb46d4bd)) +* **Gmail Node:** Add thread option for draft emails ([#8729](https://github.com/n8n-io/n8n/issues/8729)) ([2dd0b32](https://github.com/n8n-io/n8n/commit/2dd0b329ca243de87eb1b59bf831593f70c42784)) +* **Groq Chat Model Node:** Add support for Groq chat models ([#9250](https://github.com/n8n-io/n8n/issues/9250)) ([96f02bd](https://github.com/n8n-io/n8n/commit/96f02bd6552cf9ea75fcb8ba29c3afac9553aa25)) +* **HTTP Request Node:** Option to provide SSL Certificates in Http Request Node ([#9125](https://github.com/n8n-io/n8n/issues/9125)) ([306b68d](https://github.com/n8n-io/n8n/commit/306b68da6bb37dbce67dcf5c4791c2986750579c)) +* **Jira Software Node:** Add Wiki Markup support for Jira Cloud comments ([#8857](https://github.com/n8n-io/n8n/issues/8857)) ([756012b](https://github.com/n8n-io/n8n/commit/756012b0524e09601fada80213dd4da3057d329a)) +* **Microsoft To Do Node:** Add an option to set a reminder when updating a task ([#6918](https://github.com/n8n-io/n8n/issues/6918)) ([22b2afd](https://github.com/n8n-io/n8n/commit/22b2afdd23bef2a301cd9d3743400e0d69463b1b)) +* **MISP Node:** Rest search operations ([#9196](https://github.com/n8n-io/n8n/issues/9196)) ([b694e77](https://github.com/n8n-io/n8n/commit/b694e7743e17507b901706c5023a9aac83b903dd)) +* **Ollama Chat Model Node:** Add aditional Ollama config parameters & fix vision ([#9215](https://github.com/n8n-io/n8n/issues/9215)) ([e17e767](https://github.com/n8n-io/n8n/commit/e17e767e700a74b187706552fc879c00fd551611)) +* **Pipedrive Node:** Add busy and description options to activities ([#9208](https://github.com/n8n-io/n8n/issues/9208)) ([9b3ac16](https://github.com/n8n-io/n8n/commit/9b3ac1648f1888d79079fd50998140fd27efae97)) +* **Postgres Node:** Add option IS NOT NULL and hide value input fields ([#9241](https://github.com/n8n-io/n8n/issues/9241)) ([e896889](https://github.com/n8n-io/n8n/commit/e89688939438b2d5414155f053530bd9eb34b300)) +* **S3 Node:** Add support for self signed SSL certificates ([#9269](https://github.com/n8n-io/n8n/issues/9269)) ([ddff804](https://github.com/n8n-io/n8n/commit/ddff80416df87166627fdefc755e3f79102c5664)) +* **Telegram Node:** Disable page preview by default ([#9267](https://github.com/n8n-io/n8n/issues/9267)) ([41ce178](https://github.com/n8n-io/n8n/commit/41ce178491135b5f972974ebecec0f5f223a71ce)) +* Upgrade typeorm for separate sqlite read & write connections ([#9230](https://github.com/n8n-io/n8n/issues/9230)) ([0b52320](https://github.com/n8n-io/n8n/commit/0b523206358886d5b81d7009ce95cb9d3ba9fa40)) +* **Wise Node:** Add XML as supported format in getStatement operation ([#9193](https://github.com/n8n-io/n8n/issues/9193)) ([a424b59](https://github.com/n8n-io/n8n/commit/a424b59e4949e96c0e56319cea91fcf084a5208e)) +* **Wise Trigger Node:** Add support for balance updates ([#9189](https://github.com/n8n-io/n8n/issues/9189)) ([42a9891](https://github.com/n8n-io/n8n/commit/42a9891081e7f1a19364c406b056eee036180c24)) + + + +# [1.39.0](https://github.com/n8n-io/n8n/compare/n8n@1.38.0...n8n@1.39.0) (2024-04-24) + + +### Bug Fixes + +* **core:** Exclude oAuth callback urls from browser-id checks ([#9158](https://github.com/n8n-io/n8n/issues/9158)) ([46e432b](https://github.com/n8n-io/n8n/commit/46e432b177b4f1ae437f598674f188fb11ee1f20)) +* **core:** Improve browserId checks, and add logging ([#9161](https://github.com/n8n-io/n8n/issues/9161)) ([ff9ae54](https://github.com/n8n-io/n8n/commit/ff9ae549fdc6962e9990987c54804d2570da6a12)) +* **core:** Upgrade mysql2 to address CVE-2024-21511 ([#9206](https://github.com/n8n-io/n8n/issues/9206)) ([3996d28](https://github.com/n8n-io/n8n/commit/3996d2852a2e2a056af008a8f1a1c6cec9ba6084)) +* **editor:** Fix expression preview when previous node is selected ([#9140](https://github.com/n8n-io/n8n/issues/9140)) ([85780ea](https://github.com/n8n-io/n8n/commit/85780eade57f30e6870c314fa465d523e3646005)) +* **editor:** Fix parameter reset on credential change in Discord node ([#9137](https://github.com/n8n-io/n8n/issues/9137)) ([135ef75](https://github.com/n8n-io/n8n/commit/135ef75add8a42ce5163cce934ac5b2757ca4fe3)) +* **editor:** Fix sessionId for manual chat trigger execution ([#9187](https://github.com/n8n-io/n8n/issues/9187)) ([f5ccb5f](https://github.com/n8n-io/n8n/commit/f5ccb5fe33392654a292de34b9ed8319901d303b)) +* **editor:** Make sticky node content parameter non require to support empty stickies ([#9192](https://github.com/n8n-io/n8n/issues/9192)) ([f6142ff](https://github.com/n8n-io/n8n/commit/f6142ff275abb443940e9d8a4694c7f54c77a183)) +* **editor:** Prevent duplicate values in preview for SQL editor ([#9129](https://github.com/n8n-io/n8n/issues/9129)) ([5acbfb4](https://github.com/n8n-io/n8n/commit/5acbfb423436b94e58af0e532e567bdc3783a622)) +* **Google Sheets Node:** Fix "Append or Update" on an empty sheet ([#9175](https://github.com/n8n-io/n8n/issues/9175)) ([29ee4fa](https://github.com/n8n-io/n8n/commit/29ee4fab61c2f364b249b91c7561b176e78f37ac)) +* **Notion Node:** Add itemIndex to API and operation errors ([#9150](https://github.com/n8n-io/n8n/issues/9150)) ([946f09f](https://github.com/n8n-io/n8n/commit/946f09f62842c963e94d97555d1b5bf7789a1b99)) +* **Postgres Node:** Convert js arrays to postgres type, if column type is ARRAY ([#9160](https://github.com/n8n-io/n8n/issues/9160)) ([08e3502](https://github.com/n8n-io/n8n/commit/08e35027f1d4f483670dce44e8026c77aa4e6c3f)) +* **Respond to Webhook Node:** Fix issue stopping form trigger response ([#9157](https://github.com/n8n-io/n8n/issues/9157)) ([6c63cd9](https://github.com/n8n-io/n8n/commit/6c63cd971162d3f018b210d221ffc2a56535550a)) +* **Schedule Trigger Node:** Default to 0 minute if falsy on hourly run ([#9146](https://github.com/n8n-io/n8n/issues/9146)) ([d756609](https://github.com/n8n-io/n8n/commit/d75660982636389516cd97305e3c19912b77ea9c)) +* **Splunk Node:** Retry attempts if no response from API call, better error with suggestion to use Retry On Fail ([#9176](https://github.com/n8n-io/n8n/issues/9176)) ([05a569c](https://github.com/n8n-io/n8n/commit/05a569c1cd1f2ecf40987c5f677dad61fd6324e5)) + + +### Features + +* Add WhatsApp Business Trigger Node ([#8840](https://github.com/n8n-io/n8n/issues/8840)) ([23a2dd0](https://github.com/n8n-io/n8n/commit/23a2dd08b6e5391b61b73bdd4496cdb2f6fa9205)) +* **core:** Setup helmet.js for setting security headers ([#9027](https://github.com/n8n-io/n8n/issues/9027)) ([0ed4671](https://github.com/n8n-io/n8n/commit/0ed46711f426f7edf5fa7833673b6b07348a3bd7)) +* **core:** Upgrade mysql2 to address CVE-2024-21507, CVE-2024-21508, and CVE-2024-21509 ([#9154](https://github.com/n8n-io/n8n/issues/9154)) ([9bd8e10](https://github.com/n8n-io/n8n/commit/9bd8e10b356ab965bfee5d13bf339f057bcfdb14)) +* **n8n Form Trigger Node:** Option to remove attribution ([#9162](https://github.com/n8n-io/n8n/issues/9162)) ([699fd70](https://github.com/n8n-io/n8n/commit/699fd70c2427397455939391f95a5cd65521afb3)) +* **Webhook Node:** Setting to enable multiple outputs/methods ([#9086](https://github.com/n8n-io/n8n/issues/9086)) ([2bf0a39](https://github.com/n8n-io/n8n/commit/2bf0a3933e0d7da46be73b8671e72e69d7d472df)) +* **Zammad Node:** Add more options to the Organizations endpoint ([#9180](https://github.com/n8n-io/n8n/issues/9180)) ([15c88d6](https://github.com/n8n-io/n8n/commit/15c88d6839fb0b59fe5112b846ba61a29e9e3e45)) + + + +# [1.38.0](https://github.com/n8n-io/n8n/compare/n8n@1.37.0...n8n@1.38.0) (2024-04-17) + + +### Bug Fixes + +* **core:** Don't create multiple owners when importing credentials or workflows ([#9112](https://github.com/n8n-io/n8n/issues/9112)) ([3eb5be5](https://github.com/n8n-io/n8n/commit/3eb5be5f5a1a62d7cf39381a67c8d747c397a969)) +* **core:** Don't revert irreversibble migrations ([#9105](https://github.com/n8n-io/n8n/issues/9105)) ([3bb821f](https://github.com/n8n-io/n8n/commit/3bb821f10e2d865040fd1d89bec9836c7f98b8ef)) +* **core:** Support MySQL in `MoveSshKeysToDatabase` migration ([#9120](https://github.com/n8n-io/n8n/issues/9120)) ([cf435c3](https://github.com/n8n-io/n8n/commit/cf435c33110d620295587e61b355ead6e4819958)) +* **editor:** Do not show overlapping trash icon in the node's settings ([#9119](https://github.com/n8n-io/n8n/issues/9119)) ([c00150b](https://github.com/n8n-io/n8n/commit/c00150bb8ff88f8905536e5b4612c4c8cdd755a7)) +* **editor:** Open links from embedded chat in new tab ([#9121](https://github.com/n8n-io/n8n/issues/9121)) ([284de5d](https://github.com/n8n-io/n8n/commit/284de5d6c7af901ee11ecda4c80b3998fd6b5657)) +* **editor:** Render dates correctly in parameter hint ([#9089](https://github.com/n8n-io/n8n/issues/9089)) ([064e8f4](https://github.com/n8n-io/n8n/commit/064e8f4a1dc5afaa7ab21b770e3fbb9165805add)) +* **Execute Workflow Node:** Assign fallback pairedItem only if not present in output item and different length of input output ([#9145](https://github.com/n8n-io/n8n/issues/9145)) ([a95e401](https://github.com/n8n-io/n8n/commit/a95e4016967b2ef443ad0ea07338ab830d5c0100)) +* Fix issue with Crowdstrike credential not working correctly ([#9108](https://github.com/n8n-io/n8n/issues/9108)) ([4c16000](https://github.com/n8n-io/n8n/commit/4c16000efadbfc5961ef2befd4f6501f9f2f0b2c)) +* **HTTP Request Node:** Tolerate header name being empty ([#9138](https://github.com/n8n-io/n8n/issues/9138)) ([f6c9dbf](https://github.com/n8n-io/n8n/commit/f6c9dbf7b850e9b665bbc72090a41c45d125f996)) +* **Respond to Webhook Node:** Continue on fail and error branch support ([#9115](https://github.com/n8n-io/n8n/issues/9115)) ([86a20f6](https://github.com/n8n-io/n8n/commit/86a20f656389474cb9fb26acf406de4e7af7b34c)) + + +### Features + +* **editor:** Add object keys that need bracket access to autocomplete ([#9088](https://github.com/n8n-io/n8n/issues/9088)) ([98bcd50](https://github.com/n8n-io/n8n/commit/98bcd50bab47e384ddcb6261aa91ba843cfa3f5a)) +* **Github Node:** Add option to get pull requests ([#9094](https://github.com/n8n-io/n8n/issues/9094)) ([4d9000b](https://github.com/n8n-io/n8n/commit/4d9000bf27df5a2188a2d4a07d8e1e6a04f701d9)) +* **Google Gemini Chat Model Node:** Add support for new Google Gemini models ([#9130](https://github.com/n8n-io/n8n/issues/9130)) ([f1215cd](https://github.com/n8n-io/n8n/commit/f1215cdb6bdfb18b7a170286c2d8e8c0deb617ff)) +* **Summarize Node:** Option to continue when field to summarize can't be found in any items ([#9118](https://github.com/n8n-io/n8n/issues/9118)) ([d7abc30](https://github.com/n8n-io/n8n/commit/d7abc3010463ad21a9c162430485ebbb29d378b1)) + + + +# [1.37.0](https://github.com/n8n-io/n8n/compare/n8n@1.36.0...n8n@1.37.0) (2024-04-10) + + +### Bug Fixes + +* **API:** Accept `settings.executionOrder` in workflow creation ([#9072](https://github.com/n8n-io/n8n/issues/9072)) ([0c90c7c](https://github.com/n8n-io/n8n/commit/0c90c7c8c1cde23c56b34fde264ea4e6ec0300b2)) +* **AWS Bedrock Chat Model Node:** Improve filtering of Bedrock models & fix Claude 3 ([#9085](https://github.com/n8n-io/n8n/issues/9085)) ([cfaab0b](https://github.com/n8n-io/n8n/commit/cfaab0b829864f0d4900f7b36559c0bb1b2075a4)) +* Continue on fail / error output support for chains and agents ([#9078](https://github.com/n8n-io/n8n/issues/9078)) ([f62800c](https://github.com/n8n-io/n8n/commit/f62800cd727ecd2b4a41fe6bbef411f8bc6f0a2e)) +* **core:** Ensure `status` on Axios errors is available to the BE ([#9015](https://github.com/n8n-io/n8n/issues/9015)) ([744327c](https://github.com/n8n-io/n8n/commit/744327c20d909a0ccc2938dff8847d2b4756d9af)) +* **core:** Ensure only leader handles waiting executions ([#9014](https://github.com/n8n-io/n8n/issues/9014)) ([217b07d](https://github.com/n8n-io/n8n/commit/217b07d735feab535916cff4baa72e500e3b80ee)) +* **core:** Ensure TTL safeguard for test webhooks applies only to multi-main setup ([#9062](https://github.com/n8n-io/n8n/issues/9062)) ([ff81de3](https://github.com/n8n-io/n8n/commit/ff81de3313e8fd612104830b1b541b9dda392bb0)) +* **core:** Fix `isLeader` check in `WaitTracker` constructor ([#9100](https://github.com/n8n-io/n8n/issues/9100)) ([c2f4d7d](https://github.com/n8n-io/n8n/commit/c2f4d7d7966db9fd7f7b19772757c71d493bf647)) +* **core:** Remove binary data when deleting executions by filter ([#9056](https://github.com/n8n-io/n8n/issues/9056)) ([7bf0f90](https://github.com/n8n-io/n8n/commit/7bf0f900f193545c37849333e2964c89d96e25b2)) +* **editor:** Add fallback for expression resolution in multi-output case ([#9045](https://github.com/n8n-io/n8n/issues/9045)) ([bcd39a1](https://github.com/n8n-io/n8n/commit/bcd39a110b4ca4c35b66340cec240dfc0c83132c)) +* **editor:** Allow pinning of AI root nodes ([#9060](https://github.com/n8n-io/n8n/issues/9060)) ([32df171](https://github.com/n8n-io/n8n/commit/32df17104c13b713a36057ab9aaeef3fd03d9d24)) +* **editor:** Canvas showing error toast when clicking outside of "import workflow by url" modal ([#9001](https://github.com/n8n-io/n8n/issues/9001)) ([f6ce81e](https://github.com/n8n-io/n8n/commit/f6ce81e7da74f80f81909b24f9675f7abcdb4265)) +* **editor:** Connecting nodes to triggers when adding them together ([#9042](https://github.com/n8n-io/n8n/issues/9042)) ([f214362](https://github.com/n8n-io/n8n/commit/f2143620bab7c222e84e6cc0f5904805944e7163)) +* **editor:** Drop outgoing connections on order changed event for nodes with dynamic outputs ([#9055](https://github.com/n8n-io/n8n/issues/9055)) ([3dd70a1](https://github.com/n8n-io/n8n/commit/3dd70a17e27fd312f949fb2fcccc0bf50ce9302e)) +* **editor:** Expand range of allowed characters in expressions ([#9083](https://github.com/n8n-io/n8n/issues/9083)) ([3bcfef9](https://github.com/n8n-io/n8n/commit/3bcfef95f6c9e08b4429fd6b3fb9a67d7075b1aa)) +* **editor:** Fix displaying logic of execution retry button ([#9061](https://github.com/n8n-io/n8n/issues/9061)) ([92f6cbf](https://github.com/n8n-io/n8n/commit/92f6cbfba36d1238e5b981c018b2a5365aabfe9c)) +* **editor:** Fix execution with wait node ([#9051](https://github.com/n8n-io/n8n/issues/9051)) ([db4f8d4](https://github.com/n8n-io/n8n/commit/db4f8d49a3a87c4e893bb1496b0bc74bd804de64)) +* **editor:** Fix issue with case insensitive tags ([#9071](https://github.com/n8n-io/n8n/issues/9071)) ([caea27d](https://github.com/n8n-io/n8n/commit/caea27dbb599fb81aee59e87236463127bcfab8c)) +* **editor:** Fix issues in dark mode ([#9068](https://github.com/n8n-io/n8n/issues/9068)) ([7467aa3](https://github.com/n8n-io/n8n/commit/7467aa30e6c2a226cb9fee5f5d82fbd01db23e9e)) +* **editor:** Issue showing Auth2 callback section when all properties are overriden ([#8999](https://github.com/n8n-io/n8n/issues/8999)) ([dff8f7a](https://github.com/n8n-io/n8n/commit/dff8f7ac94e0d215f4e2a204774857d240e7f79b)) +* **editor:** Make share modal content scrollable ([#9025](https://github.com/n8n-io/n8n/issues/9025)) ([ec9fe98](https://github.com/n8n-io/n8n/commit/ec9fe98a357ad75349c6f64006ebbff7c95ff0fe)) +* **editor:** Make Webhook node pinnable ([#9047](https://github.com/n8n-io/n8n/issues/9047)) ([042aa62](https://github.com/n8n-io/n8n/commit/042aa62fc2ddae2b9d39f4a92068c10bfe5bec14)) +* **editor:** Prevent saving workflow while another save is in progress ([#9048](https://github.com/n8n-io/n8n/issues/9048)) ([3c9a1d2](https://github.com/n8n-io/n8n/commit/3c9a1d2da3aa7614ce1beec07654a8b2423f99bc)) +* **editor:** Rerun failed nodes in manual executions ([#9050](https://github.com/n8n-io/n8n/issues/9050)) ([bc6575a](https://github.com/n8n-io/n8n/commit/bc6575afbb106ea22ae1ff7b1b9057ccb665a964)) +* **editor:** UX improvements to mfa setup modal ([#9059](https://github.com/n8n-io/n8n/issues/9059)) ([4ac02dd](https://github.com/n8n-io/n8n/commit/4ac02dd5f46c78398186e94faabb2f8884c0f2ae)) +* Fix missing input panel in node details view ([#9043](https://github.com/n8n-io/n8n/issues/9043)) ([71c54cb](https://github.com/n8n-io/n8n/commit/71c54cba52f5de26bd9c086390313c211ad0e574)) +* **HTTP Request Node:** Duplicate key names support for form data ([#9040](https://github.com/n8n-io/n8n/issues/9040)) ([3e231db](https://github.com/n8n-io/n8n/commit/3e231dbfe67b6dbe87f383daa8a52c5ae02edd92)) +* **MySQL Node:** Query Parameters parse string to number ([#9011](https://github.com/n8n-io/n8n/issues/9011)) ([610ead9](https://github.com/n8n-io/n8n/commit/610ead9a3851eeee246313669d0ed9049c736a1a)) +* **Summarization Chain Node:** 'Final Prompt to Combine' and 'Individual Summary Prompt' options ([#8391](https://github.com/n8n-io/n8n/issues/8391)) ([e47e4bf](https://github.com/n8n-io/n8n/commit/e47e4bf67152fae727374974fecf294aff56c257)) +* Workflows executed from other workflows not stopping ([#9010](https://github.com/n8n-io/n8n/issues/9010)) ([0ac9851](https://github.com/n8n-io/n8n/commit/0ac985133be546f068f7f25b340c3bfdecadc08e)) + + +### Features + +* Add credential update and delete events to log streaming ([#9026](https://github.com/n8n-io/n8n/issues/9026)) ([f4f0a36](https://github.com/n8n-io/n8n/commit/f4f0a36fe1f8a792e3581849a0d8a78ce1e6f21a)) +* Allow workflow execution even if it has errors ([#9037](https://github.com/n8n-io/n8n/issues/9037)) ([eaaefd7](https://github.com/n8n-io/n8n/commit/eaaefd76da6e9dbb86568aafdcb48b183b41fe40)) +* Append item index suffix to an error message, if provided, and node has many input items ([#9070](https://github.com/n8n-io/n8n/issues/9070)) ([5793e56](https://github.com/n8n-io/n8n/commit/5793e5644aaf40abe620d8a0a4f76856b6c5ff83)) +* **core:** Improve Langsmith traces for AI executions ([#9081](https://github.com/n8n-io/n8n/issues/9081)) ([936682e](https://github.com/n8n-io/n8n/commit/936682eeaae5f7cdbdb2afbf9c3bf9d85bcd964c)) +* **core:** Prevent session hijacking ([#9057](https://github.com/n8n-io/n8n/issues/9057)) ([2826104](https://github.com/n8n-io/n8n/commit/28261047c399be0cc9c8d30015cc42b9410cebce)) +* **Email Trigger (IMAP) Node:** Migrate from `imap-simple` to `@n8n/imap` ([#8899](https://github.com/n8n-io/n8n/issues/8899)) ([9f87cc2](https://github.com/n8n-io/n8n/commit/9f87cc25a020e03710bd64835c6547f9f12c1fe2)) +* **JWT Node:** New node ([#9005](https://github.com/n8n-io/n8n/issues/9005)) ([0a9f6b3](https://github.com/n8n-io/n8n/commit/0a9f6b3de8f5548700e736b7d5f1d31c229595f5)) +* **Postgres Node:** Options keepAlive and keepAliveInitialDelayMillis ([#9067](https://github.com/n8n-io/n8n/issues/9067)) ([58518b6](https://github.com/n8n-io/n8n/commit/58518b684b6c9495aa6efd0e815a8d01f102bbe4)) + + + +# [1.36.0](https://github.com/n8n-io/n8n/compare/n8n@1.35.0...n8n@1.36.0) (2024-04-03) + + +### Bug Fixes + +* **editor:** Issue with JSON editor getting cut off ([#9000](https://github.com/n8n-io/n8n/issues/9000)) ([4668db2](https://github.com/n8n-io/n8n/commit/4668db20fb6a47b4e417ab8f31407d13af9c70f8)) +* **editor:** Fix canvas selection for touch devices that use mouse ([#9036](https://github.com/n8n-io/n8n/issues/9036)) ([286fa5c](https://github.com/n8n-io/n8n/commit/286fa5cd7eb5052d2c166145447f53b33174b62c)) +* **editor:** Fix execution debug button ([#9018](https://github.com/n8n-io/n8n/issues/9018)) ([aac77e1](https://github.com/n8n-io/n8n/commit/aac77e1668d2b3fd96c2e77b4626b7b0ae7bf233)) +* **editor:** Hover and active states not showing in execution list on dark mode ([#9002](https://github.com/n8n-io/n8n/issues/9002)) ([bead7eb](https://github.com/n8n-io/n8n/commit/bead7eb840b3c6c074364c6a44d001ea561fee1f)) +* **editor:** UI enhancements and fixes for expression inputs ([#8996](https://github.com/n8n-io/n8n/issues/8996)) ([8788e2a](https://github.com/n8n-io/n8n/commit/8788e2a35bed261e13da5c92ee31bbb414d019a4)) +* Prevent chat modal opening on 'Test workflow' click ([#9009](https://github.com/n8n-io/n8n/issues/9009)) ([3fd97e4](https://github.com/n8n-io/n8n/commit/3fd97e4c7299928a498e359b16f6f21eed9f0878)) +* Stop listening button not working in NDV ([#9023](https://github.com/n8n-io/n8n/issues/9023)) ([02219dd](https://github.com/n8n-io/n8n/commit/02219dde2fa3c16145c3985272567b334b69dd54)) + + +### Features + +* Add Salesforce Trigger Node ([#8920](https://github.com/n8n-io/n8n/issues/8920)) ([571b613](https://github.com/n8n-io/n8n/commit/571b6135dd41ef983a822f210c09e3623e8ee605)) +* Add Twilio Trigger Node ([#8859](https://github.com/n8n-io/n8n/issues/8859)) ([c204995](https://github.com/n8n-io/n8n/commit/c204995d9c5683d92cc7c7bd89c530ad3318b06d)) +* **core:** Introduce AWS secrets manager as external secrets store ([#8982](https://github.com/n8n-io/n8n/issues/8982)) ([2aab78b](https://github.com/n8n-io/n8n/commit/2aab78b058f46c7b1692503a2b3b6bfb8939c128)) +* **core:** Rate-limit login endpoint to mitigate brute force password guessing attacks ([#9028](https://github.com/n8n-io/n8n/issues/9028)) ([a6446fe](https://github.com/n8n-io/n8n/commit/a6446fe057749536344c4170395ce149340cd889)) +* **editor:** Update templates links ([#9024](https://github.com/n8n-io/n8n/issues/9024)) ([4619dec](https://github.com/n8n-io/n8n/commit/4619dec285da14bb097df225a5682ed8babd82dd)) +* **Webhook Node:** Overhaul ([#8889](https://github.com/n8n-io/n8n/issues/8889)) ([e84c27c](https://github.com/n8n-io/n8n/commit/e84c27c0cebd6fba135298ea18844045dcf55b4c)) + + + +# [1.35.0](https://github.com/n8n-io/n8n/compare/n8n@1.33.0...n8n@1.35.0) (2024-03-27) + + +### Bug Fixes + +* **Anthropic Chat Model Node:** Fix detection of chat models in docker build & add support Claude Haiku ([#8953](https://github.com/n8n-io/n8n/issues/8953)) ([76041b8](https://github.com/n8n-io/n8n/commit/76041b8587fc5943ee80338774125d1fabb8e927)) +* Chat Trigger exclude summarization node from valid ai nodes ([#8875](https://github.com/n8n-io/n8n/issues/8875)) ([4861556](https://github.com/n8n-io/n8n/commit/4861556a1c7da643fdc924f7f65dc89a7453744a)) +* **Cohere Model Node:** Fix issue with credential test ([#8916](https://github.com/n8n-io/n8n/issues/8916)) ([4f0b52c](https://github.com/n8n-io/n8n/commit/4f0b52c45d1f165159787197fd41138059b13db6)) +* **core:** Add missing `nodeCause` to paired item error ([#8976](https://github.com/n8n-io/n8n/issues/8976)) ([19d9e71](https://github.com/n8n-io/n8n/commit/19d9e71cb90d7085256496df8325564c13db3af4)) +* **core:** Assign credential ownership correctly in source control import ([#8955](https://github.com/n8n-io/n8n/issues/8955)) ([260bc07](https://github.com/n8n-io/n8n/commit/260bc07ca9484b6e82cc9dc82c68a6c1c58f4a49)) +* **core:** Ensure the generic OAuth2 API credential uses the OAuth2 credential test ([#8941](https://github.com/n8n-io/n8n/issues/8941)) ([079a114](https://github.com/n8n-io/n8n/commit/079a1147d41442bb7269d5e9da30e45019438ba2)) +* **core:** Improve handling of invalid objects in `cleanupParameterData` (no-chanhelog) ([#8910](https://github.com/n8n-io/n8n/issues/8910)) ([33ab781](https://github.com/n8n-io/n8n/commit/33ab781aef1b9107f9ecc7ec22c9b264b4eaae63)) +* **core:** Remove HTTP body for GET, HEAD, and OPTIONS requests ([#3621](https://github.com/n8n-io/n8n/issues/3621)) ([d85d0ec](https://github.com/n8n-io/n8n/commit/d85d0ecf45e8f256536bdd7cad6aab85971e8e43)) +* **core:** Stringify all Luxon DateTimes in cleanupParameterData ([#8959](https://github.com/n8n-io/n8n/issues/8959)) ([1fb0dd4](https://github.com/n8n-io/n8n/commit/1fb0dd4f1c074ad6462d42bea030e3bafecef2ad)) +* **core:** Update `follow-redirects` to address CVE-2024-28849 ([#8902](https://github.com/n8n-io/n8n/issues/8902)) ([a10120f](https://github.com/n8n-io/n8n/commit/a10120f74efa4c636f26eafc996e71bd372f8ee8)) +* **editor:** Add proper scroll to Environments push modal ([#8883](https://github.com/n8n-io/n8n/issues/8883)) ([bcbff76](https://github.com/n8n-io/n8n/commit/bcbff760553058f8fb43b379130db0cd064fd869)) +* **editor:** Fix accidental IDE code addition ([#8971](https://github.com/n8n-io/n8n/issues/8971)) ([117b57c](https://github.com/n8n-io/n8n/commit/117b57ccc5e3904d6ffc748d198d331f3008bcd3)) +* **editor:** Fix an issue with an empty chat response if not in `output` property ([#8913](https://github.com/n8n-io/n8n/issues/8913)) ([024be62](https://github.com/n8n-io/n8n/commit/024be62693e96020c284116110944e90c7bcf1a8)) +* **editor:** Fix design system component props ([#8923](https://github.com/n8n-io/n8n/issues/8923)) ([7176cd1](https://github.com/n8n-io/n8n/commit/7176cd1407e028ba8c543179b128c7e2ac9c0369)) +* **editor:** Fix opening of chat window when executing a child node ([#8789](https://github.com/n8n-io/n8n/issues/8789)) ([5f53d76](https://github.com/n8n-io/n8n/commit/5f53d76e39395a8effdfeba0677f333b509ec8c8)) +* **editor:** Fix source control docs link in add workflow button tooltip ([#8891](https://github.com/n8n-io/n8n/issues/8891)) ([a92d8bf](https://github.com/n8n-io/n8n/commit/a92d8bfc6e2fcc4bf79fc3f6564fdb864ccd3f41)) +* **editor:** Improve expression editor performance by removing watchers ([#8900](https://github.com/n8n-io/n8n/issues/8900)) ([a5261d6](https://github.com/n8n-io/n8n/commit/a5261d6ebb8fa4ac8796b04920a4fa4bc43bb397)) +* **editor:** Make inputs in the filter component regular inputs by default ([#8980](https://github.com/n8n-io/n8n/issues/8980)) ([295b650](https://github.com/n8n-io/n8n/commit/295b650fb8bd423eba506bc09a5746451db2c085)) +* **editor:** Nodes connectors improvements ([#8945](https://github.com/n8n-io/n8n/issues/8945)) ([264f918](https://github.com/n8n-io/n8n/commit/264f918d9720e2a11ec011993df1e7a4cb776882)) +* **editor:** Remove `isOwner` from IUser interface ([#8888](https://github.com/n8n-io/n8n/issues/8888)) ([6955e89](https://github.com/n8n-io/n8n/commit/6955e8991ca2ec13e6298c3c18ec2b28853ceda4)) +* **editor:** Use bracket notation for all invalid identifiers in expressions ([#8933](https://github.com/n8n-io/n8n/issues/8933)) ([0e4216d](https://github.com/n8n-io/n8n/commit/0e4216d7afb6a09547ae575fcfd13e9fac22b350)) +* **MySQL Node:** Set paired items correctly in single query batch mode ([#8940](https://github.com/n8n-io/n8n/issues/8940)) ([89df277](https://github.com/n8n-io/n8n/commit/89df277b80002f46d198d7b8bd3d81f5b815c116)) +* OpenAI Node function to preserve original tools after node execution ([#8872](https://github.com/n8n-io/n8n/issues/8872)) ([054a4fc](https://github.com/n8n-io/n8n/commit/054a4fce1a8163f2201efd846938b909c7d0e394)) +* Overhaul expression error messages related to paired item ([#8765](https://github.com/n8n-io/n8n/issues/8765)) ([45461c8](https://github.com/n8n-io/n8n/commit/45461c8cb57aa22697e58c6c52a25ab1d6d633d9)) +* **Pinecone Vector Store Node:** Fix vector store nodes execution issue ([#8968](https://github.com/n8n-io/n8n/issues/8968)) ([323b901](https://github.com/n8n-io/n8n/commit/323b9016c8c2409d5b72c6cc2fdb0cf6f2ba118b)) +* Validate custom tool names for forbidden chars ([#8878](https://github.com/n8n-io/n8n/issues/8878)) ([edce632](https://github.com/n8n-io/n8n/commit/edce632ee62fdb9485d1ed07ead7dd3c0d2afcf8)) + + +### Features + +* Add AI Error Debugging using OpenAI ([#8805](https://github.com/n8n-io/n8n/issues/8805)) ([948c383](https://github.com/n8n-io/n8n/commit/948c383999726278377f74987cd36ed6a5b39b7b)) +* Add Onedrive Trigger Node ([#8742](https://github.com/n8n-io/n8n/issues/8742)) ([ff8dd4e](https://github.com/n8n-io/n8n/commit/ff8dd4e604216203800d9b12fd5f1105356cf03e)) +* **core:** Add support for SQLite connection pooling ([#8722](https://github.com/n8n-io/n8n/issues/8722)) ([c4c319d](https://github.com/n8n-io/n8n/commit/c4c319d7cfb30772cca248a0039fd8e2b1c99eb7)) +* **editor:** Add missing extension methods for expressions ([#8845](https://github.com/n8n-io/n8n/issues/8845)) ([5e84c2a](https://github.com/n8n-io/n8n/commit/5e84c2ab89c7d0e4365b32b1c94a9c10cea56cb9)) +* **editor:** Add type information to autocomplete dropdown ([#8843](https://github.com/n8n-io/n8n/issues/8843)) ([d7bfd45](https://github.com/n8n-io/n8n/commit/d7bfd45333cc9780ae5f1424f33de2093bd1a2f9)) +* **editor:** Block the frontend when trying to access n8n from another host over http ([#8906](https://github.com/n8n-io/n8n/issues/8906)) ([669bd83](https://github.com/n8n-io/n8n/commit/669bd830e9b1b0f986d8a8b4525d0bdc3e3c0bd7)) +* **editor:** Refactor expression editors and mixins to composition API ([#8894](https://github.com/n8n-io/n8n/issues/8894)) ([0c179e4](https://github.com/n8n-io/n8n/commit/0c179e4e511e4e6075d390afc025c93630ef3241)) +* **editor:** Release `@n8n/chat@0.9.1` ([#8918](https://github.com/n8n-io/n8n/issues/8918)) ([e0c303c](https://github.com/n8n-io/n8n/commit/e0c303c6c10145a2ef72daaf4142315cf65c839a)) +* **editor:** Show tip when user can type dot after an expression ([#8931](https://github.com/n8n-io/n8n/issues/8931)) ([160dfd3](https://github.com/n8n-io/n8n/commit/160dfd383d79fc44be79e5a071dc5f6c6b67469b)) +* Fetch user cloud role and pass it on in website links ([#8942](https://github.com/n8n-io/n8n/issues/8942)) ([666867a](https://github.com/n8n-io/n8n/commit/666867a236bce519dbd1a8f9162d4ced1b80d567)) +* Telemetry include basic llm optional promps, trigger on save workflow event ([#8981](https://github.com/n8n-io/n8n/issues/8981)) ([335f363](https://github.com/n8n-io/n8n/commit/335f363ca16814d6ca1a8a92fc9da145b8eed990)) + + + +## [1.34.2](https://github.com/n8n-io/n8n/compare/n8n@1.34.1...n8n@1.34.2) (2024-03-26) + + +### Bug Fixes + +* **editor:** Nodes connectors improvements ([#8945](https://github.com/n8n-io/n8n/issues/8945)) ([6310e36](https://github.com/n8n-io/n8n/commit/6310e36c8eaf331b4116666677a82b9a75f862dc)) +* **Pinecone Vector Store Node:** Fix vector store nodes execution issue ([#8968](https://github.com/n8n-io/n8n/issues/8968)) ([9bd14c0](https://github.com/n8n-io/n8n/commit/9bd14c053c8b5410a49d3a5f3354e5ed0feed3b3)) + + + +## [1.34.1](https://github.com/n8n-io/n8n/compare/n8n@1.34.0...n8n@1.34.1) (2024-03-25) + + +### Bug Fixes + +* **Anthropic Chat Model Node:** Fix detection of chat models in docker build & add support Claude Haiku ([#8953](https://github.com/n8n-io/n8n/issues/8953)) ([becc804](https://github.com/n8n-io/n8n/commit/becc8045646bfd6ace40895288f5f5a323c7fc8e)) +* **core:** Ensure the generic OAuth2 API credential uses the OAuth2 credential test ([#8941](https://github.com/n8n-io/n8n/issues/8941)) ([578f01a](https://github.com/n8n-io/n8n/commit/578f01a02ccdef014938dfd3194bae182e02442a)) +* **core:** Stringify all Luxon DateTimes in cleanupParameterData ([#8959](https://github.com/n8n-io/n8n/issues/8959)) ([58d9983](https://github.com/n8n-io/n8n/commit/58d9983d0efd50d01d8406b949a4e7a3db63e465)) +* **editor:** Fix opening of chat window when executing a child node ([#8789](https://github.com/n8n-io/n8n/issues/8789)) ([e695927](https://github.com/n8n-io/n8n/commit/e69592784965f24a9c061f9a10bae58a72d2cc69)) +* **editor:** Use bracket notation for all invalid identifiers in expressions ([#8933](https://github.com/n8n-io/n8n/issues/8933)) ([1316f2d](https://github.com/n8n-io/n8n/commit/1316f2d682d847d314e0175781a4fe7561205324)) +* **MySQL Node:** Set paired items correctly in single query batch mode ([#8940](https://github.com/n8n-io/n8n/issues/8940)) ([5d129ba](https://github.com/n8n-io/n8n/commit/5d129baa2df3ff9cb2a608d4162e645e5dc64ae6)) +* Overhaul expression error messages related to paired item ([#8765](https://github.com/n8n-io/n8n/issues/8765)) ([09654f9](https://github.com/n8n-io/n8n/commit/09654f9dcca77710d91b3a6543ce50fb933eb870)) + + + +# [1.34.0](https://github.com/n8n-io/n8n/compare/n8n@1.33.0...n8n@1.34.0) (2024-03-20) + + +### Bug Fixes + +* Chat Trigger exclude summarization node from valid ai nodes ([#8875](https://github.com/n8n-io/n8n/issues/8875)) ([4861556](https://github.com/n8n-io/n8n/commit/4861556a1c7da643fdc924f7f65dc89a7453744a)) +* **Cohere Model Node:** Fix issue with credential test ([#8916](https://github.com/n8n-io/n8n/issues/8916)) ([4f0b52c](https://github.com/n8n-io/n8n/commit/4f0b52c45d1f165159787197fd41138059b13db6)) +* **core:** Improve handling of invalid objects in `cleanupParameterData` (no-chanhelog) ([#8910](https://github.com/n8n-io/n8n/issues/8910)) ([33ab781](https://github.com/n8n-io/n8n/commit/33ab781aef1b9107f9ecc7ec22c9b264b4eaae63)) +* **core:** Remove HTTP body for GET, HEAD, and OPTIONS requests ([#3621](https://github.com/n8n-io/n8n/issues/3621)) ([d85d0ec](https://github.com/n8n-io/n8n/commit/d85d0ecf45e8f256536bdd7cad6aab85971e8e43)) +* **core:** Update `follow-redirects` to address CVE-2024-28849 ([#8902](https://github.com/n8n-io/n8n/issues/8902)) ([a10120f](https://github.com/n8n-io/n8n/commit/a10120f74efa4c636f26eafc996e71bd372f8ee8)) +* **editor:** Add proper scroll to Environments push modal ([#8883](https://github.com/n8n-io/n8n/issues/8883)) ([bcbff76](https://github.com/n8n-io/n8n/commit/bcbff760553058f8fb43b379130db0cd064fd869)) +* **editor:** Fix an issue with an empty chat response if not in `output` property ([#8913](https://github.com/n8n-io/n8n/issues/8913)) ([024be62](https://github.com/n8n-io/n8n/commit/024be62693e96020c284116110944e90c7bcf1a8)) +* **editor:** Fix design system component props ([#8923](https://github.com/n8n-io/n8n/issues/8923)) ([7176cd1](https://github.com/n8n-io/n8n/commit/7176cd1407e028ba8c543179b128c7e2ac9c0369)) +* **editor:** Fix source control docs link in add workflow button tooltip ([#8891](https://github.com/n8n-io/n8n/issues/8891)) ([a92d8bf](https://github.com/n8n-io/n8n/commit/a92d8bfc6e2fcc4bf79fc3f6564fdb864ccd3f41)) +* **editor:** Improve expression editor performance by removing watchers ([#8900](https://github.com/n8n-io/n8n/issues/8900)) ([a5261d6](https://github.com/n8n-io/n8n/commit/a5261d6ebb8fa4ac8796b04920a4fa4bc43bb397)) +* **editor:** Remove `isOwner` from IUser interface ([#8888](https://github.com/n8n-io/n8n/issues/8888)) ([6955e89](https://github.com/n8n-io/n8n/commit/6955e8991ca2ec13e6298c3c18ec2b28853ceda4)) +* OpenAI Node function to preserve original tools after node execution ([#8872](https://github.com/n8n-io/n8n/issues/8872)) ([054a4fc](https://github.com/n8n-io/n8n/commit/054a4fce1a8163f2201efd846938b909c7d0e394)) +* Validate custom tool names for forbidden chars ([#8878](https://github.com/n8n-io/n8n/issues/8878)) ([edce632](https://github.com/n8n-io/n8n/commit/edce632ee62fdb9485d1ed07ead7dd3c0d2afcf8)) + + +### Features + +* Add AI Error Debugging using OpenAI ([#8805](https://github.com/n8n-io/n8n/issues/8805)) ([948c383](https://github.com/n8n-io/n8n/commit/948c383999726278377f74987cd36ed6a5b39b7b)) +* Add Onedrive Trigger Node ([#8742](https://github.com/n8n-io/n8n/issues/8742)) ([ff8dd4e](https://github.com/n8n-io/n8n/commit/ff8dd4e604216203800d9b12fd5f1105356cf03e)) +* **core:** Add support for SQLite connection pooling ([#8722](https://github.com/n8n-io/n8n/issues/8722)) ([c4c319d](https://github.com/n8n-io/n8n/commit/c4c319d7cfb30772cca248a0039fd8e2b1c99eb7)) +* **editor:** Add missing extension methods for expressions ([#8845](https://github.com/n8n-io/n8n/issues/8845)) ([5e84c2a](https://github.com/n8n-io/n8n/commit/5e84c2ab89c7d0e4365b32b1c94a9c10cea56cb9)) +* **editor:** Add type information to autocomplete dropdown ([#8843](https://github.com/n8n-io/n8n/issues/8843)) ([d7bfd45](https://github.com/n8n-io/n8n/commit/d7bfd45333cc9780ae5f1424f33de2093bd1a2f9)) +* **editor:** Block the frontend when trying to access n8n from another host over http ([#8906](https://github.com/n8n-io/n8n/issues/8906)) ([669bd83](https://github.com/n8n-io/n8n/commit/669bd830e9b1b0f986d8a8b4525d0bdc3e3c0bd7)) +* **editor:** Refactor expression editors and mixins to composition API ([#8894](https://github.com/n8n-io/n8n/issues/8894)) ([0c179e4](https://github.com/n8n-io/n8n/commit/0c179e4e511e4e6075d390afc025c93630ef3241)) +* **editor:** Release `@n8n/chat@0.9.1` ([#8918](https://github.com/n8n-io/n8n/issues/8918)) ([e0c303c](https://github.com/n8n-io/n8n/commit/e0c303c6c10145a2ef72daaf4142315cf65c839a)) + +# [1.33.0](https://github.com/n8n-io/n8n/compare/n8n@1.32.0...n8n@1.33.0) (2024-03-13) + + +### Bug Fixes + +* **core:** Always register webhooks on startup ([#8830](https://github.com/n8n-io/n8n/issues/8830)) ([c6f6254](https://github.com/n8n-io/n8n/commit/c6f6254c0e5197d4c0ba19aa52a1714e991a33b2)) +* **core:** Add fallback for pairedItem info in runPartialWorkflow ([#8842](https://github.com/n8n-io/n8n/issues/8842)) ([bd465d3](https://github.com/n8n-io/n8n/commit/bd465d394aa9eb74a127748ebda1fc84a158d236)) +* **core:** Display readable error when manual executions contains large payload ([#8834](https://github.com/n8n-io/n8n/issues/8834)) ([261b9c7](https://github.com/n8n-io/n8n/commit/261b9c73d6fbb06610839b80bb1f89125893b89b)) +* **core:** Ignore semver range when upgrading comunity packages ([#8863](https://github.com/n8n-io/n8n/issues/8863)) ([11173a0](https://github.com/n8n-io/n8n/commit/11173a011468160999e2c3f4a380741edf3ba37e)) +* **editor:** Allow sharee to use workflows with http request node without credential access ([#8841](https://github.com/n8n-io/n8n/issues/8841)) ([bde4c6c](https://github.com/n8n-io/n8n/commit/bde4c6c7a19b7275cabbbc3e4d0c5ec14be54769)) +* **editor:** Disable pinning for root nodes from canvas ([#8848](https://github.com/n8n-io/n8n/issues/8848)) ([e10fa37](https://github.com/n8n-io/n8n/commit/e10fa379d3212fd9fd964d8468add07b257af7e1)) +* **editor:** Fix workflow card open action ([#8839](https://github.com/n8n-io/n8n/issues/8839)) ([dd40570](https://github.com/n8n-io/n8n/commit/dd405700568acdc893358ef239e8f8637da66307)) +* **editor:** Make inputs in the filter component expressions by default ([#8784](https://github.com/n8n-io/n8n/issues/8784)) ([6e2aa40](https://github.com/n8n-io/n8n/commit/6e2aa405fcdff3f9b1f75d9e22719f6c0c500aa9)) +* **editor:** Improve filter component error handling ([#8832](https://github.com/n8n-io/n8n/issues/8832)) ([76fe960](https://github.com/n8n-io/n8n/commit/76fe960a7613b51a95720504537112fba13bb1d6)) +* **GitHub Document Loader Node:** Fix issue with ignore paths not working correctly ([#8798](https://github.com/n8n-io/n8n/issues/8798)) ([c8d589c](https://github.com/n8n-io/n8n/commit/c8d589cce74dc1267d8058fc8f0b61b516969b79)) +* **Notion Node:** Regex for block id ([#8860](https://github.com/n8n-io/n8n/issues/8860)) ([a1f6c57](https://github.com/n8n-io/n8n/commit/a1f6c570d63a8acd7b623fb039bfdbcd017dc022)) +* **OpenAI Node:** text > message hide tools connector for unsupported models ([#8866](https://github.com/n8n-io/n8n/issues/8866)) ([cef7c24](https://github.com/n8n-io/n8n/commit/cef7c24b7779000386e45dae4c3ee25dd2d6251b)) + + +### Features + +* **editor:** Add more AI node info to telemetry ([#8827](https://github.com/n8n-io/n8n/issues/8827)) ([ed6dc86](https://github.com/n8n-io/n8n/commit/ed6dc86d60e30ed6e5992e0bd1b27e8cd59d689b)) +* **editor:** Add sections to autocomplete dropdown ([#8720](https://github.com/n8n-io/n8n/issues/8720)) ([9b4618d](https://github.com/n8n-io/n8n/commit/9b4618dd5e58337b73e8804f11d7aca9a9bf1dc3)) +* **editor:** Help users discover expressions when using drag n drop ([#8869](https://github.com/n8n-io/n8n/issues/8869)) ([e78cc2d](https://github.com/n8n-io/n8n/commit/e78cc2d8d2d22bdea30af45edd8e319d479deb55)) +* **editor:** Improve errors in output panel ([#8644](https://github.com/n8n-io/n8n/issues/8644)) ([5301323](https://github.com/n8n-io/n8n/commit/5301323906663a64a3042bd2f8946e5f1e3f6293)) +* **Pinecone Vector Store Node:** Support serverless environments ([#8849](https://github.com/n8n-io/n8n/issues/8849)) ([a136a73](https://github.com/n8n-io/n8n/commit/a136a73e4e15b5d2d1c11ef90caec75d0318f829)) +* **Wordpress Node:** Support WordPress pages ([#8852](https://github.com/n8n-io/n8n/issues/8852)) ([a678e85](https://github.com/n8n-io/n8n/commit/a678e8570b72514b0b5da6fcac4da5534d05fee8)) + + + +# [1.32.0](https://github.com/n8n-io/n8n/compare/n8n@1.31.0...n8n@1.32.0) (2024-03-06) + + +### Bug Fixes + +* AI agents, throw error on duplicate names in dynamic tools ([#8766](https://github.com/n8n-io/n8n/issues/8766)) ([75e4df1](https://github.com/n8n-io/n8n/commit/75e4df138fc103a3370239e906b9e600dc14c835)) +* **Basic LLM Chain Node:** Fix retrieving of prompt parameter for v1.3 of the node ([#8817](https://github.com/n8n-io/n8n/issues/8817)) ([82f66c8](https://github.com/n8n-io/n8n/commit/82f66c87e0e6969535559aaa879fe1f66c6ff31f)) +* **editor:** Fix NDV output tabs resetting on any click ([#8808](https://github.com/n8n-io/n8n/issues/8808)) ([c7c1767](https://github.com/n8n-io/n8n/commit/c7c17673cbdffd328032c9ec09be17c1f9018a4e)) +* **editor:** Fix opening of node creator for sub-nodes connection hint link ([#8809](https://github.com/n8n-io/n8n/issues/8809)) ([df064af](https://github.com/n8n-io/n8n/commit/df064af6451cae0d5e389abae98fd9516b448272)) +* **editor:** Fix retrieving of messages from memory in chat modal ([#8807](https://github.com/n8n-io/n8n/issues/8807)) ([bfda8ea](https://github.com/n8n-io/n8n/commit/bfda8ead0c449b262d57f14c1e8a077d21bac441)) +* **editor:** Set correct type for right input in filter component ([#8771](https://github.com/n8n-io/n8n/issues/8771)) ([5d54663](https://github.com/n8n-io/n8n/commit/5d5466343e61125c0682d65a6829cbaf816d5a90)) +* **editor:** Update assignment hint when user hovers table row ([#8782](https://github.com/n8n-io/n8n/issues/8782)) ([8c993aa](https://github.com/n8n-io/n8n/commit/8c993aa59dd6712703d5cc0a84949b0fa7dd874a)) +* **editor:** Upgrade sanitize-html to address CVE-2024-21501 ([#8816](https://github.com/n8n-io/n8n/issues/8816)) ([a3e9e3d](https://github.com/n8n-io/n8n/commit/a3e9e3db62f9794fe4b3ae414a2d252edb6196aa)) +* **Google Drive Node:** Add supportsAllDrives: true to update and download ([#8786](https://github.com/n8n-io/n8n/issues/8786)) ([11a5331](https://github.com/n8n-io/n8n/commit/11a5331e038fc383e454230dd9996015d7ce9b16)) +* **HubSpot Node:** Include properties for contact and deal in getAll operation ([#8772](https://github.com/n8n-io/n8n/issues/8772)) ([08e2b06](https://github.com/n8n-io/n8n/commit/08e2b068fb241b3e44eebbc8d6053603d977ca32)) +* **n8n Form Trigger Node:** Do not open pop up when data is pinned in trigger ([#8781](https://github.com/n8n-io/n8n/issues/8781)) ([0481e6e](https://github.com/n8n-io/n8n/commit/0481e6e6e7259cef5f4ac1c88dd0b45ef402f166)) +* **OpenAI Node:** Message text operation parameters case fix ([#8804](https://github.com/n8n-io/n8n/issues/8804)) ([e38e96b](https://github.com/n8n-io/n8n/commit/e38e96bbec4a5f355ab0a8bb87bad3787032e9c5)) +* Simplify Structured Output Parser wrapping and fix auto-fixing output parser ([#8778](https://github.com/n8n-io/n8n/issues/8778)) ([7d82dc1](https://github.com/n8n-io/n8n/commit/7d82dc1ea8c86c2a3575c3629b29af3cfb709f44)) +* **TheHive 5 Node:** The Hive - Custom fields are no longer working ([#8780](https://github.com/n8n-io/n8n/issues/8780)) ([961d6b9](https://github.com/n8n-io/n8n/commit/961d6b9266344e221e21a771546b49c0c13ea80c)) +* **Wait Node:** Change default wait period to 5 seconds ([#8783](https://github.com/n8n-io/n8n/issues/8783)) ([a521e7a](https://github.com/n8n-io/n8n/commit/a521e7a54d746189f098ae31fc06a92975dab5a9)) + + +### Features + +* Chat Memory Manager group messages option, on insert delete return confirmation of success instead of messages ([#8757](https://github.com/n8n-io/n8n/issues/8757)) ([246bfb9](https://github.com/n8n-io/n8n/commit/246bfb9ad4349cc17c2df64b357ddb96f794dfc5)) +* **core:** Update hashing strategy for JWTs ([#8810](https://github.com/n8n-io/n8n/issues/8810)) ([cdec7c9](https://github.com/n8n-io/n8n/commit/cdec7c9334ef83a7e667a8bd5a649f165402f4e5)) +* **Notion Node:** Continue on fail support ([#8788](https://github.com/n8n-io/n8n/issues/8788)) ([4850f6a](https://github.com/n8n-io/n8n/commit/4850f6a9cca3ae5b3a08e8b68d5404293367465a)) +* Track node errors on PostHog ([#8774](https://github.com/n8n-io/n8n/issues/8774)) ([35f6826](https://github.com/n8n-io/n8n/commit/35f6826150ed91c679c855aa4ce5d1f5d5e072bd)) + + +### Performance Improvements + +* Upgrade Vue ([#8806](https://github.com/n8n-io/n8n/issues/8806)) ([1600433](https://github.com/n8n-io/n8n/commit/16004331b13d5d4cf3d379ab9c98fc898db14eeb)) + + + +# [1.31.0](https://github.com/n8n-io/n8n/compare/n8n@1.30.0...n8n@1.31.0) (2024-02-28) + + +### Bug Fixes + +* **core:** Ensure `maxRedirects` is used for any http request defining it ([#8706](https://github.com/n8n-io/n8n/issues/8706)) ([246c988](https://github.com/n8n-io/n8n/commit/246c988b9373a838086f37e603ec2827cf849588)) +* **core:** Fix pairedItem issue with partial manual executions ([#8575](https://github.com/n8n-io/n8n/issues/8575)) ([a29b41e](https://github.com/n8n-io/n8n/commit/a29b41ec55d8a0cf5610a53087e455b7e649b8bc)) +* **Default Data Loader Node:** Fix binary data loader in s3 mode ([#8626](https://github.com/n8n-io/n8n/issues/8626)) ([a5e6f59](https://github.com/n8n-io/n8n/commit/a5e6f5928ae39f19d6cb55a234818e776141e325)) +* **editor:** Do not break NDV for version-less nodes ([#8714](https://github.com/n8n-io/n8n/issues/8714)) ([8a88d15](https://github.com/n8n-io/n8n/commit/8a88d156847852b38e1fd13f3b9240887491a665)) +* **editor:** Hide previous execution data for sub-nodes in debug mode if it has execution error ([#8710](https://github.com/n8n-io/n8n/issues/8710)) ([a973b9c](https://github.com/n8n-io/n8n/commit/a973b9c077d28faa45b527cf6e0f0e6644cf354a)) +* **editor:** Update Filter component state when value is updated ([#8684](https://github.com/n8n-io/n8n/issues/8684)) ([3ba2cdc](https://github.com/n8n-io/n8n/commit/3ba2cdcadbf4df4e4521cb03bf63f13a32a926a5)) +* Fix execution error when using AI chain nodes with non-chat model ([#8724](https://github.com/n8n-io/n8n/issues/8724)) ([0882dc0](https://github.com/n8n-io/n8n/commit/0882dc0ce9ad4c9260390f99be56df2d6f7b5e86)) +* **Postgres Trigger Node:** `closeFunction` errors should not prevent a workflow from being deactivated ([#8738](https://github.com/n8n-io/n8n/issues/8738)) ([7012577](https://github.com/n8n-io/n8n/commit/7012577fce796c6d18ab8081f90014a8cded7391)) +* Send user id when setting up an account ([#8639](https://github.com/n8n-io/n8n/issues/8639)) ([27f3166](https://github.com/n8n-io/n8n/commit/27f3166272455627a2d2f851a286126310a4d5b5)) +* **Trello Node:** Remove GET request body ([#8715](https://github.com/n8n-io/n8n/issues/8715)) ([8c4a744](https://github.com/n8n-io/n8n/commit/8c4a744c56ce84984ed837583cdfd7a296de5090)) +* Wrong prompt input key for sql agent ([#8708](https://github.com/n8n-io/n8n/issues/8708)) ([7c1cf33](https://github.com/n8n-io/n8n/commit/7c1cf33616eb1990a9d6d7f4b93e91575f2cddc8)) + + +### Features + +* Add env variables to support exposing `/workflows/demo` route and `/nodes.json` route ([#8506](https://github.com/n8n-io/n8n/issues/8506)) ([4b01335](https://github.com/n8n-io/n8n/commit/4b01335aa45d93b0e4f2b7c69503430f1bcca28a)) +* Add Outlook Trigger Node ([#8656](https://github.com/n8n-io/n8n/issues/8656)) ([720ae1b](https://github.com/n8n-io/n8n/commit/720ae1b96b4c6fd644bad60191c35d8d598ad666)) +* Add support for Ollama embeddings API ([#8732](https://github.com/n8n-io/n8n/issues/8732)) ([15490ad](https://github.com/n8n-io/n8n/commit/15490ad1d47c4f0d5c3f9eb350b2a1bcad4bbec0)) +* **AI Agent Node:** Allow use of Azure Chat model for OpenAI Functions agent ([#8725](https://github.com/n8n-io/n8n/issues/8725)) ([d03d927](https://github.com/n8n-io/n8n/commit/d03d9276f923d541f9c9ef86b8dc232f2737e30b)) +* Allow instance owners and admins to edit all credentials ([#8716](https://github.com/n8n-io/n8n/issues/8716)) ([7371708](https://github.com/n8n-io/n8n/commit/737170893d17108098c14db6be80071e8ef51930)) +* **editor:** AI Floating Nodes ([#8703](https://github.com/n8n-io/n8n/issues/8703)) ([41b191e](https://github.com/n8n-io/n8n/commit/41b191e0552aa2d92d442d1dea05913e8b386d4d)) +* **editor:** Retrieve previous chat message on arrow-up ([#8696](https://github.com/n8n-io/n8n/issues/8696)) ([246f8cf](https://github.com/n8n-io/n8n/commit/246f8cfcc3acdeb323849c94542fc4ad028c4f77)) +* No expression error when node hasn’t executed ([#8448](https://github.com/n8n-io/n8n/issues/8448)) ([f9a99ec](https://github.com/n8n-io/n8n/commit/f9a99ec0295499d95534d64e016f70339a56956b)) +* Session is selector for memory nodes ([#8736](https://github.com/n8n-io/n8n/issues/8736)) ([2aaf211](https://github.com/n8n-io/n8n/commit/2aaf211dfc270920a4885a2b086b98ab8a3c2af6)) +* SQL agent improvements ([#8709](https://github.com/n8n-io/n8n/issues/8709)) ([0952430](https://github.com/n8n-io/n8n/commit/09524304e6a8d1fdcdfe6340b71a5a443b942d6d)) + + + +# [1.30.0](https://github.com/n8n-io/n8n/compare/n8n@1.29.0...n8n@1.30.0) (2024-02-21) + + +### Bug Fixes + +* **AwsS3 Node:** Use location constrain ([#8654](https://github.com/n8n-io/n8n/issues/8654)) ([c73aeee](https://github.com/n8n-io/n8n/commit/c73aeeec3d296b3a6e203eba4513f6bd4bb65732)) +* **core:** Apply correct hostname to redirected requests ([#8674](https://github.com/n8n-io/n8n/issues/8674)) ([0e36aeb](https://github.com/n8n-io/n8n/commit/0e36aeb421997948ef5d5170fa8d9b50d25b852d)) +* **core:** Define `SHELL` env variable on docker images ([#8670](https://github.com/n8n-io/n8n/issues/8670)) ([e1a4fde](https://github.com/n8n-io/n8n/commit/e1a4fde207e392c372e3876946d6a5740721e253)) +* **editor:** Correctly set condition operator when changed ([#8700](https://github.com/n8n-io/n8n/issues/8700)) ([23a1bc4](https://github.com/n8n-io/n8n/commit/23a1bc40a2eb8d340eea635a48c75f59369095bb)) +* **editor:** Escape node names with quotes in autocomplete and drag'n'drop ([#8663](https://github.com/n8n-io/n8n/issues/8663)) ([890c2bd](https://github.com/n8n-io/n8n/commit/890c2bd52bd2ccd94fbc2e626dceda30554f9e82)) +* **editor:** Filter component: do not clear expression when changing operator ([#8635](https://github.com/n8n-io/n8n/issues/8635)) ([66cbe54](https://github.com/n8n-io/n8n/commit/66cbe54e1d0ec48a36e6b6b15aaf7201970932e7)) +* **editor:** Fix node runData and pinned data check on manual run ([#8669](https://github.com/n8n-io/n8n/issues/8669)) ([40c7f77](https://github.com/n8n-io/n8n/commit/40c7f77a35ef4e9bd4dbd9f28886b1b9e6af416f)) +* **editor:** Fix SQL editors not always re-rendering when query changes ([#8621](https://github.com/n8n-io/n8n/issues/8621)) ([8e9d310](https://github.com/n8n-io/n8n/commit/8e9d3106a5792a182753e4030c135893f8bad27e)) +* **editor:** Fix unnecessary execution of nodes when there is pin data ([#8567](https://github.com/n8n-io/n8n/issues/8567)) ([46fe544](https://github.com/n8n-io/n8n/commit/46fe544b9a38ea81093d846917af970141b8c86a)) +* **editor:** Handle drag-n-dropping from other nodes in assignment component ([#8661](https://github.com/n8n-io/n8n/issues/8661)) ([c943a51](https://github.com/n8n-io/n8n/commit/c943a51a28953c2ecd0f3ae4849fd8a0de187bfc)) +* **editor:** Send protocol and n8n version in templates destination parameter, stop redirecting template preview page to website ([#8691](https://github.com/n8n-io/n8n/issues/8691)) ([a573146](https://github.com/n8n-io/n8n/commit/a57314613586841c14e86f009bf42793545c33f8)) +* **editor:** Use proper composable for localization ([#8660](https://github.com/n8n-io/n8n/issues/8660)) ([9c0fe41](https://github.com/n8n-io/n8n/commit/9c0fe413d9beb669b8b9807f98569f46a1682855)) +* **editor:** Wrap expressions in resource locator component ([#8673](https://github.com/n8n-io/n8n/issues/8673)) ([e2f2fc9](https://github.com/n8n-io/n8n/commit/e2f2fc910df5ee06fa0b57b85689b5674b6774ba)) +* **Extract From File Node:** Make binary data work with any backend ([#8647](https://github.com/n8n-io/n8n/issues/8647)) ([d33d953](https://github.com/n8n-io/n8n/commit/d33d953497d25a50f17c71f7ecc7b9cc6332816e)) +* **FTP Node:** Continue of fail looping support with paired item ([#8659](https://github.com/n8n-io/n8n/issues/8659)) ([3279762](https://github.com/n8n-io/n8n/commit/327976222114956584fdb2de51c914ad57382988)) +* **FTP Node:** Fix "Maximum call stack size exceeded" error when dealing with too many files ([#8657](https://github.com/n8n-io/n8n/issues/8657)) ([5063674](https://github.com/n8n-io/n8n/commit/506367453c46485e5baff6510ce0c653ca4cc4e4)) +* **Google Calendar Node:** Errors with after/before options ([#8628](https://github.com/n8n-io/n8n/issues/8628)) ([bee17dd](https://github.com/n8n-io/n8n/commit/bee17dd6cc4eaabc252602a02d4ec109f42ef926)) +* **GraphQL Node:** Fix request format JSON error ([#8646](https://github.com/n8n-io/n8n/issues/8646)) ([bd4b50c](https://github.com/n8n-io/n8n/commit/bd4b50cf0882553b2ecb7f0b9bd93d154f775b4f)) +* **Postgres Node:** Close connection pool only if it's not already closed or closing ([#8690](https://github.com/n8n-io/n8n/issues/8690)) ([457cac4](https://github.com/n8n-io/n8n/commit/457cac4cf9f7093d48e9f4ccb0021423c18f0b8b)) + + +### Features + +* **AI Agent Node:** Whitelist Azure Chat model for Agent ([#8699](https://github.com/n8n-io/n8n/issues/8699)) ([40aecd1](https://github.com/n8n-io/n8n/commit/40aecd1715d4fec7e5024167e89d4b2171ec19e8)) +* **core:** Add support for $("NodeName").isExecuted ([#8683](https://github.com/n8n-io/n8n/issues/8683)) ([ad82f0c](https://github.com/n8n-io/n8n/commit/ad82f0c0c827e535a22b0a51e408f4cddea280e0)) +* **core:** Move execution permission checks earlier in the lifecycle ([#8677](https://github.com/n8n-io/n8n/issues/8677)) ([059d281](https://github.com/n8n-io/n8n/commit/059d281fd1efdd281d3c9bf3f3a6c614d343e7ca)) +* **editor:** Add chevron to filter component operator select ([#8633](https://github.com/n8n-io/n8n/issues/8633)) ([20446bd](https://github.com/n8n-io/n8n/commit/20446bdf11bd9f3528fd7b3bad61d522f1102f4d)) +* **Google Sheets Node:** Option how to combine filters when reading rows ([#8652](https://github.com/n8n-io/n8n/issues/8652)) ([a5e522e](https://github.com/n8n-io/n8n/commit/a5e522e5360bd72a5567c96aaf0076baf0d95b35)) +* **Google Workspace Admin Node:** Add support for error output branch ([#8499](https://github.com/n8n-io/n8n/issues/8499)) ([ef77571](https://github.com/n8n-io/n8n/commit/ef775711d01e77533775bd3c1e1f9bb1c508f2b2)) +* Introduce prompt type option for Agent, Basic LLM Chain, and QA Chain nodes ([#8697](https://github.com/n8n-io/n8n/issues/8697)) ([2068f18](https://github.com/n8n-io/n8n/commit/2068f186ffd64766051294e8652689618a0df3fb)) + + + +# [1.29.0](https://github.com/n8n-io/n8n/compare/n8n@1.28.0...n8n@1.29.0) (2024-02-15) + + +### Bug Fixes + +* **core:** Custom workflow tool tweaks ([#8561](https://github.com/n8n-io/n8n/issues/8561)) ([ccc0ad5](https://github.com/n8n-io/n8n/commit/ccc0ad5009b2d547accfc34a9c0917114fd19c81)) +* **core:** Give better error message if `executions.process` is still used in the configs ([#8618](https://github.com/n8n-io/n8n/issues/8618)) ([cd8ca84](https://github.com/n8n-io/n8n/commit/cd8ca8412d02090af07d8657283b7637178c975c)) +* **core:** Improve the startup error when EXECUTIONS_PROCESS is set ([#8630](https://github.com/n8n-io/n8n/issues/8630)) ([a2a3ca1](https://github.com/n8n-io/n8n/commit/a2a3ca160f253b3b09f83bdc6dec11987971e464)) +* **core:** Upgrade `@n8n/typeorm` to address postgres timestamp issues ([#8627](https://github.com/n8n-io/n8n/issues/8627)) ([68498cb](https://github.com/n8n-io/n8n/commit/68498cb72a16d587c4b24f9fe5e8ac2450cd78b9)) +* **core:** Upgrade rudderstack sdk to address npm postInstall issues ([#8568](https://github.com/n8n-io/n8n/issues/8568)) ([5750e8e](https://github.com/n8n-io/n8n/commit/5750e8e88ed59b40d5115416c8afe227cb64c70e)) +* **editor:** Debounce expression changes ([#8629](https://github.com/n8n-io/n8n/issues/8629)) ([9c7e026](https://github.com/n8n-io/n8n/commit/9c7e0266ee1c2112649772ca553bff429dcabc2d)) +* **Execute Workflow Node:** Passing the workflow that is supposed to be executed as a paramter ([#8614](https://github.com/n8n-io/n8n/issues/8614)) ([b54488a](https://github.com/n8n-io/n8n/commit/b54488ada53a9941f5fd4d3cab8d39bd81b22f4a)) +* Fix resolving of expressions of deeply nested sub-nodes ([#8612](https://github.com/n8n-io/n8n/issues/8612)) ([f527430](https://github.com/n8n-io/n8n/commit/f5274302f843c34f0a8a0b3eb51e7f9262fe0ea4)) +* **FTP Node:** Fix issue with connections not closing properly ([#8619](https://github.com/n8n-io/n8n/issues/8619)) ([e597fbc](https://github.com/n8n-io/n8n/commit/e597fbc78ff60ce495eecd9ffdffda801ed90b66)) +* **FTP Node:** Fix issue with paireditems not always working ([#8613](https://github.com/n8n-io/n8n/issues/8613)) ([d38a822](https://github.com/n8n-io/n8n/commit/d38a822b9501a8730c9cf097a9372e96080e8722)) +* **Google Sheets Trigger Node:** First non-header row is ignored when using on row added event ([#8580](https://github.com/n8n-io/n8n/issues/8580)) ([2db8231](https://github.com/n8n-io/n8n/commit/2db8231af5baed941b5250af373898c3924d3baa)) +* **HTTP Request Node:** Errorneous binary object without content-disposition response header ([#8583](https://github.com/n8n-io/n8n/issues/8583)) ([e28b374](https://github.com/n8n-io/n8n/commit/e28b374170725efeff59ac39e4ba6385bf834e7f)) +* **HTTP Request Node:** Handle special characters in pagination expressions + improve hint text ([#8576](https://github.com/n8n-io/n8n/issues/8576)) ([3b2078c](https://github.com/n8n-io/n8n/commit/3b2078c3caf2a5acee48fed3055b47dbfe0af44a)) +* **Jira Software Node:** Fix issue with not all issue types being supported ([#8571](https://github.com/n8n-io/n8n/issues/8571)) ([a6211c9](https://github.com/n8n-io/n8n/commit/a6211c9a5df9513ef057be1bfb78b4e0e2a75c2f)) +* **Merge Node:** Fixing how paired items are handled in the merge node, when choosing a branch and selecting to return an empty object ([#8479](https://github.com/n8n-io/n8n/issues/8479)) ([a3bed97](https://github.com/n8n-io/n8n/commit/a3bed97883ece25ffb4ab3a8246418b123c92e52)) +* **Microsoft Outlook Node:** Download executes more than once per incoming item ([#8566](https://github.com/n8n-io/n8n/issues/8566)) ([053fb5f](https://github.com/n8n-io/n8n/commit/053fb5ff7a3a4ce30b35fa6c830787b935ebaf63)) +* **Notion Node:** Block with text results in a body validation error ([#8622](https://github.com/n8n-io/n8n/issues/8622)) ([6bc1c3d](https://github.com/n8n-io/n8n/commit/6bc1c3d7a83436ee3b3520adf4b52f59f2b4231c)) +* **RSS Feed Trigger Node:** Save last item's date instead of last execution date ([#8572](https://github.com/n8n-io/n8n/issues/8572)) ([a822588](https://github.com/n8n-io/n8n/commit/a822588012b66711f37d3ca363fcd04742eda1b8)) +* **Wait Node:** Account for workflow timezone in Wait node datetime ([#8578](https://github.com/n8n-io/n8n/issues/8578)) ([1116a28](https://github.com/n8n-io/n8n/commit/1116a28b4582cae93e9d44890cfe020cd813521c)) + + +### Features + +* Add support for AI log streaming ([#8526](https://github.com/n8n-io/n8n/issues/8526)) ([7501ad8](https://github.com/n8n-io/n8n/commit/7501ad8f3c56d9fcc5f4ec3d6fc468ab9cdb5024)) +* Add telemetry event when users click on templates link ([#8625](https://github.com/n8n-io/n8n/issues/8625)) ([bf4f896](https://github.com/n8n-io/n8n/commit/bf4f896373acb2530a8f322cd612a3de616d6d15)) +* **API:** Add tag support to public API ([#8588](https://github.com/n8n-io/n8n/issues/8588)) ([a743a40](https://github.com/n8n-io/n8n/commit/a743a4037674ccb68a311daa5511cb48de6c9bfa)) +* **Convert to File Node:** Operation to convert a string in a plain text file, option to format JSON when creating file ([#8620](https://github.com/n8n-io/n8n/issues/8620)) ([d18cba3](https://github.com/n8n-io/n8n/commit/d18cba37a4a690cb3b4cd4259d27aedaa97193e4)) +* **core:** Migrate to n8n's typeorm fork ([#8590](https://github.com/n8n-io/n8n/issues/8590)) ([8e392cf](https://github.com/n8n-io/n8n/commit/8e392cfc1dd103a00b3a0e7c186cbfad989a5080)) +* **editor:** Use website as the main templates repository ([#8591](https://github.com/n8n-io/n8n/issues/8591)) ([79b09fd](https://github.com/n8n-io/n8n/commit/79b09fdf84860eee4c80e0c7eb7e320f6e05eabb)) +* **HTML Extract Node:** Better text extraction, option to specify selectors to skip, option to clean up text data ([#8586](https://github.com/n8n-io/n8n/issues/8586)) ([32281d1](https://github.com/n8n-io/n8n/commit/32281d12d775281e4b8e419d76e46ca9e3b47267)) +* **OpenAI Node:** Overhaul ([#8335](https://github.com/n8n-io/n8n/issues/8335)) ([941278d](https://github.com/n8n-io/n8n/commit/941278db6880ae3734a7a37aa03d7e6106ee5009)) +* Upgrade typeorm, sqlite3, and pg/pg-promise ([#8579](https://github.com/n8n-io/n8n/issues/8579)) ([7826bd8](https://github.com/n8n-io/n8n/commit/7826bd842a8e1548c1eab8fff942d2efe8b3dc75)) + + + +# [1.28.0](https://github.com/n8n-io/n8n/compare/n8n@1.27.0...n8n@1.28.0) (2024-02-07) + + +### Bug Fixes + +* Allow Date/Luxon objects and additional formats in DateTime validation ([#8525](https://github.com/n8n-io/n8n/issues/8525)) ([c419c85](https://github.com/n8n-io/n8n/commit/c419c8592f16a002d9f460c555db5f8bc04d95b8)) +* **core:** Ensure AxiosError status always gets copied over to NodeApiError ([#8509](https://github.com/n8n-io/n8n/issues/8509)) ([76c5a62](https://github.com/n8n-io/n8n/commit/76c5a62f521fbd79a47d47f419fd90dcc6ca0275)) +* **core:** Fix DropRoleMapping migration ([#8521](https://github.com/n8n-io/n8n/issues/8521)) ([4fed68e](https://github.com/n8n-io/n8n/commit/4fed68ee34c239ed3cae8541d5d4e7f86cf28d13)) +* **core:** Fix new graceful shutdown env being always overridden by deprecated env ([#8503](https://github.com/n8n-io/n8n/issues/8503)) ([cc41fc7](https://github.com/n8n-io/n8n/commit/cc41fc7c801ecb1b0de4b7c5af5a03b3f30fdce8)) +* **core:** Fix PermissionChecker.check, and add additional unit tests ([#8528](https://github.com/n8n-io/n8n/issues/8528)) ([5832d3c](https://github.com/n8n-io/n8n/commit/5832d3ca4695ec812e028e40b41811ca2215c0e2)) +* **core:** Fix test runs of triggers that rely on static data ([#8524](https://github.com/n8n-io/n8n/issues/8524)) ([528c071](https://github.com/n8n-io/n8n/commit/528c07134a6705c8c7a5378f15f5e4a4b93234a9)) +* **core:** Fix workflow tagging failure due to unique constraint check ([#8505](https://github.com/n8n-io/n8n/issues/8505)) ([92f939f](https://github.com/n8n-io/n8n/commit/92f939f82799975a5f9b859e7f342f3440961320)) +* **core:** Upgrade nodemailer to address an exploit ([#8535](https://github.com/n8n-io/n8n/issues/8535)) ([ee5e422](https://github.com/n8n-io/n8n/commit/ee5e4220945fde5baaec9ad66ff08b8728912aa3)) +* **core:** Use hostname from URL instead of Host header for SNI ([#8562](https://github.com/n8n-io/n8n/issues/8562)) ([7531f34](https://github.com/n8n-io/n8n/commit/7531f343861d91df075b8f3220f5bce8858b117f)) +* **core:** Use trx manager instead of repository for tags overwrite ([#8557](https://github.com/n8n-io/n8n/issues/8557)) ([abddbb6](https://github.com/n8n-io/n8n/commit/abddbb622798bdc4a2b11347a09f10aaf03a4639)) +* **editor:** Prune values that are not in the schema in the ResourceMapper component ([#8478](https://github.com/n8n-io/n8n/issues/8478)) ([612771e](https://github.com/n8n-io/n8n/commit/612771e0328f7e565d5f075cc20ad86bd1f13bce)) +* **Embeddings OpenAI Node:** Fix dynamic models fetching ([#8533](https://github.com/n8n-io/n8n/issues/8533)) ([cccdfc7](https://github.com/n8n-io/n8n/commit/cccdfc73d6c13a37e395fdc2612f2ebf458a4f52)) +* **HTTP Request Node:** Require parameter with filled name and value to avoid infinite loop ([#8454](https://github.com/n8n-io/n8n/issues/8454)) ([3128dca](https://github.com/n8n-io/n8n/commit/3128dca1faeff85d77a28640b7dfe8fbcd85db4f)) +* **HTTP Request Node:** Support form data when using pagination ([#8497](https://github.com/n8n-io/n8n/issues/8497)) ([ca75744](https://github.com/n8n-io/n8n/commit/ca75744c7f93db827ece5bf3b17b82f07d4cffb1)) +* **Microsoft Excel 365 Node:** Upsert append new rows at the end of used range, option to append at the end of selected range ([#8461](https://github.com/n8n-io/n8n/issues/8461)) ([1e02d73](https://github.com/n8n-io/n8n/commit/1e02d73ad782fb21cdd9b7350e34beb731a677c5)) +* **MongoDB Node:** Fix "Maximum call stack size exceeded" error on too many rows ([#8530](https://github.com/n8n-io/n8n/issues/8530)) ([76cdf75](https://github.com/n8n-io/n8n/commit/76cdf75fb96fb55b4a6e7a1a9edc7c9674806391)) +* **Slack Node:** Attachments fix ([#8471](https://github.com/n8n-io/n8n/issues/8471)) ([254700a](https://github.com/n8n-io/n8n/commit/254700a059a48a66dea8b9d80e61c8250e09d5b5)) +* Update BaseChatModel import checks for MistralAI compatibility ([#8527](https://github.com/n8n-io/n8n/issues/8527)) ([c8b8379](https://github.com/n8n-io/n8n/commit/c8b83790150b9974d6d99f6a2b6b5b7be6fb8c53)) + + +### Features + +* Add assignment component with drag and drop to Set node ([#8283](https://github.com/n8n-io/n8n/issues/8283)) ([2799de4](https://github.com/n8n-io/n8n/commit/2799de491b753e6fb00f73b61393392b6fc8ad18)) +* Azure Open AI chat model & embeddings ([#8522](https://github.com/n8n-io/n8n/issues/8522)) ([934d0d3](https://github.com/n8n-io/n8n/commit/934d0d35b1814c0a39595d61a30fb9c2e05fd995)) +* **editor:** Add delete and disable button to nodes on hover ([#8482](https://github.com/n8n-io/n8n/issues/8482)) ([994754b](https://github.com/n8n-io/n8n/commit/994754bf39976c5bb33fd1c30a0eb82cc518850b)) +* **Email Trigger (IMAP) Node:** Upgrade mailparser ([#8539](https://github.com/n8n-io/n8n/issues/8539)) ([da1fe44](https://github.com/n8n-io/n8n/commit/da1fe44d5246848e2ba7bb8bc5f4577685fbcee0)) +* **RabbitMQ Trigger Node:** Add options to configure assert of exchanges and queues ([#8430](https://github.com/n8n-io/n8n/issues/8430)) ([4b3659f](https://github.com/n8n-io/n8n/commit/4b3659f04f84ef774d31cf2341c5dbb500a73afe)) + + + +# [1.27.0](https://github.com/n8n-io/n8n/compare/n8n@1.26.0...n8n@1.27.0) (2024-01-31) + + +### Bug Fixes + +* **AwsS3 Node:** Fix handling of bucket with dot in name ([#8475](https://github.com/n8n-io/n8n/issues/8475)) ([0febe62](https://github.com/n8n-io/n8n/commit/0febe62ad03f82b85922e0cb66d60eeb22b3a919)) +* **core:** Don't report executions that have been paused as failed to rudderstack and log streams ([#8501](https://github.com/n8n-io/n8n/issues/8501)) ([39e8754](https://github.com/n8n-io/n8n/commit/39e875478488f3c81147944bd6b52dc1f9def958)) +* **core:** Fix stopping and retrying failed executions ([#8480](https://github.com/n8n-io/n8n/issues/8480)) ([238b54c](https://github.com/n8n-io/n8n/commit/238b54c77bba6f7abcc7fc2b3ac48a85206ce37e)) +* **core:** Forward authorization header when on same domain ([#8507](https://github.com/n8n-io/n8n/issues/8507)) ([f1910a1](https://github.com/n8n-io/n8n/commit/f1910a10a6ac875b422d9efe9bfd3ca728ac8d96)) +* **core:** Handle possibly invalid `updatedAt` timestamps in source-control ([#8485](https://github.com/n8n-io/n8n/issues/8485)) ([033fd34](https://github.com/n8n-io/n8n/commit/033fd344b5a09a7b9c0b941279da1744e182cc57)) +* **core:** Handle zero execution statistics on metrics collection during license renewal ([#8463](https://github.com/n8n-io/n8n/issues/8463)) ([db48bdd](https://github.com/n8n-io/n8n/commit/db48bdd6d1110bfccc371ca490281c021227991f)) +* **core:** Improve handling of wrapped errors ([#8510](https://github.com/n8n-io/n8n/issues/8510)) ([670af16](https://github.com/n8n-io/n8n/commit/670af167e6f2c0e4e45bcc7e0998a371d651f89d)) +* **core:** Point users to the official documentation when they use `n8n --help` ([#8440](https://github.com/n8n-io/n8n/issues/8440)) ([9f11eba](https://github.com/n8n-io/n8n/commit/9f11eba0a4f726cc150b05c0f4b528bf25513e6f)) +* **core:** Prevent calling internal hook email event if emailing is disabled ([#8462](https://github.com/n8n-io/n8n/issues/8462)) ([9e93980](https://github.com/n8n-io/n8n/commit/9e939809575592622f6bdca112da1905ac9205ef)) +* **editor:** Disable expression editor modal opening on readonly field ([#8457](https://github.com/n8n-io/n8n/issues/8457)) ([eb27ed0](https://github.com/n8n-io/n8n/commit/eb27ed068ba21bbf4302686f0f0c0168e91c03f6)) +* **editor:** Fix workflows filter resetting ([#8411](https://github.com/n8n-io/n8n/issues/8411)) ([ad4b298](https://github.com/n8n-io/n8n/commit/ad4b298be34915718b0415322e328a1b46ef5842)) +* **editor:** Send template id as a number in telemetry events ([#8484](https://github.com/n8n-io/n8n/issues/8484)) ([327cc8d](https://github.com/n8n-io/n8n/commit/327cc8df7343b806bee87faaa86ed22d9d70127f)) +* **editor:** Show pin button on binary output but disable it with tooltip ([#8388](https://github.com/n8n-io/n8n/issues/8388)) ([caab97e](https://github.com/n8n-io/n8n/commit/caab97e667df5d305aa1d2e15c0d31eb5f1a84eb)) +* **Gotify Node:** Add option to set content type to support Markdown messages ([#8442](https://github.com/n8n-io/n8n/issues/8442)) ([c2ffd4e](https://github.com/n8n-io/n8n/commit/c2ffd4e6455b383e4ddc2eb310cefbcdf867d622)) +* **HTML Node:** Escape data path value in JSON Property ([#8441](https://github.com/n8n-io/n8n/issues/8441)) ([fc5c562](https://github.com/n8n-io/n8n/commit/fc5c5627850bf618be4ca0d9cdb20adb0f8610e9)) +* **Merge Node:** Passing on no items to "Input 2" results in wrong output items ([#8438](https://github.com/n8n-io/n8n/issues/8438)) ([dafacb9](https://github.com/n8n-io/n8n/commit/dafacb90c6b01e2f88c3de359ebb2d8d55e0aecc)) +* **Microsoft Excel 365 Node:** Better error and description on unsupported range in upsert, update, getRange operations ([#8452](https://github.com/n8n-io/n8n/issues/8452)) ([8a595d1](https://github.com/n8n-io/n8n/commit/8a595d1527bf8cd97ce8293c6a52929eb3335750)) +* Open executions with large number of execution items without crashing tab ([#8423](https://github.com/n8n-io/n8n/issues/8423)) ([56da2e4](https://github.com/n8n-io/n8n/commit/56da2e43528f157c421e97018366b8f2d854d11d)) +* Properly iterate over credentials with expressions ([#8502](https://github.com/n8n-io/n8n/issues/8502)) ([0e9a5a2](https://github.com/n8n-io/n8n/commit/0e9a5a2ab2cde251cf106b149bdd4c3142e52b40)) +* Use correct node version when pasting/importing nodes ([#8456](https://github.com/n8n-io/n8n/issues/8456)) ([70af67e](https://github.com/n8n-io/n8n/commit/70af67e744c709b85b600811b792def83518fa7c)) + + +### Features + +* Add model parameter to OpenAI embeddings ([#8481](https://github.com/n8n-io/n8n/issues/8481)) ([981ea39](https://github.com/n8n-io/n8n/commit/981ea3930e96c3b45267fa7ddac48710846e49ac)) +* Add new 'is empty' and 'is not empty' operators to Filter ([#8445](https://github.com/n8n-io/n8n/issues/8445)) ([c21c4b9](https://github.com/n8n-io/n8n/commit/c21c4b9178b3ae328ff7a068663eeb64fed3e465)) +* **core:** Upgrade Rudderstack SDK to address CVE-2023-45857 ([#8368](https://github.com/n8n-io/n8n/issues/8368)) ([2fba0e8](https://github.com/n8n-io/n8n/commit/2fba0e8d585aead43eaeb73fa49dc9b613900675)) +* **editor:** Implement loading and error states for dynamically loaded components in node parameter list ([#8477](https://github.com/n8n-io/n8n/issues/8477)) ([e643a12](https://github.com/n8n-io/n8n/commit/e643a126f40dbad0634e5abb1e3ba355bad0275d)) +* **editor:** Send template id as string in all telemetry events ([#8498](https://github.com/n8n-io/n8n/issues/8498)) ([2aed788](https://github.com/n8n-io/n8n/commit/2aed788dc354595b070e91fe76298f6702bbbe15)) +* **Google Calendar Node:** Next occurrence property in recurring events ([#8444](https://github.com/n8n-io/n8n/issues/8444)) ([bf11c7c](https://github.com/n8n-io/n8n/commit/bf11c7c1bd5826ba64acc665da4e3319f9a47174)) + +### ⚠️ BREAKING CHANGES +* **core:** Remove `own` execution-process mode ([#8490](https://github.com/n8n-io/n8n/issues/8490)) ([121a55b](https://github.com/n8n-io/n8n/commit/121a55b691469e7eb042737573c0ace276366ecb)) + + + +# [1.26.0](https://github.com/n8n-io/n8n/compare/n8n@1.25.0...n8n@1.26.0) (2024-01-24) + + +### Bug Fixes + +* **AMQP Trigger Node:** Properly close connection after manual test step ([#8396](https://github.com/n8n-io/n8n/issues/8396)) ([2c14371](https://github.com/n8n-io/n8n/commit/2c143714817e62fcb2fec9b9cac208ef029f87fd)) +* **Asana Node:** Fix issue when connecting to the new Asana environment ([#8404](https://github.com/n8n-io/n8n/issues/8404)) ([44f6ef2](https://github.com/n8n-io/n8n/commit/44f6ef2ed729ad39518cc85e068be03cd8b409ff)) +* **AWS SQS Node:** Fix issue preventing data from being sent correctly ([#8382](https://github.com/n8n-io/n8n/issues/8382)) ([daba5bb](https://github.com/n8n-io/n8n/commit/daba5bb250c3bee338dde96e6e815835dd21e6f1)) +* Change the UI text for some filter operations ([#8360](https://github.com/n8n-io/n8n/issues/8360)) ([976fe2e](https://github.com/n8n-io/n8n/commit/976fe2e6c8caf3f72c833af4447c46ddfa1d8e0a)) +* **core:** Adjust starter node priority for manual executions with pinned activators ([#8386](https://github.com/n8n-io/n8n/issues/8386)) ([749ac2b](https://github.com/n8n-io/n8n/commit/749ac2b407d9477343c169ac26daba1a36cfcc6d)) +* **core:** Errors are returned on the success branch if error item has other keys in addition to 'error' ([#8380](https://github.com/n8n-io/n8n/issues/8380)) ([25f51f4](https://github.com/n8n-io/n8n/commit/25f51f4fd79d14ccff8d35d92c11e47fe18f3e0d)) +* **core:** Fix removal of triggers and pollers from memory on deactivation in multi-main setup ([#8416](https://github.com/n8n-io/n8n/issues/8416)) ([2257ec6](https://github.com/n8n-io/n8n/commit/2257ec63b3716598f85a5237bc147bb2c887fa33)) +* **core:** Fix update workflow cli command being unable to activate all workflows ([#8412](https://github.com/n8n-io/n8n/issues/8412)) ([ae06fde](https://github.com/n8n-io/n8n/commit/ae06fdeb62d0cb982253f32956fdd55bd66058bd)) +* **core:** Missing pairedItem fixes ([#8394](https://github.com/n8n-io/n8n/issues/8394)) ([284d965](https://github.com/n8n-io/n8n/commit/284d965b5acc0819ffc109729ce1d5d0b2352abb)) +* **Discord Node:** Remove requirement on message for webhooks ([#8377](https://github.com/n8n-io/n8n/issues/8377)) ([c64e893](https://github.com/n8n-io/n8n/commit/c64e893b60143df6e9a752191adef0419811fe43)) +* **editor:** Add pinned data for freshly added nodes ([#8323](https://github.com/n8n-io/n8n/issues/8323)) ([83228e2](https://github.com/n8n-io/n8n/commit/83228e26fb8f62676e15d59f65a43106487034da)) +* **editor:** Enable ctrl/cmd click in workflow editor header ([#8387](https://github.com/n8n-io/n8n/issues/8387)) ([e43cf2f](https://github.com/n8n-io/n8n/commit/e43cf2fd715e21fd4e454c9e6b6d874306472360)) +* **editor:** Fix copy to clipboard on insecure contexts ([#8425](https://github.com/n8n-io/n8n/issues/8425)) ([7386f79](https://github.com/n8n-io/n8n/commit/7386f79362673876509c27a2f6ddef08125a0b1e)) +* **editor:** Fix doclines for `plus` and `minus` ([#8405](https://github.com/n8n-io/n8n/issues/8405)) ([ebf2b0d](https://github.com/n8n-io/n8n/commit/ebf2b0d55ccf3977269d4ea442f2ad210d1b375b)) +* **editor:** Fix invisible community package update button ([#8406](https://github.com/n8n-io/n8n/issues/8406)) ([2ccb754](https://github.com/n8n-io/n8n/commit/2ccb754e52949e0d20925871f425eef92cd6aebc)) +* **editor:** Fix secondary icon for environments on sidebar menu item ([#8407](https://github.com/n8n-io/n8n/issues/8407)) ([3544966](https://github.com/n8n-io/n8n/commit/35449667bfab7324350fe92e1f6538e3ae3cadb1)) +* **editor:** Open native context menu when editing Sticky ([#8370](https://github.com/n8n-io/n8n/issues/8370)) ([ade7d30](https://github.com/n8n-io/n8n/commit/ade7d30053f897bd9269912565d86d33046e560e)) +* **editor:** Use web native element in nav menus ([#8385](https://github.com/n8n-io/n8n/issues/8385)) ([e606e84](https://github.com/n8n-io/n8n/commit/e606e841ee1086d737849dc33f4ced867ab2cb21)) +* Fix issue preventing secrets with a - in the path from being imported ([#8378](https://github.com/n8n-io/n8n/issues/8378)) ([fc94377](https://github.com/n8n-io/n8n/commit/fc9437703687738091fdd072e03597358bd8f8d0)) +* Force posthog recording to be disabled outside cloud ([#8374](https://github.com/n8n-io/n8n/issues/8374)) ([f31cc07](https://github.com/n8n-io/n8n/commit/f31cc0743ff94c9b29cdd0d498c87340beb29585)) +* **Google Drive Node:** Fix issue preventing upload / update working in some configurations ([#8417](https://github.com/n8n-io/n8n/issues/8417)) ([4b3ea81](https://github.com/n8n-io/n8n/commit/4b3ea81028ae6dcbe3a4738dca7522b62685bb42)) +* **Microsoft Outlook Node:** Message -> Send with attachments ([#8238](https://github.com/n8n-io/n8n/issues/8238)) ([0128081](https://github.com/n8n-io/n8n/commit/01280815c950413188905f5d17a13157685d0a27)) +* **Microsoft SQL Node:** Prevent MSSQL max parameters error by chunking ([#8390](https://github.com/n8n-io/n8n/issues/8390)) ([1b0ba2c](https://github.com/n8n-io/n8n/commit/1b0ba2c02885a0dd9f548133efadcfc7485d666d)) +* **Notion Node:** Fix is_empty query on formula fields ([#8397](https://github.com/n8n-io/n8n/issues/8397)) ([08e7db4](https://github.com/n8n-io/n8n/commit/08e7db4648cd2f820e0d63d012f42057a124dd82)) +* **Switch Node:** Fix issue preventing some regex patterns from working ([#8422](https://github.com/n8n-io/n8n/issues/8422)) ([e9fea16](https://github.com/n8n-io/n8n/commit/e9fea16301cb9ee0ff7e3af45fc50d77e2cf6a23)) + + +### Features + +* **core:** Custom session timeout and refresh configuration ([#8342](https://github.com/n8n-io/n8n/issues/8342)) ([07e6705](https://github.com/n8n-io/n8n/commit/07e67052568dcb292dbf63bee8912110931726bc)) +* **core:** Email recipients on resource shared ([#8408](https://github.com/n8n-io/n8n/issues/8408)) ([a0a1830](https://github.com/n8n-io/n8n/commit/a0a1830696eaa905d37fbd56e8bc5035d12b2aa5)) +* **core:** Upgrade axios and follow-redirects to address CVE-2023-26159 ([#8366](https://github.com/n8n-io/n8n/issues/8366)) ([3912c5e](https://github.com/n8n-io/n8n/commit/3912c5e7abca98fe81d4c9c25894d30d8023ce56)) +* **core:** Upgrade bull and ioredis to address CVE-2023-52079 ([#8365](https://github.com/n8n-io/n8n/issues/8365)) ([639d347](https://github.com/n8n-io/n8n/commit/639d34769e75096d0725f1f60861dc972b344abe)) +* **editor:** Migrate `moveNodeWorkflow` mixin to `useCanvasPanning` composable ([#8322](https://github.com/n8n-io/n8n/issues/8322)) ([b6d7757](https://github.com/n8n-io/n8n/commit/b6d775768f927e69e7aa5f715c99f2fed4eaaa4c)) +* **Execute Workflow Node:** Add 'Wait For Sub-Workflow Completion' option ([#8389](https://github.com/n8n-io/n8n/issues/8389)) ([ff92fc7](https://github.com/n8n-io/n8n/commit/ff92fc7fef39076e0846f2426ffdd86c761e7896)) +* **LinkedIn Node:** Add support for Community Management API ([#7451](https://github.com/n8n-io/n8n/issues/7451)) ([7660d7e](https://github.com/n8n-io/n8n/commit/7660d7e735d248f3e731aca550c2973e85cdfebc)) +* **Microsoft Teams Node:** Overhaul ([#7477](https://github.com/n8n-io/n8n/issues/7477)) ([2c146cc](https://github.com/n8n-io/n8n/commit/2c146cca62ec605f6d722fe6c4b90c7df9cf77f7)) +* Nudge users to become template creators if eligible ([#8357](https://github.com/n8n-io/n8n/issues/8357)) ([9945701](https://github.com/n8n-io/n8n/commit/99457019f795636f56d80d3fc2c7e08055ace938)) +* **Telegram Trigger Node:** Verify Webhook requests ([#8383](https://github.com/n8n-io/n8n/issues/8383)) ([1117612](https://github.com/n8n-io/n8n/commit/11176124b5b6157c7c17fd882691d2f9d6b41487)) + + + +# [1.25.0](https://github.com/n8n-io/n8n/compare/n8n@1.24.0...n8n@1.25.0) (2024-01-17) + + +### Bug Fixes + +* Add fallback resolver for langchain modules ([#8308](https://github.com/n8n-io/n8n/issues/8308)) ([851060d](https://github.com/n8n-io/n8n/commit/851060dd3f38245da6e09c04ec0b12b24b63dca4)) +* **API:** Fix manual chat trigger execution ([#8300](https://github.com/n8n-io/n8n/issues/8300)) ([884396e](https://github.com/n8n-io/n8n/commit/884396ea0d9f4a8d7987daf2b674f080056dd1d1)) +* **AwsS3 Node:** Return confirmation of success after upload ([#8312](https://github.com/n8n-io/n8n/issues/8312)) ([c921665](https://github.com/n8n-io/n8n/commit/c921665f9abe19d9e8831062c1e7673d4d1ea694)) +* **core:** Account for immediate confirmation request during test webhook creation ([#8329](https://github.com/n8n-io/n8n/issues/8329)) ([5fbd797](https://github.com/n8n-io/n8n/commit/5fbd7971e04640be3f877b3aa22d4aee61c1d40a)) +* **core:** Ensure waiting executions account for workflow timezone ([#8340](https://github.com/n8n-io/n8n/issues/8340)) ([3734c89](https://github.com/n8n-io/n8n/commit/3734c89cf64514489831b5339d722c89b300cc54)) +* **core:** Parse any readable stream response instead of only IncomingMessage ([#8359](https://github.com/n8n-io/n8n/issues/8359)) ([eb1320f](https://github.com/n8n-io/n8n/commit/eb1320fd7a4a67cd16de10c4174c7bcf2c177b06)) +* **core:** Prevent invalid compressed responses from making executions stuck forever ([#8315](https://github.com/n8n-io/n8n/issues/8315)) ([0776814](https://github.com/n8n-io/n8n/commit/0776814ed8c520326a6447dcd7b6c53fda933054)) +* **core:** Prevent issues with missing or mismatching encryption key ([#8332](https://github.com/n8n-io/n8n/issues/8332)) ([d4c93b1](https://github.com/n8n-io/n8n/commit/d4c93b16071081002b4bd316be0921bc7867dd82)) +* **core:** Prevent NodeErrors from being wrapped multiple times ([#8301](https://github.com/n8n-io/n8n/issues/8301)) ([b267bf0](https://github.com/n8n-io/n8n/commit/b267bf07e365d8bb82a9847fb3c490437dc1010e)) +* **core:** Replace all `moment` imports with `moment-timezone` ([#8337](https://github.com/n8n-io/n8n/issues/8337)) ([52a2e25](https://github.com/n8n-io/n8n/commit/52a2e25a25e9a009a536d8a371d9404e75d756f4)) +* **core:** Report when waitTill is invalid and handle it ([#8356](https://github.com/n8n-io/n8n/issues/8356)) ([d5455d7](https://github.com/n8n-io/n8n/commit/d5455d7accb193078b05a0f52386cf9303b6a00f)) +* **editor:** Add read only mode to filter component ([#8285](https://github.com/n8n-io/n8n/issues/8285)) ([dcc76f3](https://github.com/n8n-io/n8n/commit/dcc76f348075b6e05e3f38bb9694d25ac9a5646b)) +* **editor:** Capture indexed access expressions when building completions ([#8331](https://github.com/n8n-io/n8n/issues/8331)) ([159b328](https://github.com/n8n-io/n8n/commit/159b328587f3c57c73ae77c2a0c5d5c6ecc330aa)) +* **editor:** Fix issue with synchronization table on LDAP not loading data ([#8327](https://github.com/n8n-io/n8n/issues/8327)) ([6b92d49](https://github.com/n8n-io/n8n/commit/6b92d49ea58b8e5797e4e938444b161a63137638)) +* **editor:** Properly set colors for connections and labels on nodes with pinned data ([#8209](https://github.com/n8n-io/n8n/issues/8209)) ([3b8ccb9](https://github.com/n8n-io/n8n/commit/3b8ccb9fb903036a7d6e4b33f6b5a8933576e9e6)) +* Fix node graph telemetry with default values ([#8297](https://github.com/n8n-io/n8n/issues/8297)) ([93b969a](https://github.com/n8n-io/n8n/commit/93b969a327e0770d9a0e81a95a5185b0fc12ebc6)) +* **Google Drive Node:** Fix issue preventing service account from downloading files ([#7642](https://github.com/n8n-io/n8n/issues/7642)) ([cf7131d](https://github.com/n8n-io/n8n/commit/cf7131d766dfc7aec2c973525653ffec1ced03c1)) +* **HTTP Request Node:** Delete `response.request` only when it's a valid circular references ([#8293](https://github.com/n8n-io/n8n/issues/8293)) ([05c43fa](https://github.com/n8n-io/n8n/commit/05c43faa2d7582a8ce58b9bb3338c00253ad3281)) +* **Microsoft SQL Node:** Fix "Maximum call stack size exceeded" error on too many rows ([#8334](https://github.com/n8n-io/n8n/issues/8334)) ([bb2be8d](https://github.com/n8n-io/n8n/commit/bb2be8d70580896321641a49a3044165763eb9e1)) +* **Ollama Model Node:** Use a simpler credentials test ([#8318](https://github.com/n8n-io/n8n/issues/8318)) ([63b738a](https://github.com/n8n-io/n8n/commit/63b738a542429934b3838bfc814ea2a4c51675c7)) +* **OpenAI Node:** Load correct models for operation ([#8313](https://github.com/n8n-io/n8n/issues/8313)) ([a6a5372](https://github.com/n8n-io/n8n/commit/a6a5372b5f8e48e98788c4e3750ac4b63e91a96f)) +* Properly output saml validation errors ([#8284](https://github.com/n8n-io/n8n/issues/8284)) ([8c7f399](https://github.com/n8n-io/n8n/commit/8c7f39907fa82fa37af4436511d4a2daaff13015)) +* **Salesforce Node:** Upgrade to API version 59 ([#8346](https://github.com/n8n-io/n8n/issues/8346)) ([b51cbb3](https://github.com/n8n-io/n8n/commit/b51cbb325e03fd42be6dca99819d4cc7c4c1574b)) +* **Supabase Node:** Pagination for get all rows ([#8311](https://github.com/n8n-io/n8n/issues/8311)) ([e080476](https://github.com/n8n-io/n8n/commit/e0804768e84aefe9d66ab683080f67bb15a1cb58)) +* **Venafi TLS Protect Cloud Node:** Remove parameter `Application Server Type` ([#8325](https://github.com/n8n-io/n8n/issues/8325)) ([e3cedf7](https://github.com/n8n-io/n8n/commit/e3cedf7db038a70c9d48bb7c665b1be4beb872a9)) +* **Venafi TLS Protect Cloud Trigger Node:** Handle new webhook payload format ([#8326](https://github.com/n8n-io/n8n/issues/8326)) ([057d7d0](https://github.com/n8n-io/n8n/commit/057d7d031828ea8b6e779ca535ccd50d91bfa0cc)) + + +### Features + +* **core:** Implement inter-main communication for test webhooks in multi-main setup ([#8267](https://github.com/n8n-io/n8n/issues/8267)) ([1a0e285](https://github.com/n8n-io/n8n/commit/1a0e28555385f682aa335115c4d72e671c0bdc85)) +* **editor:** Add new `/templates/search` endpoint ([#8227](https://github.com/n8n-io/n8n/issues/8227)) ([4277e92](https://github.com/n8n-io/n8n/commit/4277e92ec07671a679b0d9ab6e691ef9208585bd)) +* Implement Chat Memory Manager node ([#8127](https://github.com/n8n-io/n8n/issues/8127)) ([464be93](https://github.com/n8n-io/n8n/commit/464be9332354620b2f1890136abf95dfdb71fd2e)) + + + +# [1.24.0](https://github.com/n8n-io/n8n/compare/n8n@1.23.0...n8n@1.24.0) (2024-01-10) + + +### Bug Fixes + +* **core:** Do not add Authentication header when `authentication` type is `body` ([#8201](https://github.com/n8n-io/n8n/issues/8201)) ([ac1c642](https://github.com/n8n-io/n8n/commit/ac1c642fddfac3b0ed1144c7eccd7c88fbd5a1a5)) +* **core:** Fix test webhook deregistration ([#8247](https://github.com/n8n-io/n8n/issues/8247)) ([5032bf0](https://github.com/n8n-io/n8n/commit/5032bf0e346dccf7cade17a1518b3031118af5e1)) +* **editor:** Items count display in running workflow ([#8148](https://github.com/n8n-io/n8n/issues/8148)) ([8a3c87f](https://github.com/n8n-io/n8n/commit/8a3c87f69c20de7c713dff021e390ea4ea32b103)), closes [/github.com/n8n-io/n8n/pull/7763/files#diff-f5dae80a64b9951bb6691f1b9d439423cf84fa0cc9601b3f2c00904f3135e8deR48](https://github.com//github.com/n8n-io/n8n/pull/7763/files/issues/diff-f5dae80a64b9951bb6691f1b9d439423cf84fa0cc9601b3f2c00904f3135e8deR48) +* **editor:** Only load suggested templates for owners ([#8228](https://github.com/n8n-io/n8n/issues/8228)) ([8f22a26](https://github.com/n8n-io/n8n/commit/8f22a265d607047eff22ba957d627bbec7da7900)) +* **editor:** Tweaking button sizes in execution preview ([#8206](https://github.com/n8n-io/n8n/issues/8206)) ([9d40ae8](https://github.com/n8n-io/n8n/commit/9d40ae8b74594d4368591a62f9b39dde28efc64d)) +* **editor:** Unify canvas button positioning ([#8258](https://github.com/n8n-io/n8n/issues/8258)) ([b6c42cc](https://github.com/n8n-io/n8n/commit/b6c42cc08408d9d7cc49cc84245b4ad515fa3e6a)) +* **editor:** Vertically center workflow preview loading state ([#8231](https://github.com/n8n-io/n8n/issues/8231)) ([2d6e406](https://github.com/n8n-io/n8n/commit/2d6e406e215188dbbbeb593ac09ccad3914aaf81)) +* Fix issue with API key being required for the Qdrant Node ([#8237](https://github.com/n8n-io/n8n/issues/8237)) ([4401db3](https://github.com/n8n-io/n8n/commit/4401db3a2fad3464a5498e9a86fc6bba4f9c9f95)) +* Fix template credential setup for nodes that dont have credentials ([#8208](https://github.com/n8n-io/n8n/issues/8208)) ([cd3f5b5](https://github.com/n8n-io/n8n/commit/cd3f5b5b1f48e42cb6fa5ebcc15527c28502ceb9)) +* Fix user reinvites on FE and BE ([#8261](https://github.com/n8n-io/n8n/issues/8261)) ([0dabe5c](https://github.com/n8n-io/n8n/commit/0dabe5c74e5ad0969d4691b3db4a1e796ed8a08c)) +* **FTP Node:** FTP connection failed due to missing password credential in node ([#8131](https://github.com/n8n-io/n8n/issues/8131)) ([e056aa9](https://github.com/n8n-io/n8n/commit/e056aa9c4dd6c6a7717202029b25f4f65ddecb0d)) +* **Github Trigger Node:** Enforce SSL validation by default ([#8265](https://github.com/n8n-io/n8n/issues/8265)) ([1387541](https://github.com/n8n-io/n8n/commit/1387541e336e7311ba9c43907fa95d3196fae2eb)) +* Make params panel double width for all SQL nodes ([#8236](https://github.com/n8n-io/n8n/issues/8236)) ([048b588](https://github.com/n8n-io/n8n/commit/048b588852f5fed1c976889ba54ef564ca7f4894)) +* **Monday.com Node:** Migrate to api 2023-10 ([#8254](https://github.com/n8n-io/n8n/issues/8254)) ([ccde38a](https://github.com/n8n-io/n8n/commit/ccde38a8a8d65a21bf4d38ef7b09a5ffa3c7ad2d)) +* **MySQL Node:** Only escape table names when needed ([#8246](https://github.com/n8n-io/n8n/issues/8246)) ([3b01eb6](https://github.com/n8n-io/n8n/commit/3b01eb60c98d51d0d7572342b8d6d40763293719)) +* **Nextcloud Node:** Throw an actual error if server responded with Fatal error ([#8234](https://github.com/n8n-io/n8n/issues/8234)) ([b201ff8](https://github.com/n8n-io/n8n/commit/b201ff8f23b2bac6b00d5c16d91b4b2931f45ade)) +* **NocoDB Node:** Download attachments ([#8235](https://github.com/n8n-io/n8n/issues/8235)) ([43e8e5e](https://github.com/n8n-io/n8n/commit/43e8e5e540b9fcbca663fcf17a78a7aba2abb475)) +* **Postgres Node:** Stop marking autogenerated columns as required ([#8230](https://github.com/n8n-io/n8n/issues/8230)) ([bed04ec](https://github.com/n8n-io/n8n/commit/bed04ec122234b4329a5e415bf3627c115b3f32e)), closes [#7084](https://github.com/n8n-io/n8n/issues/7084) +* Resolve expressions in credentials following paired item ([#8250](https://github.com/n8n-io/n8n/issues/8250)) ([ccb2b07](https://github.com/n8n-io/n8n/commit/ccb2b076f8240b0712949b72ec57ae72a36ef62d)) +* **Set Node:** Field not excluded if dot notation disabled and field was set by using drag-and-drop from ui ([#8233](https://github.com/n8n-io/n8n/issues/8233)) ([cda49a4](https://github.com/n8n-io/n8n/commit/cda49a4747ef4369ce7a971872c6fb8a74f4156d)) +* Store workflow settings when saving an execution ([#8288](https://github.com/n8n-io/n8n/issues/8288)) ([8a7c629](https://github.com/n8n-io/n8n/commit/8a7c629ea183f75f9916003edf11cb8aeef445eb)) +* **Webhook Node:** Fix handling of form-data files ([#8256](https://github.com/n8n-io/n8n/issues/8256)) ([fc29030](https://github.com/n8n-io/n8n/commit/fc2903096e6e64e5b2a14593418d5651e07ca9ee)) + + +### Features + +* Add Chat Trigger node ([#7409](https://github.com/n8n-io/n8n/issues/7409)) ([af49e95](https://github.com/n8n-io/n8n/commit/af49e95cc7ccf70f233f9bd1e34fbb57f7f08ccf)) +* **core:** Cache test webhook registrations ([#8176](https://github.com/n8n-io/n8n/issues/8176)) ([22a5f52](https://github.com/n8n-io/n8n/commit/22a5f5258da0a973e1ad44c0d3d4f0fda1d23444)), closes [#8155](https://github.com/n8n-io/n8n/issues/8155) +* **core:** Validate shutdown handlers on startup ([#8260](https://github.com/n8n-io/n8n/issues/8260)) ([3b996a7](https://github.com/n8n-io/n8n/commit/3b996a7da0137a75c3047656a4bc8cc336ebfc1e)) +* **editor:** Add fullscreen view to code editor ([#8084](https://github.com/n8n-io/n8n/issues/8084)) ([071e6d6](https://github.com/n8n-io/n8n/commit/071e6d6b6e32b7196f34043710c23331ad28fac0)) +* **editor:** Update copy: `Execute` --> `Test` ([#8137](https://github.com/n8n-io/n8n/issues/8137)) ([df5d07b](https://github.com/n8n-io/n8n/commit/df5d07bcb8beba760bc17118b36ccd531bc3c755)) +* **Google Sheets Node:** Add "By Name" option to selector for Sheets ([#8241](https://github.com/n8n-io/n8n/issues/8241)) ([dce28f9](https://github.com/n8n-io/n8n/commit/dce28f9cb98db33bf22bcfee181f8e9ca64dd2bc)) +* **HTTP Request Node:** Interval Between Requests option for pagination ([#8224](https://github.com/n8n-io/n8n/issues/8224)) ([270328c](https://github.com/n8n-io/n8n/commit/270328ccf6e5502adc092f6f85d146ffb98e1208)) +* Implement MistralCloud Chat & Embeddings nodes ([#8239](https://github.com/n8n-io/n8n/issues/8239)) ([d37b908](https://github.com/n8n-io/n8n/commit/d37b9084b2c657d8b5b8bae6dbb51b428db26e1e)) +* **MongoDB Node:** Add support for TLS ([#8266](https://github.com/n8n-io/n8n/issues/8266)) ([e796e7f](https://github.com/n8n-io/n8n/commit/e796e7f06d73a74a403000c53942d56cab91781b)) +* **Switch Node:** Overhaul ([#7855](https://github.com/n8n-io/n8n/issues/7855)) ([f4092a9](https://github.com/n8n-io/n8n/commit/f4092a9e49f66845612420ba59a013796ed80d45)) + + +### Performance Improvements + +* **core:** Improve caching service ([#8213](https://github.com/n8n-io/n8n/issues/8213)) ([f53c482](https://github.com/n8n-io/n8n/commit/f53c482939db938c47523ac11a9538e35e1926a9)), closes [#7747](https://github.com/n8n-io/n8n/issues/7747) +* **core:** Optimize workflow activation errors ([#8242](https://github.com/n8n-io/n8n/issues/8242)) ([f293956](https://github.com/n8n-io/n8n/commit/f2939568cf399e67214e89bc7f859323aaeda8dd)) + + + +# [1.23.0](https://github.com/n8n-io/n8n/compare/n8n@1.22.0...n8n@1.23.0) (2024-01-03) + + +### Bug Fixes + +* **Asana Node:** Omit body from GET, HEAD, and DELETE requests ([#8057](https://github.com/n8n-io/n8n/issues/8057)) ([15ffd4f](https://github.com/n8n-io/n8n/commit/15ffd4fb9f967302e2444a873a804d2ccb64e748)) +* **core:** Better input validation for the changeRole endpoint ([#8189](https://github.com/n8n-io/n8n/issues/8189)) ([cfe9525](https://github.com/n8n-io/n8n/commit/cfe9525dd4e2dbf2496bd86ad854bb744b5dc8fe)) +* **core:** Fix issue that pinnedData is not used with Test-Webhooks ([#8123](https://github.com/n8n-io/n8n/issues/8123)) ([fa8bd8b](https://github.com/n8n-io/n8n/commit/fa8bd8b9eb202989229028cb6975cd2b50e5eef9)) +* **core:** Handle empty executions table in pruning in migrations ([#8121](https://github.com/n8n-io/n8n/issues/8121)) ([ffaa30d](https://github.com/n8n-io/n8n/commit/ffaa30ddc4ee312f44726c17a7ec91b5551092ad)) +* **core:** Remove circular dependency in WorkflowService and ActiveWorkflowRunner ([#8128](https://github.com/n8n-io/n8n/issues/8128)) ([21788d9](https://github.com/n8n-io/n8n/commit/21788d9153fb730965dabbce64c50c3b929ee728)), closes [#8122](https://github.com/n8n-io/n8n/issues/8122) +* **core:** Use pinned data only for manual mode ([#8164](https://github.com/n8n-io/n8n/issues/8164)) ([ea7e76f](https://github.com/n8n-io/n8n/commit/ea7e76fa3b3dc1f37b0415e14ea5ff90b8017b9a)) +* **Discord Node:** Remove unnecessary requirement on parameters ([#8060](https://github.com/n8n-io/n8n/issues/8060)) ([ef3a577](https://github.com/n8n-io/n8n/commit/ef3a57719eb42777502cafdd38009e6cb5b484ce)) +* **editor:** Avoid sanitizing output to search node data ([#8126](https://github.com/n8n-io/n8n/issues/8126)) ([c83d9f4](https://github.com/n8n-io/n8n/commit/c83d9f45bab986eb930e9da69eec970d3a72263d)) +* **editor:** Enable explicit undo keyboard shortcut across all code editors ([#8178](https://github.com/n8n-io/n8n/issues/8178)) ([cf7f668](https://github.com/n8n-io/n8n/commit/cf7f6688bac5dd31dc3a45df4ecce579939141e2)), closes [#5297](https://github.com/n8n-io/n8n/issues/5297) +* **editor:** Fix operation change failing in certain conditions ([#8114](https://github.com/n8n-io/n8n/issues/8114)) ([711fa2b](https://github.com/n8n-io/n8n/commit/711fa2b9251154b50d8e5e7cd9a857ccb2c0bec6)), closes [/github.com/n8n-io/n8n/blob/7806a65229878a473f5526bad0b94614e8bfa8aa/packages/workflow/src/NodeHelpers.ts#L786](https://github.com//github.com/n8n-io/n8n/blob/7806a65229878a473f5526bad0b94614e8bfa8aa/packages/workflow/src/NodeHelpers.ts/issues/L786) +* **editor:** Fix templates view layout ([#8196](https://github.com/n8n-io/n8n/issues/8196)) ([d01e42a](https://github.com/n8n-io/n8n/commit/d01e42a2aabedfd4c0f79799bbfc9b1a235d4233)) +* **editor:** Fix UI urls when hosted behind a path prefix ([#8198](https://github.com/n8n-io/n8n/issues/8198)) ([5c078f1](https://github.com/n8n-io/n8n/commit/5c078f1b3d78c7038bfdbb083fd029ef61bf2dfc)), closes [#8061](https://github.com/n8n-io/n8n/issues/8061) +* **editor:** Prevent browser zoom when scrolling inside sticky edit mode ([#8116](https://github.com/n8n-io/n8n/issues/8116)) ([e928210](https://github.com/n8n-io/n8n/commit/e928210ccdc00ad8a38e3f96ba5145c35e7b007b)) +* **editor:** Prevent canvas undo/redo when NDV is open ([#8118](https://github.com/n8n-io/n8n/issues/8118)) ([39e45d8](https://github.com/n8n-io/n8n/commit/39e45d8b929d474f1e7587329b003fe15b61636d)) +* **editor:** Prevent storing pairedItem data inside of pinData ([#8173](https://github.com/n8n-io/n8n/issues/8173)) ([405e267](https://github.com/n8n-io/n8n/commit/405e26757e2591b42a4bfeedd1fea997fbbb08c9)) +* **GitHub Node:** Fix issue that File->Get did not run once per item ([#8190](https://github.com/n8n-io/n8n/issues/8190)) ([11cda41](https://github.com/n8n-io/n8n/commit/11cda41214100a1a3e65309434ab8be3ccef1898)) +* **Invoice Ninja Node:** Fix issue with custom invoice numbers not working with v5 ([#8200](https://github.com/n8n-io/n8n/issues/8200)) ([3b6ae2d](https://github.com/n8n-io/n8n/commit/3b6ae2d0a510a57b27fc1a44cb3e710e2a783800)) +* **Microsoft Excel 365 Node:** Ensure arg is string during worksheet table search ([#8154](https://github.com/n8n-io/n8n/issues/8154)) ([8e873ca](https://github.com/n8n-io/n8n/commit/8e873ca2f3c73ddd7bbef2218d8da82032f66cec)) +* **Notion Node:** Ensure arg is string during page ID extraction ([#8153](https://github.com/n8n-io/n8n/issues/8153)) ([e94b8a6](https://github.com/n8n-io/n8n/commit/e94b8a6c30aaa2e59117d5a0cc03e1590d7ea8ca)) +* **Redis Trigger Node:** Activating a workflow with a Redis trigger fails ([#8129](https://github.com/n8n-io/n8n/issues/8129)) ([a169b74](https://github.com/n8n-io/n8n/commit/a169b7406279de43dbd3fd7d13166d987c60d01a)) +* **Schedule Trigger Node:** Use the correct `moment` import ([#8185](https://github.com/n8n-io/n8n/issues/8185)) ([17a4e2e](https://github.com/n8n-io/n8n/commit/17a4e2ea80c664e248c136b7e66eef710ccba7f2)), closes [#8184](https://github.com/n8n-io/n8n/issues/8184) +* Show public API upgrade CTA when feature is not enabled ([#8109](https://github.com/n8n-io/n8n/issues/8109)) ([e9c7fd7](https://github.com/n8n-io/n8n/commit/e9c7fd73975ced504d5a16a0dbbc313f15ccd8ab)) + + +### Features + +* **core:** Add closeFunction support to Sub-Nodes ([#7708](https://github.com/n8n-io/n8n/issues/7708)) ([bec0fae](https://github.com/n8n-io/n8n/commit/bec0faed9e51fe6ea20ab3b07b4dfa849b28516b)) +* **core:** Add user.profile.beforeUpdate hook ([#8144](https://github.com/n8n-io/n8n/issues/8144)) ([e126ed7](https://github.com/n8n-io/n8n/commit/e126ed74f3d9ed3dae72252cb8c9e8a6f7620808)) +* **core:** Improvements/overhaul for nodes working with binary data ([#7651](https://github.com/n8n-io/n8n/issues/7651)) ([5e16dd4](https://github.com/n8n-io/n8n/commit/5e16dd4ab4457acf21d3d7a3566d07944ff7f857)) +* **core:** Remove discontinued crypto-js ([#8104](https://github.com/n8n-io/n8n/issues/8104)) ([01e9a79](https://github.com/n8n-io/n8n/commit/01e9a79238bbd2c14ae77a12e54fc1c6f41e1246)) +* **core:** Unify application components shutdown ([#8097](https://github.com/n8n-io/n8n/issues/8097)) ([3a881be](https://github.com/n8n-io/n8n/commit/3a881be6c25b3e16d8c53227dc851cb420f5f1bf)) +* **editor:** Add node execution status indicator to output panel ([#8124](https://github.com/n8n-io/n8n/issues/8124)) ([ab74bad](https://github.com/n8n-io/n8n/commit/ab74bade05cb30e7fa65a491789a3df3ab7bf8b8)) +* **editor:** Add template Id to workflow metadata ([#8088](https://github.com/n8n-io/n8n/issues/8088)) ([517b050](https://github.com/n8n-io/n8n/commit/517b050d0ae1a64987ac00d5795c5e59ed176f3f)) +* **Home Assistant Node:** Use the new Home Assistant logo ([#8150](https://github.com/n8n-io/n8n/issues/8150)) ([518a99e](https://github.com/n8n-io/n8n/commit/518a99e5287dc648edafd34a4dd27c9205eb8629)) +* **Qdrant Vector Store Node:** Qdrant vector store support ([#8080](https://github.com/n8n-io/n8n/issues/8080)) ([66460f6](https://github.com/n8n-io/n8n/commit/66460f66b0b02ae6f342e52500b29fe8b724e1dc)) +* **Wordpress Node:** Add option to ignore error when using self signed certificates ([#8199](https://github.com/n8n-io/n8n/issues/8199)) ([65c8e12](https://github.com/n8n-io/n8n/commit/65c8e12b96ac8c1c53d3879d91982ca834f3cda1)) + + + +# [1.22.0](https://github.com/n8n-io/n8n/compare/n8n@1.21.0...n8n@1.22.0) (2023-12-21) + + +### Bug Fixes + +* **ActiveCampaign Node:** Fix pagination issue when loading tags ([#8017](https://github.com/n8n-io/n8n/issues/8017)) ([1943857](https://github.com/n8n-io/n8n/commit/19438572312cf9354c333aeb52ccbf1ab81fc51f)) +* **core:** Close db connection gracefully when exiting ([#8045](https://github.com/n8n-io/n8n/issues/8045)) ([e69707e](https://github.com/n8n-io/n8n/commit/e69707efd4bd947fdf6b9c66f373da63d34f41e9)) +* **core:** Consider timeout in shutdown an error ([#8050](https://github.com/n8n-io/n8n/issues/8050)) ([4cae976](https://github.com/n8n-io/n8n/commit/4cae976a3b428bd528fe71ef0b240c0fd6e23bbf)) +* **core:** Do not display error when stopping jobless execution in queue mode ([#8007](https://github.com/n8n-io/n8n/issues/8007)) ([8e6b951](https://github.com/n8n-io/n8n/commit/8e6b951a76e08b9ee9740fdd853f77553ad60cd6)) +* **core:** Fix shutdown if terminating before hooks are initialized ([#8047](https://github.com/n8n-io/n8n/issues/8047)) ([6ae2f5e](https://github.com/n8n-io/n8n/commit/6ae2f5efea65e23029475ccdc5a65ec7c8152423)) +* **core:** Handle multiple termination signals correctly ([#8046](https://github.com/n8n-io/n8n/issues/8046)) ([67bd8ad](https://github.com/n8n-io/n8n/commit/67bd8ad698bd0afe6ff7183d75da8bca4085598e)) +* **core:** Initialize queue once in queue mode ([#8025](https://github.com/n8n-io/n8n/issues/8025)) ([53c0b49](https://github.com/n8n-io/n8n/commit/53c0b49d15047461e3b65baed65c9d76dff99539)) +* **core:** Prevent axios from force setting a form-urlencoded content-type ([#8117](https://github.com/n8n-io/n8n/issues/8117)) ([bba9576](https://github.com/n8n-io/n8n/commit/bba95761e2f2b54af1fcab8a7b1d626ca10d537e)), closes [/github.com/axios/axios/blob/v1.x/lib/core/dispatchRequest.js#L45-L47](https://github.com//github.com/axios/axios/blob/v1.x/lib/core/dispatchRequest.js/issues/L45-L47) +* **core:** Remove circular references before serializing executions in public API ([#8043](https://github.com/n8n-io/n8n/issues/8043)) ([989888d](https://github.com/n8n-io/n8n/commit/989888d9bcec6f4eb3c811ce10d480737d96b102)), closes [#8030](https://github.com/n8n-io/n8n/issues/8030) +* **core:** Restore workflow ID during execution creation ([#8031](https://github.com/n8n-io/n8n/issues/8031)) ([c5e6ba8](https://github.com/n8n-io/n8n/commit/c5e6ba8cdd4a8f117ccc2e89e55497117156d8af)), closes [/github.com/n8n-io/n8n/pull/8002/files#diff-c8cbb62ca9ab2ae45e5f565cd8c63fff6475809a6241ea0b90acc575615224](https://github.com//github.com/n8n-io/n8n/pull/8002/files/issues/diff-c8cbb62ca9ab2ae45e5f565cd8c63fff6475809a6241ea0b90acc575615224) +* **core:** Use relative imports for dynamic imports in SecurityAuditService ([#8086](https://github.com/n8n-io/n8n/issues/8086)) ([785bf99](https://github.com/n8n-io/n8n/commit/785bf9974e38ea84c016e210a3108f4af567510d)), closes [#8085](https://github.com/n8n-io/n8n/issues/8085) +* **editor:** Add back credential `use` permission ([#8023](https://github.com/n8n-io/n8n/issues/8023)) ([329e5bf](https://github.com/n8n-io/n8n/commit/329e5bf9eed8556aba2bbd50bad9dbd6d3b373ad)) +* **editor:** Cleanup Executions page component ([#8053](https://github.com/n8n-io/n8n/issues/8053)) ([2689c37](https://github.com/n8n-io/n8n/commit/2689c37e87c5b3ae5029121f4d3dc878841e8844)) +* **editor:** Disable auto scroll and list size check when clicking on executions ([#7983](https://github.com/n8n-io/n8n/issues/7983)) ([fcb8b91](https://github.com/n8n-io/n8n/commit/fcb8b91f37e1fb0ef42f411c84390180e1ed7bbe)) +* **editor:** Ensure execution data overrides pinned data when copying in executions view ([#8009](https://github.com/n8n-io/n8n/issues/8009)) ([1d1cb0d](https://github.com/n8n-io/n8n/commit/1d1cb0d3c530856e0c26d8f146f60b2555625ab6)) +* **editor:** Fix copy/paste issue when switch node is in workflow ([#8103](https://github.com/n8n-io/n8n/issues/8103)) ([4b86926](https://github.com/n8n-io/n8n/commit/4b86926752fb1304a46385cb46bdf34fda0d53b6)) +* **editor:** Make keyboard shortcuts more strict; don't accept extra Ctrl/Alt/Shift keys ([#8024](https://github.com/n8n-io/n8n/issues/8024)) ([8df49e1](https://github.com/n8n-io/n8n/commit/8df49e134d886267f9f7475573d013371220dcac)) +* **editor:** Show credential share info only to appropriate users ([#8020](https://github.com/n8n-io/n8n/issues/8020)) ([b29b4d4](https://github.com/n8n-io/n8n/commit/b29b4d442bb0617aa516748ec48379eae0996cf0)) +* **editor:** Turn off executions list auto-refresh after leaving the page ([#8005](https://github.com/n8n-io/n8n/issues/8005)) ([e3c363d](https://github.com/n8n-io/n8n/commit/e3c363d72cf4ee49086d012f92a7b34be958482f)) +* **editor:** Update image sizes in template description not to be full width always ([#8037](https://github.com/n8n-io/n8n/issues/8037)) ([63a6e7e](https://github.com/n8n-io/n8n/commit/63a6e7e0340e1b00719f212ac620600a90d70ef1)) +* **HTTP Request Node:** Do not create circular references in HTTP request node output ([#8030](https://github.com/n8n-io/n8n/issues/8030)) ([5b7ea16](https://github.com/n8n-io/n8n/commit/5b7ea16d9a20880c72779b02620e99ebe9f3617a)) +* Stop binary data restoration from preventing execution from finishing ([#8082](https://github.com/n8n-io/n8n/issues/8082)) ([5ffff1b](https://github.com/n8n-io/n8n/commit/5ffff1bb22691c09c5ca8b3ada2a19d5ce155a0b)) +* Upgrade axios to address CVE-2023-45857 ([#7713](https://github.com/n8n-io/n8n/issues/7713)) ([64eb9bb](https://github.com/n8n-io/n8n/commit/64eb9bbc3624ee8f2fa90812711ad568926fdca8)) + + +### Features + +* Add config option to prefer GET request over LIST when using Hashicorp Vault ([#8049](https://github.com/n8n-io/n8n/issues/8049)) ([439a22d](https://github.com/n8n-io/n8n/commit/439a22d68f7bf32f281b1078b71607307640a09b)) +* Add option to `returnIntermediateSteps` for AI agents ([#8113](https://github.com/n8n-io/n8n/issues/8113)) ([7806a65](https://github.com/n8n-io/n8n/commit/7806a65229878a473f5526bad0b94614e8bfa8aa)) +* **core:** Add N8N_GRACEFUL_SHUTDOWN_TIMEOUT env var ([#8068](https://github.com/n8n-io/n8n/issues/8068)) ([614f488](https://github.com/n8n-io/n8n/commit/614f48838626e2af8e3f2e76ee4a144af2d40f72)) +* **editor:** Add lead enrichment suggestions to workflow list ([#8042](https://github.com/n8n-io/n8n/issues/8042)) ([36a923c](https://github.com/n8n-io/n8n/commit/36a923cf7bd4d42b8f8decbf01255c41d6dc1671)), closes [-update-workflows-list-page-to-show-fake-door-templates#comment-b6644c99](https://github.com/-update-workflows-list-page-to-show-fake-door-templates/issues/comment-b6644c99) +* **editor:** Finalize workers view ([#8052](https://github.com/n8n-io/n8n/issues/8052)) ([edfa784](https://github.com/n8n-io/n8n/commit/edfa78414d6bce901becc05e9d860f2521139688)) +* **editor:** Gracefully ignore invalid payloads in postMessage handler ([#8096](https://github.com/n8n-io/n8n/issues/8096)) ([9d22c7a](https://github.com/n8n-io/n8n/commit/9d22c7a2782a1908f81bcf80260cd91cb296e239)) +* **editor:** Upgrade frontend tooling to address a few vulnerabilities ([#8100](https://github.com/n8n-io/n8n/issues/8100)) ([19b7f1f](https://github.com/n8n-io/n8n/commit/19b7f1ffb17dcd6ac77839f97c2544f60f4ad55e)) +* **Filter Node:** Overhaul UI by adding the new filter component ([#8016](https://github.com/n8n-io/n8n/issues/8016)) ([3d53052](https://github.com/n8n-io/n8n/commit/3d530522f828dfc985ae98e4bb551aa3a2bd44c6)) +* **Respond to Webhook Node:** Overhaul with improvements like returning all items ([#8093](https://github.com/n8n-io/n8n/issues/8093)) ([32d397e](https://github.com/n8n-io/n8n/commit/32d397eff315fdc77677c0b134a7a25bcd8ca5d0)) + + +### Performance Improvements + +* **editor:** Improve canvas rendering performance ([#8022](https://github.com/n8n-io/n8n/issues/8022)) ([b780436](https://github.com/n8n-io/n8n/commit/b780436a6b445dc5951217b5a1f2c61b34961757)) + + + +# [1.21.0](https://github.com/n8n-io/n8n/compare/n8n@1.20.0...n8n@1.21.0) (2023-12-13) + + +### Bug Fixes + +* **core:** Ensure inviter and invitee are set correctly in invite link ([#7943](https://github.com/n8n-io/n8n/issues/7943)) ([386bd61](https://github.com/n8n-io/n8n/commit/386bd619676e54e960ca0af3ff47fa3b9c16c813)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **core:** Fix user comparison in same-user subworkflow caller policy ([#7913](https://github.com/n8n-io/n8n/issues/7913)) ([92bab72](https://github.com/n8n-io/n8n/commit/92bab72cffb1083b495d211d0a31920e83e66769)) +* **core:** Perform multi-main leader check against key ID ([#7964](https://github.com/n8n-io/n8n/issues/7964)) ([1a87f70](https://github.com/n8n-io/n8n/commit/1a87f70e8404218308072ee2f35c6ba2af34c23f)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **core:** Prevent workflow history saving error from happening ([#7812](https://github.com/n8n-io/n8n/issues/7812)) ([e5581ce](https://github.com/n8n-io/n8n/commit/e5581ce8023e21d3dcf140099f3a53e5ffb4584f)) +* **editor:** Add missing string for worker in log streaming ([#7971](https://github.com/n8n-io/n8n/issues/7971)) ([148bc1d](https://github.com/n8n-io/n8n/commit/148bc1d303af3aafd73e73e11c3dd9cefd40a1dd)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Allow SSH protocol in git repository URL for environments ([#7944](https://github.com/n8n-io/n8n/issues/7944)) ([bc1c72f](https://github.com/n8n-io/n8n/commit/bc1c72f992a47a9c263aec175ca820088cf340ec)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Fix bug with node names with certain characters ([#8013](https://github.com/n8n-io/n8n/issues/8013)) ([26f0d57](https://github.com/n8n-io/n8n/commit/26f0d57f5fb71a06c92968a4997cceae62f32312)) +* **editor:** Fix Webhook URL expansion icon ([#8011](https://github.com/n8n-io/n8n/issues/8011)) ([b00b905](https://github.com/n8n-io/n8n/commit/b00b9057a42f23cd9c4bb6675a3e6134610bf81b)) +* **editor:** Prevent opening NDV search if `/` is typed in a contenteditable element ([#7968](https://github.com/n8n-io/n8n/issues/7968)) ([e8a493f](https://github.com/n8n-io/n8n/commit/e8a493f71863e6a5d2685b48a61a0d11daf5edc5)) +* **editor:** Return early in ws message handler if no 'command' keyword is found ([#7946](https://github.com/n8n-io/n8n/issues/7946)) ([5b2defc](https://github.com/n8n-io/n8n/commit/5b2defc867a0627a861bf0fb98abfd99f8efe934)) +* Ensure external hooks post workflow execute run in queue mode ([#7947](https://github.com/n8n-io/n8n/issues/7947)) ([3ba7deb](https://github.com/n8n-io/n8n/commit/3ba7deb337963d40ae70f40ffb2f4eb23cac89b7)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **FileMaker Node:** Prevent erroring on zero fields loaded ([#7955](https://github.com/n8n-io/n8n/issues/7955)) ([10ad386](https://github.com/n8n-io/n8n/commit/10ad3866048ad06d0e8455ed2c52c618ae9e5032)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* Fix issue preventing secrets from loading if the path contains - or / ([#7988](https://github.com/n8n-io/n8n/issues/7988)) ([0ac9594](https://github.com/n8n-io/n8n/commit/0ac959463f25187c5be4116a2209411afd903d87)) +* **Google Sheets Node:** Prevent erroring on zero sheet search results ([#7957](https://github.com/n8n-io/n8n/issues/7957)) ([9b877a9](https://github.com/n8n-io/n8n/commit/9b877a942787c855c3a3a011c19c5d1d30b8da67)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **Google Sheets Node:** Prevent erroring when fetching mapping columns ([#7972](https://github.com/n8n-io/n8n/issues/7972)) ([29a1066](https://github.com/n8n-io/n8n/commit/29a10668d17cdeb8b0e93c912f59c5976b6fc6c6)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **Postgres Node:** Do not include id column in upsert fields selection if it's not unique ([#7975](https://github.com/n8n-io/n8n/issues/7975)) ([435392c](https://github.com/n8n-io/n8n/commit/435392cbfe150c5e85d092686b3b7e20273421cc)) +* **Postgres Trigger Node:** Increase manual trigger timeout from 30 to 60 seconds ([#8015](https://github.com/n8n-io/n8n/issues/8015)) ([09a5729](https://github.com/n8n-io/n8n/commit/09a5729305a8072f5e98a320c85ad1c83a6946ed)) +* Restrict updating/deleting of shared but not owned credentials ([#7950](https://github.com/n8n-io/n8n/issues/7950)) ([42e828d](https://github.com/n8n-io/n8n/commit/42e828d5c655e54b6a4ec83c398c684996b9cc3e)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **Webhook Node:** Binary data handling ([#7804](https://github.com/n8n-io/n8n/issues/7804)) ([565b409](https://github.com/n8n-io/n8n/commit/565b409a82ca6173efd19f26a5f5b27a359a3b87)) +* **Webhook Node:** Do not create binary data when there is no data in the request ([#8000](https://github.com/n8n-io/n8n/issues/8000)) ([70f0755](https://github.com/n8n-io/n8n/commit/70f0755278e0a2bdb61c29623f27623b65473ab4)), closes [/github.com/n8n-io/n8n/pull/7804/files#r1422641833](https://github.com//github.com/n8n-io/n8n/pull/7804/files/issues/r1422641833) + + +### Features + +* Add config option for external secret update interval ([#7995](https://github.com/n8n-io/n8n/issues/7995)) ([b6c1c04](https://github.com/n8n-io/n8n/commit/b6c1c04b541d0944c5baac1ab021539c8f020f10)) +* AI nodes usability fixes + Summarization Chain V2 ([#7949](https://github.com/n8n-io/n8n/issues/7949)) ([dcf1286](https://github.com/n8n-io/n8n/commit/dcf12867b3c49596cd214812caee3292d2e794de)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* Data transformation nodes and actions in Nodes Panel ([#7760](https://github.com/n8n-io/n8n/issues/7760)) ([675ec21](https://github.com/n8n-io/n8n/commit/675ec21d335af2b2c9598bc2bec18194506ef71a)) +* **editor:** Add AppCues tracking for onboarding event ([#7945](https://github.com/n8n-io/n8n/issues/7945)) ([04cabaf](https://github.com/n8n-io/n8n/commit/04cabafef7acbc30cba647732e2ca8ae8a02d29a)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Add option to disable NDV in workflow previews ([#7990](https://github.com/n8n-io/n8n/issues/7990)) ([393afef](https://github.com/n8n-io/n8n/commit/393afef1747f168d5fa42be2424fd02125f1bbac)), closes [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Filter component + implement in If node ([#7490](https://github.com/n8n-io/n8n/issues/7490)) ([8a53434](https://github.com/n8n-io/n8n/commit/8a5343401dd355436120a9a424ae455e80b50da6)) +* **editor:** Show template credential setup based on feature flag ([#7989](https://github.com/n8n-io/n8n/issues/7989)) ([08ee307](https://github.com/n8n-io/n8n/commit/08ee3072093fb26b14b48e2b35d8c8d018317f13)) +* **Google Ads Node:** Update to support v15 ([#7962](https://github.com/n8n-io/n8n/issues/7962)) ([7f01269](https://github.com/n8n-io/n8n/commit/7f0126915aae514a0ab515a4baf5582da2aeb1e3)) +* Introduce advanced permissions ([#7844](https://github.com/n8n-io/n8n/issues/7844)) ([dbd62a4](https://github.com/n8n-io/n8n/commit/dbd62a4992ab8aca59e3cb50d3d970454e462238)) +* **Local File Trigger Node:** Add polling option typically good to watch network files/folders ([#7942](https://github.com/n8n-io/n8n/issues/7942)) ([2fbdfec](https://github.com/n8n-io/n8n/commit/2fbdfec0c0a3f5da64764e7821e84db30b664e49)) +* **n8n Form Trigger Node:** Improvements ([#7571](https://github.com/n8n-io/n8n/issues/7571)) ([953a58f](https://github.com/n8n-io/n8n/commit/953a58f18bfdd36fa8b526ca6213631aacab49cb)) + + + +# [1.20.0](https://github.com/n8n-io/n8n/compare/n8n@1.19.0...n8n@1.20.0) (2023-12-06) + + +### Bug Fixes + +* **AWS DynamoDB Node:** Improve error message parsing ([#7793](https://github.com/n8n-io/n8n/issues/7793)) ([5ba5ed8](https://github.com/n8n-io/n8n/commit/5ba5ed8e3c8ba2f909859bde129d92576fbda46f)) +* **core:** Allow grace period for binary data deletion after manual execution ([#7889](https://github.com/n8n-io/n8n/issues/7889)) ([61d8aeb](https://github.com/n8n-io/n8n/commit/61d8aebeaf6487269b252b353fdf16dcb67f41ff)) +* **core:** Consolidate ownership and sharing data on workflows and credentials ([#7920](https://github.com/n8n-io/n8n/issues/7920)) ([38b88b9](https://github.com/n8n-io/n8n/commit/38b88b946bab67dc1a964bb3c980a627d4a32595)) +* **core:** Fix hard deletes stopping if database query throws ([#7848](https://github.com/n8n-io/n8n/issues/7848)) ([46dd4d3](https://github.com/n8n-io/n8n/commit/46dd4d3105db3a15c81903ae81c9bbb21a45397b)) +* **core:** Make sure mfa secret and recovery codes are not returned on login ([#7936](https://github.com/n8n-io/n8n/issues/7936)) ([f5502cc](https://github.com/n8n-io/n8n/commit/f5502cc628f6b348f7fe3325b96ec9dc3360beaf)), closes [/github.com/n8n-io/n8n/pull/6994/files#diff-95a87cb029a3d26e6722df2e68132453fc254fc1f4540cbdaa95cfdbda1893deL91](https://github.com//github.com/n8n-io/n8n/pull/6994/files/issues/diff-95a87cb029a3d26e6722df2e68132453fc254fc1f4540cbdaa95cfdbda1893deL91) [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Fix deletion of last execution at execution preview ([#7883](https://github.com/n8n-io/n8n/issues/7883)) ([ce2d388](https://github.com/n8n-io/n8n/commit/ce2d388f059c0bb32d27f4b29e901d1a70083610)) +* **editor:** Replace isInstanceOwner checks with scopes where applicable ([#7858](https://github.com/n8n-io/n8n/issues/7858)) ([132d691](https://github.com/n8n-io/n8n/commit/132d691cbf983f60293c7423de0077fb7c97e0af)) +* **Google Sheets Node:** Fix issue with paired items not being set correctly ([#7862](https://github.com/n8n-io/n8n/issues/7862)) ([5207a2f](https://github.com/n8n-io/n8n/commit/5207a2fe5210e40d3b2aedd95182a18e497c72ab)) +* **Notion Node:** Fix broken Notion node parameters ([#7864](https://github.com/n8n-io/n8n/issues/7864)) ([51d1f5b](https://github.com/n8n-io/n8n/commit/51d1f5b82070542d45c3d57387343959a3f0abb2)), closes [#7791](https://github.com/n8n-io/n8n/issues/7791) + + +### Features + +* **BambooHR Node:** Add support for Only Current on company reports ([#7878](https://github.com/n8n-io/n8n/issues/7878)) ([4175801](https://github.com/n8n-io/n8n/commit/4175801c90ad4f744d1a7c331d4fb20891ed2e9e)) +* **core:** Allow admin creation ([#7837](https://github.com/n8n-io/n8n/issues/7837)) ([476806e](https://github.com/n8n-io/n8n/commit/476806ebb0f31f656992fb67aba37116f10e1475)) +* **editor:** Add sections to create node panel ([#7831](https://github.com/n8n-io/n8n/issues/7831)) ([39fa8d2](https://github.com/n8n-io/n8n/commit/39fa8d21bbee5d870b2620ec65401a5ca134c4f1)) +* **editor:** Open template credential setup from collection ([#7882](https://github.com/n8n-io/n8n/issues/7882)) ([627ddb9](https://github.com/n8n-io/n8n/commit/627ddb91fb6c00796671a1f72f59a251cd89004d)) +* **editor:** Select credentials in template setup if theres only one ([#7879](https://github.com/n8n-io/n8n/issues/7879)) ([fe3417a](https://github.com/n8n-io/n8n/commit/fe3417a615534a01cb0c7b5e8f47bc18abd5cd4d)) + + +### Performance Improvements + +* **editor:** Improve node rendering performance when opening large workflows ([#7904](https://github.com/n8n-io/n8n/issues/7904)) ([a8049a0](https://github.com/n8n-io/n8n/commit/a8049a0def21506ebf4fb1d3b69ae28ec35fdc21)), closes [#7901](https://github.com/n8n-io/n8n/issues/7901) [4#a39f9e5ba64a48b58a71d81c837e8227](https://github.com/4/issues/a39f9e5ba64a48b58a71d81c837e8227) [4#f6a177d32bde4b57ae2da0b8e454](https://github.com/4/issues/f6a177d32bde4b57ae2da0b8e454) [4#fef2d36ab02247e1a0f65a74f6fb534](https://github.com/4/issues/fef2d36ab02247e1a0f65a74f6fb534) +* **editor:** Improve performance when opening large workflows with node issues ([#7901](https://github.com/n8n-io/n8n/issues/7901)) ([4bd7ae2](https://github.com/n8n-io/n8n/commit/4bd7ae29f7c82b8817420e617a123024147c6c70)) + + + +# [1.19.0](https://github.com/n8n-io/n8n/compare/n8n@1.18.0...n8n@1.19.0) (2023-11-29) + + +### Bug Fixes + +* **core:** Ensure member and admin cannot be promoted to owner ([#7830](https://github.com/n8n-io/n8n/issues/7830)) ([9b87a59](https://github.com/n8n-io/n8n/commit/9b87a596ca4aec462faedcca1ba4655b168bc3bc)), closes [/linear.app/n8n/issue/PAY-985/add-user-role-modification-endpoint#comment-62355f6](https://github.com//linear.app/n8n/issue/PAY-985/add-user-role-modification-endpoint/issues/comment-62355f6) +* **core:** Prevent error messages due to statistics about data loading ([#7824](https://github.com/n8n-io/n8n/issues/7824)) ([847f6ac](https://github.com/n8n-io/n8n/commit/847f6ac771316eea270d2e83adac5d8a6483475a)) +* **core:** Tighten checks for multi-main setup usage ([#7788](https://github.com/n8n-io/n8n/issues/7788)) ([fdb2c18](https://github.com/n8n-io/n8n/commit/fdb2c18ecc49d1e8759e809d2e2c2e4aa17009da)) +* **core:** Use AbortController to notify nodes to abort execution ([#6141](https://github.com/n8n-io/n8n/issues/6141)) ([d2c18c5](https://github.com/n8n-io/n8n/commit/d2c18c57274cac02e70cf1cc9e533a6ca98f0ec6)) +* **editor:** Add telemetry to workflow history ([#7811](https://github.com/n8n-io/n8n/issues/7811)) ([d497041](https://github.com/n8n-io/n8n/commit/d4970410e1ba328b05ddc23abcbf33c719de5624)) +* **editor:** Allow owners and admins to share workflows and credentials they don't own ([#7833](https://github.com/n8n-io/n8n/issues/7833)) ([3ab3ec9](https://github.com/n8n-io/n8n/commit/3ab3ec9da88f7b7ae07a98d7ef7c4f9892079048)) +* **editor:** Disable context menu actions in read-only mode ([#7789](https://github.com/n8n-io/n8n/issues/7789)) ([902beff](https://github.com/n8n-io/n8n/commit/902beffce51d547094ea249d1fbbb70a879165d6)) +* **editor:** Fix cloud plan data loading on instance ([#7841](https://github.com/n8n-io/n8n/issues/7841)) ([8b99384](https://github.com/n8n-io/n8n/commit/8b99384367161a47b3de13b7e83bcf6d07e3bf19)) +* **editor:** Fix credential icon for old node type version ([#7843](https://github.com/n8n-io/n8n/issues/7843)) ([4074107](https://github.com/n8n-io/n8n/commit/40741075117dd8018ca1c6d03c050c3959142ebd)) +* **editor:** Fix icon for unknown node type ([#7842](https://github.com/n8n-io/n8n/issues/7842)) ([28ac5a7](https://github.com/n8n-io/n8n/commit/28ac5a750eb28e9ab41611a76fa5fb9c30ce64dc)) +* **editor:** Fix mouse position in workflow previews ([#7853](https://github.com/n8n-io/n8n/issues/7853)) ([c063398](https://github.com/n8n-io/n8n/commit/c0633987bfd6de24db0efc4bcb73adba9e9b6a74)) +* **editor:** Show nice error when environment is not set up ([#7778](https://github.com/n8n-io/n8n/issues/7778)) ([5835e05](https://github.com/n8n-io/n8n/commit/5835e055d39921cdf2aa9799e427931bec8e5e2c)) +* **editor:** Suppress dev server websocket messages in workflow view ([#7808](https://github.com/n8n-io/n8n/issues/7808)) ([685ffd7](https://github.com/n8n-io/n8n/commit/685ffd74137199f5e10145a33f3e0f03dabc2e7f)) +* **Google Sheets Node:** Read operation execute for each item ([#7800](https://github.com/n8n-io/n8n/issues/7800)) ([d548872](https://github.com/n8n-io/n8n/commit/d5488725a83f6705b95c9de9d8736adf1b870134)) +* **HTTP Request Node:** Enable expressions for binary input data fields ([#7782](https://github.com/n8n-io/n8n/issues/7782)) ([6208af0](https://github.com/n8n-io/n8n/commit/6208af07eb393b0fb8483b8ae4949a63423cc409)) +* **Microsoft SQL Node:** Prevent double escaping table name ([#7801](https://github.com/n8n-io/n8n/issues/7801)) ([73ec753](https://github.com/n8n-io/n8n/commit/73ec7533ce4724940c2b23f677a9dfcf75de6a16)) + + +### Features + +* Add AI tool building capabilities ([#7336](https://github.com/n8n-io/n8n/issues/7336)) ([87def60](https://github.com/n8n-io/n8n/commit/87def60979bd6525b59df4bd811571a2afe83bec)) +* Add initial scope checks via decorators ([#7737](https://github.com/n8n-io/n8n/issues/7737)) ([a37f1cb](https://github.com/n8n-io/n8n/commit/a37f1cb0bad87c486c387383f8179aa42f0b9e1a)) +* Add user role select to users list settings page ([#7796](https://github.com/n8n-io/n8n/issues/7796)) ([137e238](https://github.com/n8n-io/n8n/commit/137e23853fdbd3e62037a6cb7f742811af41a03d)) +* Ado 1296 spike credential setup in templates ([#7786](https://github.com/n8n-io/n8n/issues/7786)) ([aae45b0](https://github.com/n8n-io/n8n/commit/aae45b043b9e1427f9ffc44ef88d719782fccee5)) +* **core:** Add Support for custom CORS origins for webhooks ([#7455](https://github.com/n8n-io/n8n/issues/7455)) ([99a9ea4](https://github.com/n8n-io/n8n/commit/99a9ea497a3d21739f911da5c88c076f60471bed)) +* **core:** Allow user role modification ([#7797](https://github.com/n8n-io/n8n/issues/7797)) ([7a86d36](https://github.com/n8n-io/n8n/commit/7a86d3606852fcbc537533af24eef34279b229c6)) +* **core:** Set up endpoint for all existing roles with license flag ([#7834](https://github.com/n8n-io/n8n/issues/7834)) ([2356fb0](https://github.com/n8n-io/n8n/commit/2356fb0f0c247271ffa00d1cf25460e06212f1c4)) +* **editor:** Add node name and version to NDV node settings ([#7731](https://github.com/n8n-io/n8n/issues/7731)) ([da85198](https://github.com/n8n-io/n8n/commit/da851986f6f7cd4375b06c28a149dcb375fe8b83)) +* **editor:** Add routing middleware, permission checks, RBAC store, RBAC component ([#7702](https://github.com/n8n-io/n8n/issues/7702)) ([67a8891](https://github.com/n8n-io/n8n/commit/67a88914f2f2d11c413e7f627d659333d8419af8)) +* **editor:** Replace middleware for Role checks with Scope checks ([#7847](https://github.com/n8n-io/n8n/issues/7847)) ([72852a6](https://github.com/n8n-io/n8n/commit/72852a60eb15cbf45ebcdd390770c4cd9929a457)) +* **editor:** Show avatars for users currently working on the same workflow ([#7763](https://github.com/n8n-io/n8n/issues/7763)) ([77bc8ec](https://github.com/n8n-io/n8n/commit/77bc8ecd4b1552f7253bc1348087db518ce7ce07)) +* **Notion Node:** Option to simplify output in getChildBlocks operation ([#7791](https://github.com/n8n-io/n8n/issues/7791)) ([d667bca](https://github.com/n8n-io/n8n/commit/d667bca658a2b79fa5d0afba9ef25f26a10cdfc2)) +* **Slack Node:** Add support for getting the profile of a user ([#7829](https://github.com/n8n-io/n8n/issues/7829)) ([90bb6ba](https://github.com/n8n-io/n8n/commit/90bb6ba4174a71f0d42e8dc9f009b879ca9d4616)) + + + +# [1.18.0](https://github.com/n8n-io/n8n/compare/n8n@1.17.0...n8n@1.18.0) (2023-11-22) + + +### Bug Fixes + +* **core:** Account for non-ASCII chars in filename on binary data download ([#7742](https://github.com/n8n-io/n8n/issues/7742)) ([b4ebb1a](https://github.com/n8n-io/n8n/commit/b4ebb1a28dc87c297721299a635e836dcaa273b7)) +* **core:** Correct permissions for getstatus ([#7724](https://github.com/n8n-io/n8n/issues/7724)) ([f96c1d2](https://github.com/n8n-io/n8n/commit/f96c1d204400028c55a2120d0569180379c0649f)) +* **core:** Ensure failed executions are saved in queue mode ([#7744](https://github.com/n8n-io/n8n/issues/7744)) ([b7c5c74](https://github.com/n8n-io/n8n/commit/b7c5c7406f6f978bbd84737de34114e9492ae5f6)) +* **core:** Guard against node not found on cancelling test webhook ([#7750](https://github.com/n8n-io/n8n/issues/7750)) ([6be453b](https://github.com/n8n-io/n8n/commit/6be453b716eff14df420ef565ea1b5ffb3ce73f0)) +* **editor:** Handle permission edge cases (empty scopes) ([#7723](https://github.com/n8n-io/n8n/issues/7723)) ([e2ffd39](https://github.com/n8n-io/n8n/commit/e2ffd397fc0ab8d88128ba78d02c5df003af4a9d)) +* **editor:** Make sure LineController is registered with chart.js ([#7730](https://github.com/n8n-io/n8n/issues/7730)) ([ebee1a5](https://github.com/n8n-io/n8n/commit/ebee1a590873aa56c23fd610616196ee27fe657a)) +* **editor:** Move workerview entry into settings menu ([#7761](https://github.com/n8n-io/n8n/issues/7761)) ([366cd67](https://github.com/n8n-io/n8n/commit/366cd672a74649a19fc927e0327ae7c19ed5a1fc)) +* **editor:** Only show push to git menu item to owners ([#7766](https://github.com/n8n-io/n8n/issues/7766)) ([0d3d33d](https://github.com/n8n-io/n8n/commit/0d3d33dd1f2354248ac341a0c9f2553812f404e0)) +* **editor:** Show v1 banner dismiss button if owner ([#7722](https://github.com/n8n-io/n8n/issues/7722)) ([44d3b3e](https://github.com/n8n-io/n8n/commit/44d3b3ed7ee77715006591a4f49049388fcd4035)) +* **editor:** Use project diagram icon for worker view ([#7764](https://github.com/n8n-io/n8n/issues/7764)) ([ff0b651](https://github.com/n8n-io/n8n/commit/ff0b6511f74831c499ab032910dfa9cf38356e8c)) +* **editor:** Validate user info before submiting ([#7608](https://github.com/n8n-io/n8n/issues/7608)) ([2064f7f](https://github.com/n8n-io/n8n/commit/2064f7f251913a0cc22b4e27bb38df921f711109)) +* **GitHub Node:** Fix issue preventing file edits on branches ([#7734](https://github.com/n8n-io/n8n/issues/7734)) ([ce002a6](https://github.com/n8n-io/n8n/commit/ce002a6cc672d1e13cc3d3470add78781d1ef20e)) +* **Google Sheets Node:** Check for `null` before destructuring ([#7729](https://github.com/n8n-io/n8n/issues/7729)) ([5d4a52d](https://github.com/n8n-io/n8n/commit/5d4a52d3b7e35924e1a8c9a2c808418bdf224d2c)) +* **Item Lists Node:** Don't check same type in remove duplicates operation ([#7678](https://github.com/n8n-io/n8n/issues/7678)) ([4f30764](https://github.com/n8n-io/n8n/commit/4f307646f3a5691331c7c610c62f562921a005f8)) +* **JotForm Trigger Node:** Fix iteration on form loader ([#7751](https://github.com/n8n-io/n8n/issues/7751)) ([82f3202](https://github.com/n8n-io/n8n/commit/82f3202a2de2863f01abe3cf84d6f37eba4fb6fa)) + + +### Features + +* Add Creator hub link to Templates page ([#7721](https://github.com/n8n-io/n8n/issues/7721)) ([4dbae0e](https://github.com/n8n-io/n8n/commit/4dbae0e2e95d1b5f46cfc50a5a9fc6bb761defde)) +* **core:** Coordinate manual workflow activation and deactivation in multi-main scenario ([#7643](https://github.com/n8n-io/n8n/issues/7643)) ([4c40825](https://github.com/n8n-io/n8n/commit/4c4082503c916d654758da738321f9e78a098ce5)), closes [#7566](https://github.com/n8n-io/n8n/issues/7566) +* **editor:** Add node context menu ([#7620](https://github.com/n8n-io/n8n/issues/7620)) ([8d12c1a](https://github.com/n8n-io/n8n/commit/8d12c1ad8d9283764647836bdd50224259d506e9)) +* **editor:** Node IO filter ([#7503](https://github.com/n8n-io/n8n/issues/7503)) ([1881765](https://github.com/n8n-io/n8n/commit/18817651ec5d9ed5e774379ae5cf8f57c5461e43)) + + + +# [1.17.0](https://github.com/n8n-io/n8n/compare/n8n@1.16.0...n8n@1.17.0) (2023-11-15) + + +### Bug Fixes + +* **Convert to/from binary data Node:** Better mime type defaults ([#7693](https://github.com/n8n-io/n8n/issues/7693)) ([9b3be0c](https://github.com/n8n-io/n8n/commit/9b3be0cfd8b0b58903d89ea3bf0b73be579a4f89)) +* **core:** Consider subworkflows successfully run when in waiting state ([#7699](https://github.com/n8n-io/n8n/issues/7699)) ([0e00dab](https://github.com/n8n-io/n8n/commit/0e00dab9f5d5a6622cdc22fa8bfbecc039f6b67a)) +* **core:** Fix named parameter resolution in migrations ([#7688](https://github.com/n8n-io/n8n/issues/7688)) ([4441ed5](https://github.com/n8n-io/n8n/commit/4441ed51169e8be930c548b17f54147ff6bd8e7d)), closes [#7628](https://github.com/n8n-io/n8n/issues/7628) +* **core:** Initialize JWT Secret before it's used anywhere ([#7707](https://github.com/n8n-io/n8n/issues/7707)) ([3460eb5](https://github.com/n8n-io/n8n/commit/3460eb5eeba95e51ccdac05084daf883c9750022)) +* **core:** Reduce memory usage in credentials risk auditing ([#7663](https://github.com/n8n-io/n8n/issues/7663)) ([9fd6319](https://github.com/n8n-io/n8n/commit/9fd6319583d0446e41de4fb80d4bc5a6c5e1ca07)) +* **Date & Time Node:** Add fromFormat option to solve ambiguous date strings ([#7675](https://github.com/n8n-io/n8n/issues/7675)) ([d2d11e0](https://github.com/n8n-io/n8n/commit/d2d11e0208e8a20145910bbdd02e7b273fb0aa13)) +* **editor:** Fix resource mapper component being truncated ([#7664](https://github.com/n8n-io/n8n/issues/7664)) ([00dff50](https://github.com/n8n-io/n8n/commit/00dff50140d12e37bfeecdf1300ff313c179ec89)) +* **editor:** More securely clear executions tab auto refresh timer ([#7685](https://github.com/n8n-io/n8n/issues/7685)) ([37dd658](https://github.com/n8n-io/n8n/commit/37dd658dc5bc1128c91d86105bf7f49dfcf96985)) +* **editor:** Redirect to workflow editor after saving in debug mode ([#7645](https://github.com/n8n-io/n8n/issues/7645)) ([020042e](https://github.com/n8n-io/n8n/commit/020042ef1a329e805035061fbf6743bde892e3b1)) +* **Google Sheets Node:** Append exceeding grid limits ([#7684](https://github.com/n8n-io/n8n/issues/7684)) ([88efb99](https://github.com/n8n-io/n8n/commit/88efb9958711bac446b6a698dfba50afd2b46132)) +* **HTTP Request Node:** Support generic credentials when using pagination ([#7686](https://github.com/n8n-io/n8n/issues/7686)) ([48b240b](https://github.com/n8n-io/n8n/commit/48b240b0269858adb8fde8abb8a7211b2a3e78e0)), closes [#7653](https://github.com/n8n-io/n8n/issues/7653) +* **HubSpot Node:** Fetching available parameters fails when using expressions ([#7672](https://github.com/n8n-io/n8n/issues/7672)) ([a9ab738](https://github.com/n8n-io/n8n/commit/a9ab73896e6a42b2fd5df296c9ee95ac82936b7e)) +* **HubSpot Node:** Update deal owner on Hubspot Deal ([#7673](https://github.com/n8n-io/n8n/issues/7673)) ([3c0734b](https://github.com/n8n-io/n8n/commit/3c0734bd2d92e9d2b9e99658c2e14710f57f36ef)) +* **Spreadsheet File Node:** Read file as utf-8 in v1 ([#7701](https://github.com/n8n-io/n8n/issues/7701)) ([786b4ad](https://github.com/n8n-io/n8n/commit/786b4adcce910fa52104550d90a688c4046628f9)) + + +### Features + +* **core:** Expression function $ifEmpty ([#7660](https://github.com/n8n-io/n8n/issues/7660)) ([1c7225e](https://github.com/n8n-io/n8n/commit/1c7225ebdb1d92ce45313bbab27b0839d963fc4c)) +* **Date & Time Node:** Option to include other fields in output item ([#7661](https://github.com/n8n-io/n8n/issues/7661)) ([aea3c50](https://github.com/n8n-io/n8n/commit/aea3c501313debaf1cf2b194023a534f829290ea)) +* **Discord Node:** Overhaul ([#5351](https://github.com/n8n-io/n8n/issues/5351)) ([6a53c2a](https://github.com/n8n-io/n8n/commit/6a53c2a375ca71ffad1491da5ae7e6ec461a1a56)) +* **Discourse Node:** Add new options to Get Users ([#7674](https://github.com/n8n-io/n8n/issues/7674)) ([2e8c841](https://github.com/n8n-io/n8n/commit/2e8c841277c2ba45ab2ab3e823135f2b15a7e570)) +* **editor:** Add color selector to sticky node ([#7453](https://github.com/n8n-io/n8n/issues/7453)) ([8359364](https://github.com/n8n-io/n8n/commit/8359364536809e667be86f4b4df0838c94a801d7)) +* **editor:** Add HTTP request nodes for credentials without a node ([#7157](https://github.com/n8n-io/n8n/issues/7157)) ([14035e1](https://github.com/n8n-io/n8n/commit/14035e1244fee5bc49b9afe57d63d9e887f25dd0)) +* **editor:** Add workflow filters to querystring ([#7456](https://github.com/n8n-io/n8n/issues/7456)) ([afd637b](https://github.com/n8n-io/n8n/commit/afd637b5eab2bba33fd9ec8b24104bef5e2a4cc0)) +* **editor:** Adds a EE view to show worker details and job status ([#7600](https://github.com/n8n-io/n8n/issues/7600)) ([cbc6909](https://github.com/n8n-io/n8n/commit/cbc690907fa36e2fde0218dd6f7737d00498c674)) +* **GitLab Node:** Add support for pagination on getIssues ([#7529](https://github.com/n8n-io/n8n/issues/7529)) ([0a0798e](https://github.com/n8n-io/n8n/commit/0a0798e48500b0c159aa37deae7ce5d144f4f4c7)) +* **OpenAI Node:** Add dall-e-3 support ([#7655](https://github.com/n8n-io/n8n/issues/7655)) ([a9c7188](https://github.com/n8n-io/n8n/commit/a9c7188c4d9d3a020cb26647c9030f6ffd47a35a)) +* **RabbitMQ Trigger Node:** Add exchange and routing key options ([#7547](https://github.com/n8n-io/n8n/issues/7547)) ([5aee2b7](https://github.com/n8n-io/n8n/commit/5aee2b768f7743c6508c518bab35206577035380)) +* **Telegram Node:** Add support for markdownv2 ([#7679](https://github.com/n8n-io/n8n/issues/7679)) ([819b3a7](https://github.com/n8n-io/n8n/commit/819b3a746a1cfbb785c97d0c681734211a599852)) +* **Venafi TLS Protect Cloud Node:** Add region parameter to Venafi protect cloud ([#7689](https://github.com/n8n-io/n8n/issues/7689)) ([a08fca5](https://github.com/n8n-io/n8n/commit/a08fca51d928b7bfb7c0081287a38274048892bb)) + + +### Performance Improvements + +* **core:** Lazyload security audit reporters ([#7696](https://github.com/n8n-io/n8n/issues/7696)) ([b2ca050](https://github.com/n8n-io/n8n/commit/b2ca0500311d85742ef8abf8f9f0d1436e6d9ba1)) + + + +# [1.16.0](https://github.com/n8n-io/n8n/compare/n8n@1.15.1...n8n@1.16.0) (2023-11-08) + + +### Bug Fixes + +* **core:** Comply with custom default for workflow saving settings ([#7634](https://github.com/n8n-io/n8n/issues/7634)) ([48c068f](https://github.com/n8n-io/n8n/commit/48c068f97b6c7df08fec9fd9d80a0e7eaacc95f5)) +* **core:** Decrease reset password token expire time ([#7598](https://github.com/n8n-io/n8n/issues/7598)) ([2aa7f63](https://github.com/n8n-io/n8n/commit/2aa7f6375a01625980278aee714bdc06002b0948)) +* **core:** Ensure `init` before checking leader or follower in multi-main scenario ([#7621](https://github.com/n8n-io/n8n/issues/7621)) ([a994ba5](https://github.com/n8n-io/n8n/commit/a994ba5e8d7092edeae05e7aa5fdfbb9fd854034)) +* **core:** Ensure pruning starts only after migrations have completed ([#7626](https://github.com/n8n-io/n8n/issues/7626)) ([f748de9](https://github.com/n8n-io/n8n/commit/f748de9567ed1ecebea0ee35e9c71f8ea0e2d450)) +* **core:** Fix accessor error when running partial execution ([#7618](https://github.com/n8n-io/n8n/issues/7618)) ([26361df](https://github.com/n8n-io/n8n/commit/26361dfcd31c9952c8ef109314ca88f5f03e40f4)), closes [#6229](https://github.com/n8n-io/n8n/issues/6229) +* **core:** Make password-reset urls valid only for single-use ([#7622](https://github.com/n8n-io/n8n/issues/7622)) ([6031424](https://github.com/n8n-io/n8n/commit/60314248f4b021f451eb744184fe150ddc03bc6e)) +* **Crypto Node:** Fix issue with value not appearing for Sign action ([#7619](https://github.com/n8n-io/n8n/issues/7619)) ([5df583f](https://github.com/n8n-io/n8n/commit/5df583f783731e46500600e6a23ff3b7fdfb4e52)) +* **editor:** Allow overriding theme from query params ([#7591](https://github.com/n8n-io/n8n/issues/7591)) ([2854a0c](https://github.com/n8n-io/n8n/commit/2854a0cf467258c6dacc15c2b200cf6480b6ecef)) +* **editor:** Fix issue that frontend breaks with unkown nodes ([#7596](https://github.com/n8n-io/n8n/issues/7596)) ([db56a9e](https://github.com/n8n-io/n8n/commit/db56a9ee37e8b041ea8958fc8400b9e5b6b81316)) +* **editor:** Fix local storage flags defaulting to undefined string ([#7603](https://github.com/n8n-io/n8n/issues/7603)) ([151e60f](https://github.com/n8n-io/n8n/commit/151e60f829663e79982aae6ac1cd8489f3083224)) +* **editor:** Fix workflow history prune time limit (getting hours instead of days) ([#7644](https://github.com/n8n-io/n8n/issues/7644)) ([3d5a485](https://github.com/n8n-io/n8n/commit/3d5a485bcf7fef4c6b7d96df3a77c041178951a6)) +* **editor:** Hide not supported node options ([#7597](https://github.com/n8n-io/n8n/issues/7597)) ([b532a7b](https://github.com/n8n-io/n8n/commit/b532a7bdb7d33d5ffb20665dfde58cb664d39b4a)) +* **editor:** Remove unknown credentials on pasting workflow ([#7582](https://github.com/n8n-io/n8n/issues/7582)) ([d633753](https://github.com/n8n-io/n8n/commit/d63375368713b31e15735721c7a7603fe08a6645)) +* **editor:** Reset canvas zoom before workspace reset in node view ([#7625](https://github.com/n8n-io/n8n/issues/7625)) ([78b84af](https://github.com/n8n-io/n8n/commit/78b84af8d1cfed005c7d9c715d832e8c91fd9e3f)) +* **editor:** Zoom in/out on canvas the same amount on scroll/gesture ([#7602](https://github.com/n8n-io/n8n/issues/7602)) ([c92402a](https://github.com/n8n-io/n8n/commit/c92402a3cabfdc227f3c929bc7731d42f4516776)) +* **Facebook Lead Ads Trigger Node:** Fix issue with missing scope for business management ([#7616](https://github.com/n8n-io/n8n/issues/7616)) ([32b85ba](https://github.com/n8n-io/n8n/commit/32b85ba2fec6e74d8648be7e718b52140c1bc4fc)) + + +### Features + +* **core:** Add the node version to telemetry in node_graph_string ([#7449](https://github.com/n8n-io/n8n/issues/7449)) ([59dc36a](https://github.com/n8n-io/n8n/commit/59dc36abd9141a863cb41c17a9115410b27bdb16)) +* **core:** Coordinate workflow activation in multiple main scenario in internal API ([#7566](https://github.com/n8n-io/n8n/issues/7566)) ([c857e42](https://github.com/n8n-io/n8n/commit/c857e42677ef0d415caf66f00d7af029546dfd79)) +* **core:** Initial support for two-way communication over websockets ([#7570](https://github.com/n8n-io/n8n/issues/7570)) ([ac87701](https://github.com/n8n-io/n8n/commit/ac877014eda83eb2ee61c87f29e2583f3fbfd125)) +* **core:** Log executed migrations with info level ([#7586](https://github.com/n8n-io/n8n/issues/7586)) ([7dac9ab](https://github.com/n8n-io/n8n/commit/7dac9ab82c2f91cfbb66a57f175c1865e8c8107a)) +* **core:** Rate limit forgot password endpoint ([#7604](https://github.com/n8n-io/n8n/issues/7604)) ([5790e25](https://github.com/n8n-io/n8n/commit/5790e251b8072679d7c061e2d2fa1f4229e03cf8)) +* **LinkedIn Node:** Add support for Article thumbnails ([#7489](https://github.com/n8n-io/n8n/issues/7489)) ([e6d3d1a](https://github.com/n8n-io/n8n/commit/e6d3d1a4c2dd6a860e935df4b0ce3f13e23030c7)) +* **NocoDB Node:** Add new data apis and workspace support ([#7329](https://github.com/n8n-io/n8n/issues/7329)) ([da2d2a8](https://github.com/n8n-io/n8n/commit/da2d2a83bbfb05db3a10aef99bfde3ccaf160d60)) + + + +## [1.15.1](https://github.com/n8n-io/n8n/compare/n8n@1.14.0...n8n@1.15.1) (2023-11-02) + + +### Bug Fixes + +* **core:** Ensure execution deletion in worker lifecycle hook ([#7481](https://github.com/n8n-io/n8n/issues/7481)) ([742c8a8](https://github.com/n8n-io/n8n/commit/742c8a8534098522fe103fad09fa95f70c460b3d)) +* **core:** Fix data encryption on credentials import ([#7560](https://github.com/n8n-io/n8n/issues/7560)) ([b350568](https://github.com/n8n-io/n8n/commit/b350568505d48ec880fe98d2b62ef090d5399c5f)) +* **core:** Fix issue that prevents owner logging in when using ldap ([#7408](https://github.com/n8n-io/n8n/issues/7408)) ([479f902](https://github.com/n8n-io/n8n/commit/479f90231d0a03c69b17189384812b5a1d81ef3d)) +* **core:** Handle missing resultData in runData ([#7523](https://github.com/n8n-io/n8n/issues/7523)) ([1055bd3](https://github.com/n8n-io/n8n/commit/1055bd3762b90b013a300bd87e3fa902e902cb9e)) +* **core:** Permission check for subworkflow properly checking for workflow settings ([#7576](https://github.com/n8n-io/n8n/issues/7576)) ([437c95e](https://github.com/n8n-io/n8n/commit/437c95e84e144cc77f2866a74d6b670c415895cd)) +* **core:** Prevent executions from becoming forever running ([#7569](https://github.com/n8n-io/n8n/issues/7569)) ([9bdb85c](https://github.com/n8n-io/n8n/commit/9bdb85c4ced96fde75435e334dc757d6c7679926)) +* **core:** Upgrade crypto-js to address CVE-2023-46233 ([#7519](https://github.com/n8n-io/n8n/issues/7519)) ([65e5593](https://github.com/n8n-io/n8n/commit/65e559323371e8235b92e2134d9908d69043fac4)) +* **editor:** Do not truncate form inputs ([#7528](https://github.com/n8n-io/n8n/issues/7528)) ([ae616f1](https://github.com/n8n-io/n8n/commit/ae616f146bc2ce8d37f8cf5116c6c4c8682a91a6)) +* **editor:** Fix NDV close after using input select ([#7544](https://github.com/n8n-io/n8n/issues/7544)) ([3b5e181](https://github.com/n8n-io/n8n/commit/3b5e181e66f8d7e3860e3078dae7cbb20e92551a)) +* **editor:** Fix NDV unexpected re-render ([#7532](https://github.com/n8n-io/n8n/issues/7532)) ([2853fcf](https://github.com/n8n-io/n8n/commit/2853fcff735fd0b98c19c1192349ef2c659d2493)) +* **editor:** Fix route component caching, incorrect use of array reduce method and enable WF history feature ([#7434](https://github.com/n8n-io/n8n/issues/7434)) ([12a89e6](https://github.com/n8n-io/n8n/commit/12a89e6d1441f81380d5e477274a5e2d3eb29f2d)) +* **editor:** Fixes the issue that Switch Node can not be created ([#7516](https://github.com/n8n-io/n8n/issues/7516)) ([df89685](https://github.com/n8n-io/n8n/commit/df89685e1548219f4c06614287abafbc96697817)) +* **editor:** Handle `localStorage` being blocked/unavailable ([#7348](https://github.com/n8n-io/n8n/issues/7348)) ([c05bc67](https://github.com/n8n-io/n8n/commit/c05bc6728d3227af4931ddcda5ed8bc6a3539dd0)) +* Fix dark mode small issues ([#7573](https://github.com/n8n-io/n8n/issues/7573)) ([1d81afc](https://github.com/n8n-io/n8n/commit/1d81afcbdf17166f3ebf468673e3ba348ae7fecb)) +* **Jira Software Node:** Handle missing issue types in issue types loader ([#7534](https://github.com/n8n-io/n8n/issues/7534)) ([9762705](https://github.com/n8n-io/n8n/commit/9762705833c809fd2781de179279a15c1be988eb)) +* **Switch Node:** Allow sortable Switch rules ([#7555](https://github.com/n8n-io/n8n/issues/7555)) ([7a56e58](https://github.com/n8n-io/n8n/commit/7a56e58a608132ef795d8c5cdaccb8caa49c0e8f)) + + +### Features + +* **core:** Add optional Error-Output ([#7460](https://github.com/n8n-io/n8n/issues/7460)) ([655efea](https://github.com/n8n-io/n8n/commit/655efeaf669e9722895b66fef47f000507459210)) +* **core:** Make queue mode settings configurable ([#7526](https://github.com/n8n-io/n8n/issues/7526)) ([3d95b24](https://github.com/n8n-io/n8n/commit/3d95b243e935e4eba97a418d05fa687169ab7d07)) +* **core:** Set up leader selection for multiple main instances ([#7527](https://github.com/n8n-io/n8n/issues/7527)) ([442c73e](https://github.com/n8n-io/n8n/commit/442c73e63bb54f50657a511d88912a80cab64c7f)) +* **editor:** Implement the `UserStack` design system component ([#7559](https://github.com/n8n-io/n8n/issues/7559)) ([ce14f62](https://github.com/n8n-io/n8n/commit/ce14f6266b30caadb477b08d4257b82c769a74c3)) +* **HTTP Request Node:** Add pagination support ([#5993](https://github.com/n8n-io/n8n/issues/5993)) ([cc2bd2e](https://github.com/n8n-io/n8n/commit/cc2bd2e19c8b75320b236de215d389220fbe24ae)) +* **HTTP Request Node:** Update icon and default color ([#7572](https://github.com/n8n-io/n8n/issues/7572)) ([ff279ab](https://github.com/n8n-io/n8n/commit/ff279ab4112435c341b84081d68b976ff03bf261)) +* **n8n Form Trigger Node:** Add text area and password input types ([#7474](https://github.com/n8n-io/n8n/issues/7474)) ([b72040a](https://github.com/n8n-io/n8n/commit/b72040aa5423aa6cb16dea2e7c6ea6439376b653)) +* **editor:** Dark mode is here! You can change it under personal settings.([#6980](https://github.com/n8n-io/n8n/pull/6980)) ([0746783](https://github.com/n8n-io/n8n/commit/0746783e027ebe6715588a68db399a34e0211a96)) + + + +# [1.15.0](https://github.com/n8n-io/n8n/compare/n8n@1.14.0...n8n@1.15.0) (2023-11-02) + + +### Bug Fixes + +* **core:** Ensure execution deletion in worker lifecycle hook ([#7481](https://github.com/n8n-io/n8n/issues/7481)) ([742c8a8](https://github.com/n8n-io/n8n/commit/742c8a8534098522fe103fad09fa95f70c460b3d)) +* **core:** Fix data encryption on credentials import ([#7560](https://github.com/n8n-io/n8n/issues/7560)) ([b350568](https://github.com/n8n-io/n8n/commit/b350568505d48ec880fe98d2b62ef090d5399c5f)) +* **core:** Fix issue that prevents owner logging in when using ldap ([#7408](https://github.com/n8n-io/n8n/issues/7408)) ([479f902](https://github.com/n8n-io/n8n/commit/479f90231d0a03c69b17189384812b5a1d81ef3d)) +* **core:** Handle missing resultData in runData ([#7523](https://github.com/n8n-io/n8n/issues/7523)) ([1055bd3](https://github.com/n8n-io/n8n/commit/1055bd3762b90b013a300bd87e3fa902e902cb9e)) +* **core:** Permission check for subworkflow properly checking for workflow settings ([#7576](https://github.com/n8n-io/n8n/issues/7576)) ([437c95e](https://github.com/n8n-io/n8n/commit/437c95e84e144cc77f2866a74d6b670c415895cd)) +* **core:** Prevent executions from becoming forever running ([#7569](https://github.com/n8n-io/n8n/issues/7569)) ([9bdb85c](https://github.com/n8n-io/n8n/commit/9bdb85c4ced96fde75435e334dc757d6c7679926)) +* **core:** Upgrade crypto-js to address CVE-2023-46233 ([#7519](https://github.com/n8n-io/n8n/issues/7519)) ([65e5593](https://github.com/n8n-io/n8n/commit/65e559323371e8235b92e2134d9908d69043fac4)) +* **editor:** Do not truncate form inputs ([#7528](https://github.com/n8n-io/n8n/issues/7528)) ([ae616f1](https://github.com/n8n-io/n8n/commit/ae616f146bc2ce8d37f8cf5116c6c4c8682a91a6)) +* **editor:** Fix NDV close after using input select ([#7544](https://github.com/n8n-io/n8n/issues/7544)) ([3b5e181](https://github.com/n8n-io/n8n/commit/3b5e181e66f8d7e3860e3078dae7cbb20e92551a)) +* **editor:** Fix NDV unexpected re-render ([#7532](https://github.com/n8n-io/n8n/issues/7532)) ([2853fcf](https://github.com/n8n-io/n8n/commit/2853fcff735fd0b98c19c1192349ef2c659d2493)) +* **editor:** Fix route component caching, incorrect use of array reduce method and enable WF history feature ([#7434](https://github.com/n8n-io/n8n/issues/7434)) ([12a89e6](https://github.com/n8n-io/n8n/commit/12a89e6d1441f81380d5e477274a5e2d3eb29f2d)) +* **editor:** Fixes the issue that Switch Node can not be created ([#7516](https://github.com/n8n-io/n8n/issues/7516)) ([df89685](https://github.com/n8n-io/n8n/commit/df89685e1548219f4c06614287abafbc96697817)) +* **editor:** Handle `localStorage` being blocked/unavailable ([#7348](https://github.com/n8n-io/n8n/issues/7348)) ([c05bc67](https://github.com/n8n-io/n8n/commit/c05bc6728d3227af4931ddcda5ed8bc6a3539dd0)) +* Fix dark mode small issues ([#7573](https://github.com/n8n-io/n8n/issues/7573)) ([1d81afc](https://github.com/n8n-io/n8n/commit/1d81afcbdf17166f3ebf468673e3ba348ae7fecb)) +* **Jira Software Node:** Handle missing issue types in issue types loader ([#7534](https://github.com/n8n-io/n8n/issues/7534)) ([9762705](https://github.com/n8n-io/n8n/commit/9762705833c809fd2781de179279a15c1be988eb)) +* **Switch Node:** Allow sortable Switch rules ([#7555](https://github.com/n8n-io/n8n/issues/7555)) ([7a56e58](https://github.com/n8n-io/n8n/commit/7a56e58a608132ef795d8c5cdaccb8caa49c0e8f)) + + +### Features + +* **core:** Add optional Error-Output ([#7460](https://github.com/n8n-io/n8n/issues/7460)) ([655efea](https://github.com/n8n-io/n8n/commit/655efeaf669e9722895b66fef47f000507459210)) +* **core:** Make queue mode settings configurable ([#7526](https://github.com/n8n-io/n8n/issues/7526)) ([3d95b24](https://github.com/n8n-io/n8n/commit/3d95b243e935e4eba97a418d05fa687169ab7d07)) +* **core:** Set up leader selection for multiple main instances ([#7527](https://github.com/n8n-io/n8n/issues/7527)) ([442c73e](https://github.com/n8n-io/n8n/commit/442c73e63bb54f50657a511d88912a80cab64c7f)) +* **editor:** Implement the `UserStack` design system component ([#7559](https://github.com/n8n-io/n8n/issues/7559)) ([ce14f62](https://github.com/n8n-io/n8n/commit/ce14f6266b30caadb477b08d4257b82c769a74c3)) +* **HTTP Request Node:** Add pagination support ([#5993](https://github.com/n8n-io/n8n/issues/5993)) ([cc2bd2e](https://github.com/n8n-io/n8n/commit/cc2bd2e19c8b75320b236de215d389220fbe24ae)) +* **HTTP Request Node:** Update icon and default color ([#7572](https://github.com/n8n-io/n8n/issues/7572)) ([ff279ab](https://github.com/n8n-io/n8n/commit/ff279ab4112435c341b84081d68b976ff03bf261)) +* **n8n Form Trigger Node:** Add text area and password input types ([#7474](https://github.com/n8n-io/n8n/issues/7474)) ([b72040a](https://github.com/n8n-io/n8n/commit/b72040aa5423aa6cb16dea2e7c6ea6439376b653)) +* * **editor:** Dark mode is here! You can change it under personal settings.([#6980](https://github.com/n8n-io/n8n/pull/6980)) ([0746783](https://github.com/n8n-io/n8n/commit/0746783e027ebe6715588a68db399a34e0211a96)) + + + +# [1.14.0](https://github.com/n8n-io/n8n/compare/n8n@1.13.0...n8n@1.14.0) (2023-10-25) + + +### Features + +* **Switch Node:** Add support for infinite Switch outputs ([#7499](https://github.com/n8n-io/n8n/issues/7499)) ([2febc61](https://github.com/n8n-io/n8n/commit/2febc61ec94928eb196e1b5f815fffa13f8bae07)) + + + +# [1.13.0](https://github.com/n8n-io/n8n/compare/n8n@1.12.0...n8n@1.13.0) (2023-10-25) + + +### Bug Fixes + +* **core:** Always derive `instanceId` from the encryption key (no-changlog) ([#7501](https://github.com/n8n-io/n8n/issues/7501)) ([a9fdd01](https://github.com/n8n-io/n8n/commit/a9fdd018f4f5ba1e11cc10dc3a3b7929a586f818)) +* **core:** Do not return `inviteAcceptUrl` in response if email was sent ([#7465](https://github.com/n8n-io/n8n/issues/7465)) ([55c6a1b](https://github.com/n8n-io/n8n/commit/55c6a1b0d394265fa4018a7023971589d8e61b4a)) +* **core:** Ensure nodes post-processors run in the correct order ([#7500](https://github.com/n8n-io/n8n/issues/7500)) ([6f45298](https://github.com/n8n-io/n8n/commit/6f45298d3d61b33e762f520129f4775e216707c8)), closes [#7497](https://github.com/n8n-io/n8n/issues/7497) +* **core:** Fix `frontend.settings` external hook execution ([#7496](https://github.com/n8n-io/n8n/issues/7496)) ([774fe20](https://github.com/n8n-io/n8n/commit/774fe202bfde4f2c5cc95f28a33185e261b031a5)) +* **core:** Handle gzip and deflate compressed request payloads ([#7461](https://github.com/n8n-io/n8n/issues/7461)) ([83762e0](https://github.com/n8n-io/n8n/commit/83762e051d5e34d9e43caebd6275780da05c6a46)) +* **core:** Reduce logging overhead for levels that do not output ([#7479](https://github.com/n8n-io/n8n/issues/7479)) ([76c0481](https://github.com/n8n-io/n8n/commit/76c04815f7f53bf6b4c06bbe5afa52f51f28750d)) +* **Customer.io Node:** Fix api endpoint when using EU region ([#7485](https://github.com/n8n-io/n8n/issues/7485)) ([519680c](https://github.com/n8n-io/n8n/commit/519680c2cf37f3b7341e87e71b911ac2fee8bdfa)), closes [#7484](https://github.com/n8n-io/n8n/issues/7484) +* **editor:** Allow importing the same workflow multiple times ([#7458](https://github.com/n8n-io/n8n/issues/7458)) ([3c0a166](https://github.com/n8n-io/n8n/commit/3c0a166f7f1cf225e5d1b4da91f7449f2deed5ca)), closes [#7457](https://github.com/n8n-io/n8n/issues/7457) +* **editor:** Fix canvas selection breaking after interacting with node actions ([#7466](https://github.com/n8n-io/n8n/issues/7466)) ([bc47365](https://github.com/n8n-io/n8n/commit/bc473655fbc09b1172cd6949236ca2871c9d3b8e)) +* **editor:** Fix connections disappearing after reactivating canvas and renaming a node ([#7483](https://github.com/n8n-io/n8n/issues/7483)) ([450e0cc](https://github.com/n8n-io/n8n/commit/450e0cc66abbe57697f66835a837e53b5eb883a3)) +* **Google Sheets Node:** Append or update runs forever when without column headers ([#7463](https://github.com/n8n-io/n8n/issues/7463)) ([ab6a9bb](https://github.com/n8n-io/n8n/commit/ab6a9bbac29a2caf34f4dd8211cd18116f659804)) +* **Microsoft SQL Node:** Prevent SQL injection ([#7467](https://github.com/n8n-io/n8n/issues/7467)) ([a739245](https://github.com/n8n-io/n8n/commit/a7392453323fe06371988fd5bb28d659a7a00cd8)) +* **MQTT Trigger Node:** Fix node causing a start up hang when active ([#7498](https://github.com/n8n-io/n8n/issues/7498)) ([baecb93](https://github.com/n8n-io/n8n/commit/baecb93bef30ac00f09b46ea987bb4c9a2fca394)) +* **MySQL Node:** Resolve expressions in v1 ([#7464](https://github.com/n8n-io/n8n/issues/7464)) ([5c46bb0](https://github.com/n8n-io/n8n/commit/5c46bb09c137023608119093cabdf896555b22b9)) +* **Redis Node:** Fix adding sets data types ([#7444](https://github.com/n8n-io/n8n/issues/7444)) ([4e66023](https://github.com/n8n-io/n8n/commit/4e66023cd428513b76626795c27ba0713c6c4ea9)), closes [#6339](https://github.com/n8n-io/n8n/issues/6339) +* **Spreadsheet File Node:** Fix include empty cells not working with v2 ([#7505](https://github.com/n8n-io/n8n/issues/7505)) ([05e6f2a](https://github.com/n8n-io/n8n/commit/05e6f2a6ac43fb4059e7e6cc40af6c5d75e01c8b)), closes [Ticket#763644](https://github.com/Ticket/issues/763644) + + +### Features + +* **core:** Add support for oauth based service accounts with UM SMTP ([#7311](https://github.com/n8n-io/n8n/issues/7311)) ([647372b](https://github.com/n8n-io/n8n/commit/647372be275c46e9963c96163c9e913a17f13e5f)) +* **editor:** Add PH tracking to event ([#7511](https://github.com/n8n-io/n8n/issues/7511)) ([c47d27d](https://github.com/n8n-io/n8n/commit/c47d27dd6da9420add7dad243b2701876f39a95b)) +* **Facebook Lead Ads Trigger Node:** Add Facebook Lead Ads Trigger Node ([#7113](https://github.com/n8n-io/n8n/issues/7113)) ([ac814a9](https://github.com/n8n-io/n8n/commit/ac814a9c613f6f9943be8002110ca9e2433918b2)) +* **Ghost Node:** Add support for lexical format ([#7488](https://github.com/n8n-io/n8n/issues/7488)) ([7b1973c](https://github.com/n8n-io/n8n/commit/7b1973c058e0cb7dfa436953c6f046c2b3b145eb)) +* **RSS Feed Trigger Node:** Add RSS feed trigger node ([#7386](https://github.com/n8n-io/n8n/issues/7386)) ([689360e](https://github.com/n8n-io/n8n/commit/689360ee069043415838f1488486ce8deaef9e38)) + + + +# [1.12.0](https://github.com/n8n-io/n8n/compare/n8n@1.11.0...n8n@1.12.0) (2023-10-18) + + +### Bug Fixes + +* **core:** Add check that queue is defined and remove cyclic dependency ([#7404](https://github.com/n8n-io/n8n/issues/7404)) ([45f2ef3](https://github.com/n8n-io/n8n/commit/45f2ef373ee76abb0d4b9ad805beb02d8969ebd6)) +* **core:** Do not throw when deleting workflows with executions without binary-data ([#7411](https://github.com/n8n-io/n8n/issues/7411)) ([2b6a15e](https://github.com/n8n-io/n8n/commit/2b6a15e478fd1a6041be4eac19dcf1c5e5583886)) +* **core:** Fix expression with paired item with multi-input node ([#7424](https://github.com/n8n-io/n8n/issues/7424)) ([ec14141](https://github.com/n8n-io/n8n/commit/ec141416e29540a2bf6ce99be1b13d76517eca13)) +* **core:** Fix ignoring crashed executions without event msgs ([#7368](https://github.com/n8n-io/n8n/issues/7368)) ([2f4d91b](https://github.com/n8n-io/n8n/commit/2f4d91b2cd2b87ae4aceb06a4170cd86802c9bbf)) +* **core:** Pg-promise de-initialization fix ([#7417](https://github.com/n8n-io/n8n/issues/7417)) ([7703904](https://github.com/n8n-io/n8n/commit/77039044ebd74b907a44a08ae5421517fc74b46e)) +* **core:** Prevent false stalled jobs in queue mode from displaying as errored ([#7435](https://github.com/n8n-io/n8n/issues/7435)) ([e01b9e5](https://github.com/n8n-io/n8n/commit/e01b9e5ae1bf6ccdec422c2767fdf870b2e159b0)) +* **core:** Prevent undefined issues when restoring binary data ([#7419](https://github.com/n8n-io/n8n/issues/7419)) ([46977a2](https://github.com/n8n-io/n8n/commit/46977a2aff342bc7568f5a378b78689a5c3d8e95)) +* **editor:** Fix remote options fetching on every keystroke ([#7320](https://github.com/n8n-io/n8n/issues/7320)) ([367255a](https://github.com/n8n-io/n8n/commit/367255ab2c13b4a33a746d3d4b9f3164309a11c8)) +* **editor:** Open only one tab with plans page ([#7377](https://github.com/n8n-io/n8n/issues/7377)) ([c599006](https://github.com/n8n-io/n8n/commit/c599006b917e681fca918b789a80d79883bd69a9)) +* **Google Sheets Node:** Update by row_number, restored 'Handling Extra Data Option', updated Cell Format default ([#7357](https://github.com/n8n-io/n8n/issues/7357)) ([d8531a5](https://github.com/n8n-io/n8n/commit/d8531a53b9a256095157129b64adde7749aa84c9)) +* **Ldap Node:** Fix issue with connections not closing correctly ([#7432](https://github.com/n8n-io/n8n/issues/7432)) ([c3f0be8](https://github.com/n8n-io/n8n/commit/c3f0be809f3ea09573fec26648ef030c7d8ddc16)) +* **Set Node:** Null should not throw an error ([#7416](https://github.com/n8n-io/n8n/issues/7416)) ([e9b6ab0](https://github.com/n8n-io/n8n/commit/e9b6ab04cd4b292c291b77e66aa717605e72f852)) +* **TheHive 5 Node:** Observable encoding in alert > create fix ([#7450](https://github.com/n8n-io/n8n/issues/7450)) ([a2d2e3d](https://github.com/n8n-io/n8n/commit/a2d2e3dda798d166cfc2a49b0c4f0eb164a666dc)) + + +### Features + +* **core:** Make executions pruning interval configurable ([#7439](https://github.com/n8n-io/n8n/issues/7439)) ([40707fa](https://github.com/n8n-io/n8n/commit/40707fa6926776aa806221df8edee05ee7e9f3ed)) +* **Google Calendar Trigger Node:** Add support for cancelled events ([#7436](https://github.com/n8n-io/n8n/issues/7436)) ([9d241a0](https://github.com/n8n-io/n8n/commit/9d241a0d6dbfd990920708c718ff3de83c9b5883)) +* **HubSpot Trigger Node:** Add support for ticket related events ([#7156](https://github.com/n8n-io/n8n/issues/7156)) ([57c6093](https://github.com/n8n-io/n8n/commit/57c609384af7c583c213c639add7170e0bbb24fd)) +* **n8n Form Trigger Node:** New node ([#7130](https://github.com/n8n-io/n8n/issues/7130)) ([3ddc176](https://github.com/n8n-io/n8n/commit/3ddc176dfa2d3d99a328a29a3a8613e35ff456a0)) +* **Spreadsheet File Node:** Improve CSV parsing ([#7448](https://github.com/n8n-io/n8n/issues/7448)) ([79f23fb](https://github.com/n8n-io/n8n/commit/79f23fb93979aa1ef3a9bbf7049b93d1e6a0e95a)) + + + +# [1.11.0](https://github.com/n8n-io/n8n/compare/n8n@1.10.0...n8n@1.11.0) (2023-10-11) + + +### Bug Fixes + +* Add role check for upgrade path ([#7374](https://github.com/n8n-io/n8n/issues/7374)) ([a43f720](https://github.com/n8n-io/n8n/commit/a43f720658aedf8a52f713c28b29ed08bfad33c8)) +* **core:** Add an option to enable postgres ssl with default certs ([#6889](https://github.com/n8n-io/n8n/issues/6889)) ([789e1e7](https://github.com/n8n-io/n8n/commit/789e1e7ed41838bdadb5e8cb429aa76610fa5749)) +* **core:** Fix error on missing paired item data ([#7399](https://github.com/n8n-io/n8n/issues/7399)) ([47e8953](https://github.com/n8n-io/n8n/commit/47e8953ec9cccaddc603aff4c6e6fce88258e6d0)) +* **core:** Missing pairing info ([#7326](https://github.com/n8n-io/n8n/issues/7326)) ([e2c3c7a](https://github.com/n8n-io/n8n/commit/e2c3c7acebdae2014b32620277fce70d0db85dd0)) +* **core:** Prevent object deletion request on no prefix match ([#7366](https://github.com/n8n-io/n8n/issues/7366)) ([63e11e4](https://github.com/n8n-io/n8n/commit/63e11e4be93a50fe753d84771dbc72ead38fdc0e)) +* **editor:** Fix completions for `.json` on quoted node name in Code node ([#7382](https://github.com/n8n-io/n8n/issues/7382)) ([86e7ec7](https://github.com/n8n-io/n8n/commit/86e7ec796acd9d8fbe8e42a5beba687cd6133a2f)), closes [/linear.app/n8n/issue/PAY-635/autocomplete-only-supports-3-levels-of-children#comment-234f738](https://github.com//linear.app/n8n/issue/PAY-635/autocomplete-only-supports-3-levels-of-children/issues/comment-234f738) +* **editor:** Implement canvas zoom UX improvements ([#7376](https://github.com/n8n-io/n8n/issues/7376)) ([7e06b31](https://github.com/n8n-io/n8n/commit/7e06b31a5f3de8f073e7ef42be24e04899091486)) +* **editor:** Make workflow history button available only for dev builds ([#7392](https://github.com/n8n-io/n8n/issues/7392)) ([7ed466d](https://github.com/n8n-io/n8n/commit/7ed466db7faa9af88e54276bc6cb9a6dc9e80493)) +* **editor:** Remove excess margin below run data editor ([#7372](https://github.com/n8n-io/n8n/issues/7372)) ([3fa2764](https://github.com/n8n-io/n8n/commit/3fa27647d8e8a39812d9802d4c0954b3135db847)) +* **editor:** Sanitize HTML binary-data before rendering in the UI ([#7400](https://github.com/n8n-io/n8n/issues/7400)) ([2b075bf](https://github.com/n8n-io/n8n/commit/2b075bfc2da02afc43c1ebfee565b849c87aee49)) +* **editor:** Use display option's @Version specifier ([#7351](https://github.com/n8n-io/n8n/issues/7351)) ([afbf0c3](https://github.com/n8n-io/n8n/commit/afbf0c3d5e50976ca5d9f4c08051ea7bd3d2b354)) +* **Google BigQuery Node:** Location default to jobReference ([#7354](https://github.com/n8n-io/n8n/issues/7354)) ([97bb703](https://github.com/n8n-io/n8n/commit/97bb703d0a13cf979a2c462c87570707d5032867)) +* **Google Drive Trigger Node:** Add Shared Drives support ([#7369](https://github.com/n8n-io/n8n/issues/7369)) ([3e7a4d3](https://github.com/n8n-io/n8n/commit/3e7a4d3b2cc12fcb1b011fccd0773bb807986884)) +* **Google Sheets Node:** Fix "Maximum call stack size exceeded" error on too many rows ([#7384](https://github.com/n8n-io/n8n/issues/7384)) ([732b15a](https://github.com/n8n-io/n8n/commit/732b15a1faeab5b802dfddb282418601f34ade27)) +* **HTML Node:** Update property fields to not use expressions on drag ([#7379](https://github.com/n8n-io/n8n/issues/7379)) ([77643e5](https://github.com/n8n-io/n8n/commit/77643e5ccbc2f68cd240c7aa7e21c0efbcc08b63)) +* **Notion Node:** Handle empty values correctly for Notion selects + multi selects ([#7383](https://github.com/n8n-io/n8n/issues/7383)) ([fbcd1d4](https://github.com/n8n-io/n8n/commit/fbcd1d40edfa8737a8786a8f961723c42f84a851)) +* **Set Node:** Increase search priority ([#7358](https://github.com/n8n-io/n8n/issues/7358)) ([e5ad1e7](https://github.com/n8n-io/n8n/commit/e5ad1e7e4df33a06c9649fe0652edfc80f7cb818)) +* **Webhook Node:** Backward compatible form-data parsing for non-array files ([#7385](https://github.com/n8n-io/n8n/issues/7385)) ([6479eb1](https://github.com/n8n-io/n8n/commit/6479eb180ff9a43791b5211157f5c450e1463ffd)) + + +### Features + +* **core:** Add Job Summary to Worker response ([#7360](https://github.com/n8n-io/n8n/issues/7360)) ([b8608ce](https://github.com/n8n-io/n8n/commit/b8608cee6d5ab96ec38a8a7f5428d02ce01366ee)) +* **core:** Integrate object store as binary data manager ([#7253](https://github.com/n8n-io/n8n/issues/7253)) ([1a661e6](https://github.com/n8n-io/n8n/commit/1a661e6d00e907b9bc4bdc248db6dbc25972294b)), closes [#7225](https://github.com/n8n-io/n8n/issues/7225) +* **core:** Switch binary filesystem mode to nested path structure ([#7307](https://github.com/n8n-io/n8n/issues/7307)) ([0847623](https://github.com/n8n-io/n8n/commit/0847623f85192232d129778ab4295be3cd685877)), closes [#7253](https://github.com/n8n-io/n8n/issues/7253) +* **editor:** Make PDF and Audio binary-data viewable in the UI ([#7367](https://github.com/n8n-io/n8n/issues/7367)) ([8187be1](https://github.com/n8n-io/n8n/commit/8187be1b7dd723fec9591e2fc4f57a782f3ca398)), closes [#7361](https://github.com/n8n-io/n8n/issues/7361) +* **editor:** Support autologin for upgrade path ([#7316](https://github.com/n8n-io/n8n/issues/7316)) ([1dfa052](https://github.com/n8n-io/n8n/commit/1dfa052301c4580838bc17830676c8fb383a00b4)) +* **Execute Workflow Node:** Run once for each item mode ([#7289](https://github.com/n8n-io/n8n/issues/7289)) ([c8c14ca](https://github.com/n8n-io/n8n/commit/c8c14ca0af1faaf680c97868210b7372ac8ac61e)) +* **Item Lists Node:** Split merge binary data ([#7297](https://github.com/n8n-io/n8n/issues/7297)) ([965db8f](https://github.com/n8n-io/n8n/commit/965db8f7f272056d10515a94a3b0b69b75913a9a)) +* **Loop Over Items (Split in Batches) Node:** Automatically add a loop + rename ([#7228](https://github.com/n8n-io/n8n/issues/7228)) ([7b773cc](https://github.com/n8n-io/n8n/commit/7b773cc5cc77853f73db186f7f7a7fbc6379db31)) +* **Notion Node:** Fetch child blocks recursively ([#7304](https://github.com/n8n-io/n8n/issues/7304)) ([193181a](https://github.com/n8n-io/n8n/commit/193181a9c62d21812e5452b06606c63f26f6b9d0)) + + + +# [1.10.0](https://github.com/n8n-io/n8n/compare/n8n@1.9.0...n8n@1.10.0) (2023-10-05) + + +### Bug Fixes + +* **Convert to/from binary data Node:** Rename 'Move Binary Data' to 'Convert to/from binary data' ([#7318](https://github.com/n8n-io/n8n/issues/7318)) ([5e6c1d4](https://github.com/n8n-io/n8n/commit/5e6c1d4f4bc049d366dbc9b66d5481f6ed43190e)) +* **core:** Account for itemless case on restoring binary data ID ([#7305](https://github.com/n8n-io/n8n/issues/7305)) ([1691223](https://github.com/n8n-io/n8n/commit/169122378968b4d4c73d921ee2d5d86b76e1cd01)) +* **core:** Fix pruning of non-finished executions ([#7333](https://github.com/n8n-io/n8n/issues/7333)) ([1b4848a](https://github.com/n8n-io/n8n/commit/1b4848afcb817621e0977186c7400945758bee96)) +* **editor:** Disable email confirmation banner for trialing users ([#7340](https://github.com/n8n-io/n8n/issues/7340)) ([6d3d178](https://github.com/n8n-io/n8n/commit/6d3d1789dbd800a6ef75aab3a69e5b5ef5dba933)) +* **editor:** Display value of selected matching column in RMC ([#7298](https://github.com/n8n-io/n8n/issues/7298)) ([3aac22b](https://github.com/n8n-io/n8n/commit/3aac22b4c1d472b8470346bfc4df99b7794c3692)) +* **editor:** Fix canvas endpoint snapping when dragging connection ([#7346](https://github.com/n8n-io/n8n/issues/7346)) ([b59b908](https://github.com/n8n-io/n8n/commit/b59b9086d769b859156f4f8f99411afe5e4b9108)) +* **editor:** Fix disappearing NDV header in code nodes ([#7290](https://github.com/n8n-io/n8n/issues/7290)) ([7ebf8f3](https://github.com/n8n-io/n8n/commit/7ebf8f327ad433acc949034cd070f2362dfacd4e)) +* **editor:** Fix RLC not loading when an expression can't resolve ([#7295](https://github.com/n8n-io/n8n/issues/7295)) ([ddc26c2](https://github.com/n8n-io/n8n/commit/ddc26c21bd8da6c95cbe447bf7e479ddc449e6a4)) +* **editor:** Separate cloud endpoint calls ([#7312](https://github.com/n8n-io/n8n/issues/7312)) ([04dfcd7](https://github.com/n8n-io/n8n/commit/04dfcd73bee2c1ea0d47fd7102383719827d53d0)) +* **Jira Software Node:** Get all users in dropdown/RLC ([#7322](https://github.com/n8n-io/n8n/issues/7322)) ([3704760](https://github.com/n8n-io/n8n/commit/370476072471da6b3b849d8f5acc5cf1380a5ba8)), closes [#2670](https://github.com/n8n-io/n8n/issues/2670) +* **Notion Node:** Rename Notion API Key to Internal Integration Token ([#7176](https://github.com/n8n-io/n8n/issues/7176)) ([ec2aa38](https://github.com/n8n-io/n8n/commit/ec2aa3819c1e9624c4692d5f6942f096aae900e7)) +* **Postgres Node:** Node requires comma-separated string even when using a single parameter through an expression ([#7300](https://github.com/n8n-io/n8n/issues/7300)) ([763d451](https://github.com/n8n-io/n8n/commit/763d4514fafe489c169cd0513bf3b2c4cf7f7f12)) +* **Set Node:** Do not stringify null and undefined ([#7313](https://github.com/n8n-io/n8n/issues/7313)) ([f0a6687](https://github.com/n8n-io/n8n/commit/f0a66873b94b032792f30bc279118cdb13210505)) +* **Typeform Trigger Node:** Change output format for TypeForm trigger to object instead of array ([#7315](https://github.com/n8n-io/n8n/issues/7315)) ([b3fc00e](https://github.com/n8n-io/n8n/commit/b3fc00e045dc16f758b684b71591ec2d1f889d73)) + + +### Features + +* **core:** Add "Sent by n8n" attribution ([#7183](https://github.com/n8n-io/n8n/issues/7183)) ([8f9fe62](https://github.com/n8n-io/n8n/commit/8f9fe6269b5608346b0ac3612aa2e3af4a7baae1)) +* **core:** Add support for building LLM applications ([#7235](https://github.com/n8n-io/n8n/issues/7235)) ([00a4b8b](https://github.com/n8n-io/n8n/commit/00a4b8b0c62883f2a36559a939f9c32c5d1c048e)), closes [#7246](https://github.com/n8n-io/n8n/issues/7246) [#7137](https://github.com/n8n-io/n8n/issues/7137) +* Workflow History pruning and prune time settings ([#7343](https://github.com/n8n-io/n8n/issues/7343)) ([0adc533](https://github.com/n8n-io/n8n/commit/0adc53371969ac3f759d06d9fbb095267fffe95e)) + + + +# [1.9.0](https://github.com/n8n-io/n8n/compare/n8n@1.8.0...n8n@1.9.0) (2023-09-28) + + +### Bug Fixes + +* **Airtable Node:** Attachments field type fix ([#7227](https://github.com/n8n-io/n8n/issues/7227)) ([2af967c](https://github.com/n8n-io/n8n/commit/2af967cf88a4d6e795ff1641f9946f9fcbf49a03)) +* **core:** Change WorkflowHistory nodes/connections columns to be json ([#7282](https://github.com/n8n-io/n8n/issues/7282)) ([a80abad](https://github.com/n8n-io/n8n/commit/a80abad3af8fed3a40b6f713cd94a16f44d84f60)) +* **core:** Fix binary data manager check on pruning ([#7251](https://github.com/n8n-io/n8n/issues/7251)) ([484035e](https://github.com/n8n-io/n8n/commit/484035eb519ab28de31488484caaa4bf9a77d095)) +* **core:** Fix missing execution ID in webhook-based workflow producing binary data ([#7244](https://github.com/n8n-io/n8n/issues/7244)) ([33991e9](https://github.com/n8n-io/n8n/commit/33991e92d0aabd13a44ba103de43a6ec2b90ca46)) +* **core:** Handle filename* with quotes in Content-Disposition header ([#7229](https://github.com/n8n-io/n8n/issues/7229)) ([67b985f](https://github.com/n8n-io/n8n/commit/67b985fe89cc45e40f66b38bd2e0c310a4b3504c)) +* **core:** Make DNS resolution order configurable ([#7272](https://github.com/n8n-io/n8n/issues/7272)) ([5b3121c](https://github.com/n8n-io/n8n/commit/5b3121c415ec96535eae4ed2adc28461311b6c6e)) +* **core:** Make senderId required for all command messages ([#7252](https://github.com/n8n-io/n8n/issues/7252)) ([4b01428](https://github.com/n8n-io/n8n/commit/4b014286cf66e5f9b660fc145274be7caccbf34c)) +* **core:** Prevent executions from displaying Running status incorrectly ([#7261](https://github.com/n8n-io/n8n/issues/7261)) ([861cac5](https://github.com/n8n-io/n8n/commit/861cac5257f5e0d3cc2a010f09481ab93b8241e6)), closes [/linear.app/n8n/issue/HELP-338/large-number-of-long-running-executions-for-nadjalemlist#comment-18d1fc96](https://github.com//linear.app/n8n/issue/HELP-338/large-number-of-long-running-executions-for-nadjalemlist/issues/comment-18d1fc96) +* **core:** Use consistent timezone-aware timestamps in postgres ([#6948](https://github.com/n8n-io/n8n/issues/6948)) ([0132514](https://github.com/n8n-io/n8n/commit/0132514f8b7005a895880bab9f547484e2e90710)), closes [#2178](https://github.com/n8n-io/n8n/issues/2178) [#2810](https://github.com/n8n-io/n8n/issues/2810) [#3855](https://github.com/n8n-io/n8n/issues/3855) [#2813](https://github.com/n8n-io/n8n/issues/2813) +* **editor:** Add debug feature docs link ([#7240](https://github.com/n8n-io/n8n/issues/7240)) ([4614e1e](https://github.com/n8n-io/n8n/commit/4614e1e1c90f1c7630ccc58e8400380afedba585)) +* **editor:** Fix SQL editor issue ([#7236](https://github.com/n8n-io/n8n/issues/7236)) ([647fc6c](https://github.com/n8n-io/n8n/commit/647fc6c555702bb3222871fb50f8916c6d2ae3f3)) +* Ensure new Set node is on top of search list ([#7215](https://github.com/n8n-io/n8n/issues/7215)) ([2491ccf](https://github.com/n8n-io/n8n/commit/2491ccf4d9f36ecc93e559918cf68c690ae149db)) +* **HTTP Request Node:** Add suggestion how to fix '429 - too many requests' errors ([#7293](https://github.com/n8n-io/n8n/issues/7293)) ([0bc33b1](https://github.com/n8n-io/n8n/commit/0bc33b1cc2c2d3ae8db0342545b3a4a2b4931af9)) +* Issue enforcing user limits on start plan ([#7188](https://github.com/n8n-io/n8n/issues/7188)) ([303bc8e](https://github.com/n8n-io/n8n/commit/303bc8e71e60c3ee8ccd6b823814945d892e3726)) +* **Item Lists Node:** Concatenate operation pairedItems fix ([#7286](https://github.com/n8n-io/n8n/issues/7286)) ([cde23a1](https://github.com/n8n-io/n8n/commit/cde23a1bb1934bebe8e19308745910d625e3ca73)) +* **Respond to Webhook Node:** JSON output from expression fix ([#7294](https://github.com/n8n-io/n8n/issues/7294)) ([8bc369d](https://github.com/n8n-io/n8n/commit/8bc369dd40836472502dc7b36c503a04db3f480c)) + + +### Features + +* Add onboarding flow ([#7212](https://github.com/n8n-io/n8n/issues/7212)) ([01e9340](https://github.com/n8n-io/n8n/commit/01e93406219f6c1712247d9855590ea06df3e965)) +* Add user cloud it to telemetry ([#7232](https://github.com/n8n-io/n8n/issues/7232)) ([60c152d](https://github.com/n8n-io/n8n/commit/60c152dc72c27862f50304b8a824e2ec5e201787)) +* **core:** Add secrets provider reload and refactor ([#7277](https://github.com/n8n-io/n8n/issues/7277)) ([53a7502](https://github.com/n8n-io/n8n/commit/53a7502d20eb95055e842e0450e9daea308443a1)) +* **core:** Add Tournament as the new default expression evaluator ([#6964](https://github.com/n8n-io/n8n/issues/6964)) ([bf74f09](https://github.com/n8n-io/n8n/commit/bf74f09d69014da3c3fb2a56288b010670a4b982)) +* **core:** Initial workflow history API ([#7234](https://github.com/n8n-io/n8n/issues/7234)) ([0083a9e](https://github.com/n8n-io/n8n/commit/0083a9e45d21928be259664532528706d0a57ecf)) +* **core:** Introduce object store service ([#7225](https://github.com/n8n-io/n8n/issues/7225)) ([fa84545](https://github.com/n8n-io/n8n/commit/fa845453bb3d2ef72ef555bc8b8fa3f8bb703e1f)) +* **editor:** Rework banners framework and add email confirmation banner ([#7205](https://github.com/n8n-io/n8n/issues/7205)) ([b0e98b5](https://github.com/n8n-io/n8n/commit/b0e98b59a6500b11f306403c563191749478c3fb)), closes [4#6afd052ec8d146a1b0fab8884a19add7](https://github.com/4/issues/6afd052ec8d146a1b0fab8884a19add7) [/github.com/n8n-io/n8n/blob/f9f122d46d26565a4cc5dcf63060e7ed9f359e53/packages/editor-ui/src/components/banners/BannerStack.vue#L14](https://github.com//github.com/n8n-io/n8n/blob/f9f122d46d26565a4cc5dcf63060e7ed9f359e53/packages/editor-ui/src/components/banners/BannerStack.vue/issues/L14) [/github.com/n8n-io/n8n/blob/b80d2e3bec59a9abe141a4c808ea2b7f5d9fecce/packages/editor-ui/src/stores/cloudPlan.store.ts#L59](https://github.com//github.com/n8n-io/n8n/blob/b80d2e3bec59a9abe141a4c808ea2b7f5d9fecce/packages/editor-ui/src/stores/cloudPlan.store.ts/issues/L59) +* **MISP Node:** Update credential to support HTTP Request node ([#7268](https://github.com/n8n-io/n8n/issues/7268)) ([e4c302c](https://github.com/n8n-io/n8n/commit/e4c302c6833c2a9cc6a5fc46d21272266222dd63)) + + +### Performance Improvements + +* **core:** Skip unneeded calls on every pruning cycle ([#7260](https://github.com/n8n-io/n8n/issues/7260)) ([db01164](https://github.com/n8n-io/n8n/commit/db01164ce11c2c1574899a60541d13712bb39324)) + + + +# [1.8.0](https://github.com/n8n-io/n8n/compare/n8n@1.7.0...n8n@1.8.0) (2023-09-20) + + +### Bug Fixes + +* **core:** Make parsing of content-type and content-disposition headers more flexible ([#7217](https://github.com/n8n-io/n8n/issues/7217)) ([d41546b](https://github.com/n8n-io/n8n/commit/d41546b899e75c0decbf2fe2f0841b33c9b39bc9)), closes [#7149](https://github.com/n8n-io/n8n/issues/7149) +* **core:** Resolve domains to IPv4 first ([#7206](https://github.com/n8n-io/n8n/issues/7206)) ([e9ce531](https://github.com/n8n-io/n8n/commit/e9ce5312106e550c15eb4adc049e4b0151a7c40c)) +* **editor:** Add ssh key type selection to source control settings when regenerating key ([#7172](https://github.com/n8n-io/n8n/issues/7172)) ([54bf66d](https://github.com/n8n-io/n8n/commit/54bf66d335060e866b4f120269b156c4690a8246)) +* **editor:** No need to add click emitting click events, VUE delegates the handler to the root element of the component ([#7182](https://github.com/n8n-io/n8n/issues/7182)) ([3c055e4](https://github.com/n8n-io/n8n/commit/3c055e4d8d77addea014d8da363518c4c14c9a98)) +* **editor:** Prevent duplicate creation of credential for OAuth2 ([#7163](https://github.com/n8n-io/n8n/issues/7163)) ([07a6417](https://github.com/n8n-io/n8n/commit/07a6417f0f52988e3dfac4583aab84426fc471cc)) +* **editor:** Testing flaky resource mapper feature in e2e tests ([#7165](https://github.com/n8n-io/n8n/issues/7165)) ([aaf87c3](https://github.com/n8n-io/n8n/commit/aaf87c3edd434ab464f3ec4a4001c07895370cb0)) +* **HTML Node:** Add pairedItem support for 'Convert to HTML Table' operation ([#7196](https://github.com/n8n-io/n8n/issues/7196)) ([6bc477b](https://github.com/n8n-io/n8n/commit/6bc477b50ebcb31eae6068b2218fea56349a64a9)) +* **HTTP Request Node:** Decrease default timeout to 5min ([#7177](https://github.com/n8n-io/n8n/issues/7177)) ([321780d](https://github.com/n8n-io/n8n/commit/321780d4a2a1f7e5e7ab0ea23e0b2fc28d539330)) +* **seven Node:** Rename sms77 to seven, fix credentials test ([#7180](https://github.com/n8n-io/n8n/issues/7180)) ([cf776b8](https://github.com/n8n-io/n8n/commit/cf776b8f1756c3e20ed39fe882ebaba05f12eb6a)) +* **X (Formerly Twitter) Node:** Rename Twitter to X (keep Twitter alias) ([#7179](https://github.com/n8n-io/n8n/issues/7179)) ([d317e09](https://github.com/n8n-io/n8n/commit/d317e09c597a37fbb099ed1f032d27225e655eb9)) + + +### Features + +* **core:** Add command to trigger license refresh on workers ([#7184](https://github.com/n8n-io/n8n/issues/7184)) ([9f797b9](https://github.com/n8n-io/n8n/commit/9f797b96d818a5ae74ad82917347c99f3c249688)) +* **core:** Add rsa option to ssh key generation ([#7154](https://github.com/n8n-io/n8n/issues/7154)) ([fdac2c8](https://github.com/n8n-io/n8n/commit/fdac2c85729e19be0fd18f6807a7f5f99dfca002)) +* **Linear Node:** Add support for OAuth2 ([#7201](https://github.com/n8n-io/n8n/issues/7201)) ([12a3168](https://github.com/n8n-io/n8n/commit/12a3168367e3208665ab78fe52b00cf508f7a50d)) +* **Microsoft Outlook Node:** Node overhaul ([#4449](https://github.com/n8n-io/n8n/issues/4449)) ([556a613](https://github.com/n8n-io/n8n/commit/556a6132bafc3eeb574fbd753a438a5e0f2c466d)) +* **Set Node:** Overhaul ([#6348](https://github.com/n8n-io/n8n/issues/6348)) ([3a47455](https://github.com/n8n-io/n8n/commit/3a474552b211fad8939a19492f34c5e7b3137002)) + + + +# [1.7.0](https://github.com/n8n-io/n8n/compare/n8n@1.6.0...n8n@1.7.0) (2023-09-13) + + +### Bug Fixes + +* Account for nanoid workflow ids for subworkflow execute policy ([#7094](https://github.com/n8n-io/n8n/issues/7094)) ([67092c0](https://github.com/n8n-io/n8n/commit/67092c0a1bf98ccc5ceadc3d582fac7bff2dc46c)) +* **Code Node:** Disable WASM to address CVE-2023-37903 ([#7122](https://github.com/n8n-io/n8n/issues/7122)) ([36a8e91](https://github.com/n8n-io/n8n/commit/36a8e911e6f58d0b87816fae0443c6ce8f5ea45a)) +* **Code Node:** Upgrade vm2 to address CVE-2023-37466 ([#7123](https://github.com/n8n-io/n8n/issues/7123)) ([0a35025](https://github.com/n8n-io/n8n/commit/0a35025e5e6669661bdfcc16378453ec9109a347)) +* **core:** Disable Node.js custom inspection to address CVE-2023-37903 ([#7125](https://github.com/n8n-io/n8n/issues/7125)) ([a223734](https://github.com/n8n-io/n8n/commit/a223734a4a781834bee1a1484dffc47c56e8d50e)), closes [#7122](https://github.com/n8n-io/n8n/issues/7122) +* **editor:** Tweak hover area of workflow / cred cards ([#7108](https://github.com/n8n-io/n8n/issues/7108)) ([217de21](https://github.com/n8n-io/n8n/commit/217de21605beca57f087921231ae929279071686)) +* **editor:** Unbind workflow endpoint events in case of workspace reset ([#7129](https://github.com/n8n-io/n8n/issues/7129)) ([c9b7948](https://github.com/n8n-io/n8n/commit/c9b79485cf7d361174aeba175ccb98de7d918693)) +* **editor:** Update git repo url validation regex ([#7151](https://github.com/n8n-io/n8n/issues/7151)) ([e51f173](https://github.com/n8n-io/n8n/commit/e51f173608dd79bfe53eb86eeaed976109f74410)) +* **Google Cloud Firestore Node:** Fix empty string interpreted as number ([#7136](https://github.com/n8n-io/n8n/issues/7136)) ([915cfa0](https://github.com/n8n-io/n8n/commit/915cfa0f6a0311ca34d2f8eeb471c601473314aa)) +* **HubSpot Node:** Fix issue with contact lists not working ([#5582](https://github.com/n8n-io/n8n/issues/5582)) ([6e5a4f6](https://github.com/n8n-io/n8n/commit/6e5a4f6a589550a816f421ffa966cfeea3cac64d)) +* **Postgres Node:** Fix automatic column mapping ([#7121](https://github.com/n8n-io/n8n/issues/7121)) ([92af131](https://github.com/n8n-io/n8n/commit/92af1314fe60560cdfb52b3307cc74559ba530a8)) +* **Zoho CRM Node:** Fix issue with Sales Order not updating ([#6959](https://github.com/n8n-io/n8n/issues/6959)) ([fd800b6](https://github.com/n8n-io/n8n/commit/fd800b674b52079eb2572a4d2465774759e9b31d)) + + +### Features + +* **core:** Add an option to enable WAL mode for SQLite ([#7118](https://github.com/n8n-io/n8n/issues/7118)) ([1d1a022](https://github.com/n8n-io/n8n/commit/1d1a022defefc790905cfb8fcb9dd364ffb063bb)) +* **core:** Add commands to workers to respond with current state ([#7029](https://github.com/n8n-io/n8n/issues/7029)) ([7b49cf2](https://github.com/n8n-io/n8n/commit/7b49cf2a2c750d685af6cff464401f38482dac5a)) +* **Salesforce Node:** Add fax field to lead option ([#7030](https://github.com/n8n-io/n8n/issues/7030)) ([01f875a](https://github.com/n8n-io/n8n/commit/01f875a94d193ba1e709bf6cfe31a3951f3af81a)) + + + +# [1.6.0](https://github.com/n8n-io/n8n/compare/n8n@1.5.1...n8n@1.6.0) (2023-09-06) + + +### Bug Fixes + +* **core:** Add support for in-transit encryption (TLS) on Redis connections ([#7047](https://github.com/n8n-io/n8n/issues/7047)) ([a910757](https://github.com/n8n-io/n8n/commit/a910757cc5ac7e47f9e0ca6a57c8b624e269aaa4)) +* **core:** Disallow orphan executions ([#7069](https://github.com/n8n-io/n8n/issues/7069)) ([8a28e98](https://github.com/n8n-io/n8n/commit/8a28e98ec811952163c58feaad608ec14ffc9243)) +* **core:** Split event bus controller into community and ee ([#7107](https://github.com/n8n-io/n8n/issues/7107)) ([011ee2e](https://github.com/n8n-io/n8n/commit/011ee2e04b62e0182e9d6787064dea70654cb4ab)) +* **editor:** Standardize save text ([#7093](https://github.com/n8n-io/n8n/issues/7093)) ([58b3492](https://github.com/n8n-io/n8n/commit/58b3492b0dcf140920ec97ce6d50e91a6d3a01a6)) +* Ensure all new executions are saved ([#7061](https://github.com/n8n-io/n8n/issues/7061)) ([b8e06d2](https://github.com/n8n-io/n8n/commit/b8e06d245f5b8bc969d2eb29793e9d98a33e69a8)) +* Load remote resources even if expressions in non requried parameters resolve ([#6987](https://github.com/n8n-io/n8n/issues/6987)) ([8a8d4e8](https://github.com/n8n-io/n8n/commit/8a8d4e8bb32588e79c3fcda2317c491ade9b3637)) +* **Postgres Node:** Connection pool of the database object has been destroyed ([#7074](https://github.com/n8n-io/n8n/issues/7074)) ([9dd5f0e](https://github.com/n8n-io/n8n/commit/9dd5f0e579ccfd5144ddb547977f15b45dd7c9ce)) +* **Postgres Node:** Tunnel doesn't always close ([#7087](https://github.com/n8n-io/n8n/issues/7087)) ([58e55ba](https://github.com/n8n-io/n8n/commit/58e55ba6691dc73a9f475a1989dccb51144e1825)) + + +### Features + +* **core:** Add list query middleware to credentials ([#7041](https://github.com/n8n-io/n8n/issues/7041)) ([fd78021](https://github.com/n8n-io/n8n/commit/fd78021b68a261291d76811a2a01d7336577bca7)) +* **core:** Add support for floating licenses ([#7090](https://github.com/n8n-io/n8n/issues/7090)) ([e26553f](https://github.com/n8n-io/n8n/commit/e26553f1981d2788f4f2e1e5f5ce23ff8a4a7c8d)) +* **core:** Migration for soft deletions for executions ([#7088](https://github.com/n8n-io/n8n/issues/7088)) ([413e0bc](https://github.com/n8n-io/n8n/commit/413e0bccb4d0144e5bdcf9221762e3f2b4428194)) +* **HTTP Request Node:** Determine binary file name from content-disposition headers ([#7032](https://github.com/n8n-io/n8n/issues/7032)) ([273d091](https://github.com/n8n-io/n8n/commit/273d0913fe5f45c0fe074e6a788e475d5a1d50bd)) +* **TheHive Node:** Overhaul ([#6457](https://github.com/n8n-io/n8n/issues/6457)) ([73e782e](https://github.com/n8n-io/n8n/commit/73e782e2cf9d4b96d8b3748e74ad93570663e536)) + + + +## [1.5.1](https://github.com/n8n-io/n8n/compare/n8n@1.5.0...n8n@1.5.1) (2023-08-31) + + +### Features + +* **Strapi Node:** Add token credentials ([#7048](https://github.com/n8n-io/n8n/issues/7048)) ([c01bca5](https://github.com/n8n-io/n8n/commit/c01bca562b71d8b524e29edd37e8397d0689b180)) + + + +# [1.5.0](https://github.com/n8n-io/n8n/compare/n8n@1.4.0...n8n@1.5.0) (2023-08-31) + + +### Bug Fixes + +* **Agile CRM Node:** Fix issue with company address not working ([#6997](https://github.com/n8n-io/n8n/issues/6997)) ([2f81652](https://github.com/n8n-io/n8n/commit/2f81652400b6a793fa610728519fd992c03c3d0d)) +* **Code Node:** Switch over to vm2 fork ([#7018](https://github.com/n8n-io/n8n/issues/7018)) ([dfe0fa6](https://github.com/n8n-io/n8n/commit/dfe0fa65f8111cd534387e26197cb3836d694e27)) +* **core:** Invalid NODES_INCLUDE should not crash the app ([#7038](https://github.com/n8n-io/n8n/issues/7038)) ([04e3178](https://github.com/n8n-io/n8n/commit/04e31789019aad6fe122ed81b06552a61d7f3a6d)), closes [#6683](https://github.com/n8n-io/n8n/issues/6683) +* **core:** Setup websocket keep-live messages ([#6866](https://github.com/n8n-io/n8n/issues/6866)) ([8bdb07d](https://github.com/n8n-io/n8n/commit/8bdb07d33ded48eab0b8f892a06e18f37bee9372)), closes [#6757](https://github.com/n8n-io/n8n/issues/6757) +* **core:** Throw `NodeSSLError` only for nodes that allow ignoring SSL issues ([#6928](https://github.com/n8n-io/n8n/issues/6928)) ([a01c3fb](https://github.com/n8n-io/n8n/commit/a01c3fbc19d66cf8b1dac3e34e0999dd36d81e7c)) +* **Date & Time Node:** Dont parse date if it's not set (null or undefined) ([#7050](https://github.com/n8n-io/n8n/issues/7050)) ([d72f79f](https://github.com/n8n-io/n8n/commit/d72f79ffb393a096f510f0c41bb66d987fe8cb0d)) +* **editor:** Fix sending of Ask AI tracking events ([#7002](https://github.com/n8n-io/n8n/issues/7002)) ([fb05afa](https://github.com/n8n-io/n8n/commit/fb05afa16560c3c837abf46824f8dc7fa3bb1c83)) +* **Microsoft Excel 365 Node:** Support for more extensions in workbook rlc ([#7020](https://github.com/n8n-io/n8n/issues/7020)) ([d6e1cf2](https://github.com/n8n-io/n8n/commit/d6e1cf232f86ddc69cceb69c8971c3373dab454c)) +* **MongoDB Node:** Stringify response ObjectIDs ([#6990](https://github.com/n8n-io/n8n/issues/6990)) ([9ca990b](https://github.com/n8n-io/n8n/commit/9ca990b9936ee80972952d0a1ad73c2926809ba2)) +* **MongoDB Node:** Upgrade mongodb package to address CVE-2021-32050 ([#7054](https://github.com/n8n-io/n8n/issues/7054)) ([d3f6356](https://github.com/n8n-io/n8n/commit/d3f635657c7514296fd0a473ba13672db2717490)) +* **Postgres Node:** Empty return data fix for Postgres and MySQL ([#7016](https://github.com/n8n-io/n8n/issues/7016)) ([176ccd6](https://github.com/n8n-io/n8n/commit/176ccd62bc1d6f28958c0fc894ee647f1e3a5f6e)) +* **Webhook Node:** Fix URL params for webhooks ([#6986](https://github.com/n8n-io/n8n/issues/6986)) ([596b569](https://github.com/n8n-io/n8n/commit/596b5695cdcca33da02bec428d58de8b2a13297e)) + + +### Features + +* **core:** Add filtering, selection and pagination to users ([#6994](https://github.com/n8n-io/n8n/issues/6994)) ([b716241](https://github.com/n8n-io/n8n/commit/b716241b428ef09cf6bdf32cb3a8680e9ba8f25f)) +* **core:** Add MFA ([#4767](https://github.com/n8n-io/n8n/issues/4767)) ([2b7ba6f](https://github.com/n8n-io/n8n/commit/2b7ba6fdf100ef78b60358648d773e2f200847b8)) +* **editor:** Debug executions in the editor ([#6834](https://github.com/n8n-io/n8n/issues/6834)) ([c833078](https://github.com/n8n-io/n8n/commit/c833078c87adeadb1e701f17d3f380c669eb1460)) +* External Secrets storage for credentials ([#6477](https://github.com/n8n-io/n8n/issues/6477)) ([ed927d3](https://github.com/n8n-io/n8n/commit/ed927d34b25b4ddd7048b622c141e32a8a57b6b7)) +* **RSS Read Node:** Add support for self signed certificates ([#7039](https://github.com/n8n-io/n8n/issues/7039)) ([3b9f0fe](https://github.com/n8n-io/n8n/commit/3b9f0fed7af2d3a234049ab7d50d883ee4608007)) + + + +# [1.4.0](https://github.com/n8n-io/n8n/compare/n8n@1.3.0...n8n@1.4.0) (2023-08-23) + + +### Bug Fixes + +* **core:** Add recoveryInProgress flag file ([#6962](https://github.com/n8n-io/n8n/issues/6962)) ([7b96820](https://github.com/n8n-io/n8n/commit/7b96820218449958180d4c34bebdc4c4de9172e1)) +* **core:** Fix `continueOnFail` for expression error in Set ([#6939](https://github.com/n8n-io/n8n/issues/6939)) ([d4fac05](https://github.com/n8n-io/n8n/commit/d4fac0527b7a34aace9ea0ff89dde152026d6c17)) +* **core:** Fix `import:workflow` command ([#6996](https://github.com/n8n-io/n8n/issues/6996)) ([8c38d85](https://github.com/n8n-io/n8n/commit/8c38d85e765114df03afb5e221b3d4ae4e6c0fc7)) +* **core:** Replace throw with warning when deactivating a non-active workflow ([#6969](https://github.com/n8n-io/n8n/issues/6969)) ([b6a00fe](https://github.com/n8n-io/n8n/commit/b6a00febbdd62560fa68321fbcd6e44c92a82ddd)) +* **core:** Set up OAuth2 cred test ([#6960](https://github.com/n8n-io/n8n/issues/6960)) ([4fc69b7](https://github.com/n8n-io/n8n/commit/4fc69b776ccea91c2a38249dc4f4ef0b191ce374)) +* **editor:** Do not flag dynamic load options issue on expression ([#6932](https://github.com/n8n-io/n8n/issues/6932)) ([60a1ef0](https://github.com/n8n-io/n8n/commit/60a1ef09934608afbe171bf3cf18ca5e95987153)) +* **editor:** Ensure community node install button tracks user agreement ([#6976](https://github.com/n8n-io/n8n/issues/6976)) ([0ddfc73](https://github.com/n8n-io/n8n/commit/0ddfc73bee88c994876787a41540ef847f4fcd4f)) +* **editor:** Fix parsing for single quoted resolvables ([#6982](https://github.com/n8n-io/n8n/issues/6982)) ([f32e993](https://github.com/n8n-io/n8n/commit/f32e9932275bb51a724c6ae681ed9ca0683a8e8e)) +* **editor:** Fix Remove all fields not removing values in resource mapper ([#6940](https://github.com/n8n-io/n8n/issues/6940)) ([e6cff3f](https://github.com/n8n-io/n8n/commit/e6cff3fce4de1644b75b07318fe561f721aee51c)) +* **editor:** Prevent Code node linter from erroring on `null` parse ([#6934](https://github.com/n8n-io/n8n/issues/6934)) ([40d3a29](https://github.com/n8n-io/n8n/commit/40d3a295d3c5ddcf5c034c02b3584532c395a12c)) +* **Google Sheets Node:** Fix short sheet name interpreted as range ([#6989](https://github.com/n8n-io/n8n/issues/6989)) ([00268a0](https://github.com/n8n-io/n8n/commit/00268a019a1b09ba49971504bb38cd81a281236e)) +* **Google Sheets Trigger Node:** Support sheet names with non-latin characters ([#6970](https://github.com/n8n-io/n8n/issues/6970)) ([052dd7c](https://github.com/n8n-io/n8n/commit/052dd7cc9d10b2365b38001c6e091bd52bf45873)) +* **GraphQL Node:** Improve error handling ([#6955](https://github.com/n8n-io/n8n/issues/6955)) ([41db637](https://github.com/n8n-io/n8n/commit/41db6371f0d7a736bf7e1c13e2a3acd678bec246)) +* **Mautic Node:** Fix issue with owner not being set correctly ([#6991](https://github.com/n8n-io/n8n/issues/6991)) ([64b950f](https://github.com/n8n-io/n8n/commit/64b950f2944f8fe352e6832f4059047d14ba9485)) +* **Salesforce Node:** Fix Account update owner operation ([#6958](https://github.com/n8n-io/n8n/issues/6958)) ([9b27878](https://github.com/n8n-io/n8n/commit/9b27878d8fe8755d76d09db07be01bcab83b8772)) +* **Shopify Node:** Fix pagination when using options ([#6972](https://github.com/n8n-io/n8n/issues/6972)) ([475d9c9](https://github.com/n8n-io/n8n/commit/475d9c98e8281e62e3566671bb6ddb1d028b543f)) +* **Webhook Node:** Backward compatible form-data parsing for non-array fields ([#6967](https://github.com/n8n-io/n8n/issues/6967)) ([9455bcf](https://github.com/n8n-io/n8n/commit/9455bcfef5383ee351b620f12a0f5fba2447a839)) + + +### Features + +* **core:** Add a warning to error workflows that cannot be started due to permission or settings ([#6961](https://github.com/n8n-io/n8n/issues/6961)) ([67b88f7](https://github.com/n8n-io/n8n/commit/67b88f75f424011e3e49ebe3a37e36871a2253ae)) +* **core:** Add support for ready hooks, and credentials overwrite endpoint in workers ([#6954](https://github.com/n8n-io/n8n/issues/6954)) ([8f8a1de](https://github.com/n8n-io/n8n/commit/8f8a1de3dd478e2e7b33b4b8a64bb0e3b7cda1f3)) +* **editor:** Show banner for non-production licenses ([#6943](https://github.com/n8n-io/n8n/issues/6943)) ([413570c](https://github.com/n8n-io/n8n/commit/413570c49dc800cfed0581b8ae1798aa0b4bbd50)) +* Remove PostHog event calls ([#6915](https://github.com/n8n-io/n8n/issues/6915)) ([270946a](https://github.com/n8n-io/n8n/commit/270946a93bc61ae3953a3dd9b2835e458565e12c)) +* **Send Email Node:** Add support for sending text and html email simultaneously ([#6978](https://github.com/n8n-io/n8n/issues/6978)) ([3860d41](https://github.com/n8n-io/n8n/commit/3860d41d737d56a616e3a8c61a90d3ec956b89cd)) + + + +# [1.3.0](https://github.com/n8n-io/n8n/compare/n8n@1.2.0...n8n@1.3.0) (2023-08-16) + + +### Bug Fixes + +* **core:** Don't let bull override the default redis config ([#6897](https://github.com/n8n-io/n8n/issues/6897)) ([cfeb322](https://github.com/n8n-io/n8n/commit/cfeb322b3b19405e2788e3ca3325fd491d5ebd28)) +* **core:** Fix fetching of EE executions ([#6901](https://github.com/n8n-io/n8n/issues/6901)) ([f3fce48](https://github.com/n8n-io/n8n/commit/f3fce48155d4a1ed262efce16cbe537b19aa6963)) +* **core:** Update frontend urls when using the `--tunnel` option ([#6898](https://github.com/n8n-io/n8n/issues/6898)) ([718e613](https://github.com/n8n-io/n8n/commit/718e61354dc62e6aa1af69050eea52de6aa3c350)) +* **editor:** Disable telemetry in dev mode and in E2E tests ([#6869](https://github.com/n8n-io/n8n/issues/6869)) ([808a928](https://github.com/n8n-io/n8n/commit/808a92809e4d389d902249646d5dc146de1d4e98)) +* **editor:** Fix code node’s content property to be reactive ([#6931](https://github.com/n8n-io/n8n/issues/6931)) ([3b75bc6](https://github.com/n8n-io/n8n/commit/3b75bc6bc1c954c4d4a899edddda214f0a2245fb)) +* **editor:** Fix event emit on credential sharing ([#6922](https://github.com/n8n-io/n8n/issues/6922)) ([297c3c9](https://github.com/n8n-io/n8n/commit/297c3c91f23a47b1aa78323d2b6e5677fbab8402)) +* **editor:** Fix multiOptions parameters resetting on initial load ([#6903](https://github.com/n8n-io/n8n/issues/6903)) ([49867c2](https://github.com/n8n-io/n8n/commit/49867c2b176a5e76c4b9e7caafc29a44633332bf)) +* **editor:** Update execution view layout ([#6882](https://github.com/n8n-io/n8n/issues/6882)) ([0339732](https://github.com/n8n-io/n8n/commit/03397323784809eb64c30b2ee535e8516ab06fbf)) +* **Email Trigger (IMAP) Node:** Fix connection issue with unexpected spaces in host ([#6886](https://github.com/n8n-io/n8n/issues/6886)) ([f3248e4](https://github.com/n8n-io/n8n/commit/f3248e46e478e5ac061c3d69da988b0db8acf553)) +* Fix issue with key formatting if null or undefined ([#6924](https://github.com/n8n-io/n8n/issues/6924)) ([4e4a3cf](https://github.com/n8n-io/n8n/commit/4e4a3cf7ab8dee45cc15e8948f8628c5915cc841)) +* Fix issue with key formatting introduced in 1.2.0 ([#6896](https://github.com/n8n-io/n8n/issues/6896)) ([0e075c9](https://github.com/n8n-io/n8n/commit/0e075c9cb540d78209aaa3c8240259f074c31846)) +* Fix lag when node parameters are updated ([#6941](https://github.com/n8n-io/n8n/issues/6941)) ([3eb65e0](https://github.com/n8n-io/n8n/commit/3eb65e08c4f22334aeae172504991a688273528e)) +* **HTTP Request Node:** Improve error handling for TCP socket errors when `Continue On Fail` is enabled ([#6925](https://github.com/n8n-io/n8n/issues/6925)) ([96ff1f8](https://github.com/n8n-io/n8n/commit/96ff1f847d2672ecf6604dde6c3b5c594c3b1e2f)) +* Prevent workflow breaking when credential type is unknown ([#6923](https://github.com/n8n-io/n8n/issues/6923)) ([e83b93f](https://github.com/n8n-io/n8n/commit/e83b93f293ab8df37f896cc315afab50a477bbef)) +* **Respond to Webhook Node:** Return headers in response ([#6921](https://github.com/n8n-io/n8n/issues/6921)) ([a82107f](https://github.com/n8n-io/n8n/commit/a82107fb05120f7365086032c9b05ea316716327)) + + +### Features + +* **core:** Add support for not requiring SMTP auth with user management ([#3742](https://github.com/n8n-io/n8n/issues/3742)) ([eead6d4](https://github.com/n8n-io/n8n/commit/eead6d49f238f41714911d1488b75515472c34a0)) +* **core:** Descriptive message for common nodeJS errors ([#6841](https://github.com/n8n-io/n8n/issues/6841)) ([3adb0b6](https://github.com/n8n-io/n8n/commit/3adb0b66ea2c1e929850345bc31e5b0a708eabd0)) +* **editor:** Ask AI in Code node ([#6672](https://github.com/n8n-io/n8n/issues/6672)) ([fde6ad1](https://github.com/n8n-io/n8n/commit/fde6ad1e7fa6236c6c1ac1e3ff6d8e959012543a)) +* Enable parallel processing on multiple queue nodes ([#6295](https://github.com/n8n-io/n8n/issues/6295)) ([44afcff](https://github.com/n8n-io/n8n/commit/44afcff95916cd00df66391e54440d3f96788913)) + + + +# [1.2.0](https://github.com/n8n-io/n8n/compare/n8n@1.1.0...n8n@1.2.0) (2023-08-09) + + +### Bug Fixes + +* Auth.api user limit test expecting incorrect status ([#6836](https://github.com/n8n-io/n8n/issues/6836)) ([371bfa0](https://github.com/n8n-io/n8n/commit/371bfa0f488f0a240bd1a8854759d6f6639e6eea)) +* **Code Node:** Consistent redirection of stdout for JS and Python sandboxes ([#6818](https://github.com/n8n-io/n8n/issues/6818)) ([f718c22](https://github.com/n8n-io/n8n/commit/f718c2291f0385bfb60ea70e7b7a943a4c6b5149)) +* **core:** Add missing primary key on the `execution_data` table on postgres ([#6797](https://github.com/n8n-io/n8n/issues/6797)) ([dc295ac](https://github.com/n8n-io/n8n/commit/dc295ac5bfe8a62ce208656a8b25a89376af276a)) +* **core:** Add sharing data to workflows in EE executions ([#6872](https://github.com/n8n-io/n8n/issues/6872)) ([6796d9e](https://github.com/n8n-io/n8n/commit/6796d9e5d676a7e76206462ef18640c246f0dc5a)) +* **core:** Allow ignoring SSL issues on generic oauth2 credentials ([#6702](https://github.com/n8n-io/n8n/issues/6702)) ([feac369](https://github.com/n8n-io/n8n/commit/feac369f6c5590f30e63bed6d5569a57710a8d2e)) +* **core:** Change VariablesService to DI and use caching ([#6827](https://github.com/n8n-io/n8n/issues/6827)) ([659ca26](https://github.com/n8n-io/n8n/commit/659ca26fe7cf7d1b9403f6c20b63b598f7dfa7b5)) +* **core:** Fix loading of scoped-community packages ([#6807](https://github.com/n8n-io/n8n/issues/6807)) ([53e58b4](https://github.com/n8n-io/n8n/commit/53e58b408a8ef6a0f911593180d818ccee92cc1d)) +* **core:** Fix property existence checks on AugmentObject ([#6842](https://github.com/n8n-io/n8n/issues/6842)) ([732416f](https://github.com/n8n-io/n8n/commit/732416f52f49eeb542401439e5b96b4b1a083c05)) +* **core:** Fix source control name and email being switched ([#6839](https://github.com/n8n-io/n8n/issues/6839)) ([6ec7033](https://github.com/n8n-io/n8n/commit/6ec7033bb711648c8c2d93f4f2b15b3b6dfdcaea)) +* **core:** Fix WebSocket close codes ([a8bfb46](https://github.com/n8n-io/n8n/commit/a8bfb4618323731de8bc9bc265b3bbb9de69c50c)) +* **core:** Log crash causes to console when sentry is disabled ([#6890](https://github.com/n8n-io/n8n/issues/6890)) ([6553d92](https://github.com/n8n-io/n8n/commit/6553d92c7c128b117b6bdbd33182002f6cf24fa1)) +* **core:** OAuth1 authentication fix for Clever Cloud API ([#6847](https://github.com/n8n-io/n8n/issues/6847)) ([5ab30fd](https://github.com/n8n-io/n8n/commit/5ab30fdd95c766e57f8c2cf7c7a9951bbf3d7c2d)) +* **core:** Restrict read/write file paths access ([#6582](https://github.com/n8n-io/n8n/issues/6582)) ([f6bf9e9](https://github.com/n8n-io/n8n/commit/f6bf9e9887bb46bb884fd06f3e789f8585445501)) +* **core:** Serialize BigInts ([#6805](https://github.com/n8n-io/n8n/issues/6805)) ([7b27fa5](https://github.com/n8n-io/n8n/commit/7b27fa5898cd3c8c9882cbec0c36ff703510b57d)) +* **core:** Update packages to address CVE-2023-2142 and CVE-2020-28469 ([#6844](https://github.com/n8n-io/n8n/issues/6844)) ([a5667e6](https://github.com/n8n-io/n8n/commit/a5667e6c42fae96a48e39464cbcec808d36bb5ee)) +* Correct typos in Taiga and ServiceNow nodes ([#6814](https://github.com/n8n-io/n8n/issues/6814)) ([803b152](https://github.com/n8n-io/n8n/commit/803b1521facb7908c34fbb729d15aaaf5680e471)) +* Display source control buttons properly ([#6756](https://github.com/n8n-io/n8n/issues/6756)) ([d050b99](https://github.com/n8n-io/n8n/commit/d050b99fb218ae31dfd0ec7dea88884e14021495)) +* **editor:** Close tags dropdown when modal is opened ([#6766](https://github.com/n8n-io/n8n/issues/6766)) ([cf00ba7](https://github.com/n8n-io/n8n/commit/cf00ba708941bebceda8278a359b0badaa111f2c)), closes [#6571](https://github.com/n8n-io/n8n/issues/6571) +* **editor:** Do not show mapping discoverability tooltip after dismiss ([#6862](https://github.com/n8n-io/n8n/issues/6862)) ([08982ed](https://github.com/n8n-io/n8n/commit/08982ede4c3dd2b38b1a0ce91adf8198f0af1776)) +* **editor:** Fix code node highlight error ([#6791](https://github.com/n8n-io/n8n/issues/6791)) ([50b0dc2](https://github.com/n8n-io/n8n/commit/50b0dc21fd0a698f87538e5458274ed50ab6774e)) +* **editor:** Fix collapsed sub menu elements ([#6778](https://github.com/n8n-io/n8n/issues/6778)) ([d33528d](https://github.com/n8n-io/n8n/commit/d33528dab399bc01461ab5e3af1fbb75e5d89d91)) +* **editor:** Fix credential errors in executions view for workflow sharee ([#6875](https://github.com/n8n-io/n8n/issues/6875)) ([a0f9b2e](https://github.com/n8n-io/n8n/commit/a0f9b2eefe6050264b0c892c6e79e2140ab05825)) +* **editor:** Fix redo when adding node on connection ([#6833](https://github.com/n8n-io/n8n/issues/6833)) ([4ac4b85](https://github.com/n8n-io/n8n/commit/4ac4b850dd7187e4e4ef50bbe442ebf273e51eb9)) +* **editor:** Fix tooltip opening delay prop name ([#6776](https://github.com/n8n-io/n8n/issues/6776)) ([e19b0d7](https://github.com/n8n-io/n8n/commit/e19b0d7748765570903831d5e676e6c4deba2fef)) +* **editor:** Fix value syncing in SQL and HTML editor ([#6848](https://github.com/n8n-io/n8n/issues/6848)) ([90e825f](https://github.com/n8n-io/n8n/commit/90e825f74368588073b1403b0681a89704eced06)) +* **editor:** Improve displaying and hiding of connections actions ([#6823](https://github.com/n8n-io/n8n/issues/6823)) ([369a2e9](https://github.com/n8n-io/n8n/commit/369a2e97968c6d40d827d8e780a8d3f1fd2188f7)) +* **editor:** Prevent text edit dialog from re-opening in same tick ([#6781](https://github.com/n8n-io/n8n/issues/6781)) ([c9f3acc](https://github.com/n8n-io/n8n/commit/c9f3acc8485edd0adbde4a633318a01b018d6fc0)) +* **editor:** Remove additional margin on tooltip ([#6802](https://github.com/n8n-io/n8n/issues/6802)) ([651cf34](https://github.com/n8n-io/n8n/commit/651cf34da60fb3842132905adfee3cd5e1395c75)) +* **editor:** Resolve vue 3 related console-warnings ([#6779](https://github.com/n8n-io/n8n/issues/6779)) ([30484a0](https://github.com/n8n-io/n8n/commit/30484a0615c90525fa0604b8b0df16159f18a6d4)) +* **editor:** Vue3 - Fix modal positioning and multi-select tag sizing ([#6783](https://github.com/n8n-io/n8n/issues/6783)) ([4e491b7](https://github.com/n8n-io/n8n/commit/4e491b754fe74ebdffdc292f4358ee98ad99e01d)) +* **Email Trigger (IMAP) Node:** UTF-8 attachments are not correctly named ([#6856](https://github.com/n8n-io/n8n/issues/6856)) ([72814d1](https://github.com/n8n-io/n8n/commit/72814d1f0fb5dfce19a89397a6a1cd5829563222)) +* Fix all modal sizes ([#6820](https://github.com/n8n-io/n8n/issues/6820)) ([7525cfe](https://github.com/n8n-io/n8n/commit/7525cfe2dcd03e5050ffb32e8fef64132788d62e)) +* Fix horizontal overflow for dialogs ([#6830](https://github.com/n8n-io/n8n/issues/6830)) ([41d8a18](https://github.com/n8n-io/n8n/commit/41d8a18d47dfeb0119034efe0e5779a9bfb0edce)) +* Fix issue with key based credentials not being read correctly ([#6824](https://github.com/n8n-io/n8n/issues/6824)) ([db21a8d](https://github.com/n8n-io/n8n/commit/db21a8db75833c86ab3d662c6c864f9f5e47c436)) +* Fix tags overflow handler in workflows header ([#6784](https://github.com/n8n-io/n8n/issues/6784)) ([7cd4588](https://github.com/n8n-io/n8n/commit/7cd45885bf13479948a4c5bb441d24b3c042a624)) +* **GoToWebinar Node:** Fix issue with timezone incorrectly being required ([#6865](https://github.com/n8n-io/n8n/issues/6865)) ([905eef8](https://github.com/n8n-io/n8n/commit/905eef85594bb10f27c7c97f24ac7f9fcedcc5cf)) +* Handle subtitle errors when pasting workflow ([#6826](https://github.com/n8n-io/n8n/issues/6826)) ([31a4cfc](https://github.com/n8n-io/n8n/commit/31a4cfc9698666bb69b2fd54d7af3644a31b69c2)) +* **Lemlist Node:** Fix pagination issues with campaigns and activities ([#6734](https://github.com/n8n-io/n8n/issues/6734)) ([c3e76ec](https://github.com/n8n-io/n8n/commit/c3e76ec697991f4a184e51cb7753a5c89064f7f9)) +* **Linear Node:** Fix issue creation priority ([#6813](https://github.com/n8n-io/n8n/issues/6813)) ([fce8cc4](https://github.com/n8n-io/n8n/commit/fce8cc427579c9098359b9d1ecc420650299b229)) +* **Postgres Trigger Node:** Imposible to cancell execution manually ([#6709](https://github.com/n8n-io/n8n/issues/6709)) ([491378d](https://github.com/n8n-io/n8n/commit/491378de772670b640be23e3b9bc7eadad6d3ebe)) +* Remove tag animation ([#6821](https://github.com/n8n-io/n8n/issues/6821)) ([52aafe0](https://github.com/n8n-io/n8n/commit/52aafe07372dd3dd46ddc8e4ad14de08f02fb4e8)) +* Respect set modal widths ([#6771](https://github.com/n8n-io/n8n/issues/6771)) ([3aaf1ac](https://github.com/n8n-io/n8n/commit/3aaf1ac0fd9cb7db6e074c1dca421de066c0793b)), closes [#6571](https://github.com/n8n-io/n8n/issues/6571) +* Show NodeIcon tooltips by removing pointer-events: none ([#6777](https://github.com/n8n-io/n8n/issues/6777)) ([eb898f7](https://github.com/n8n-io/n8n/commit/eb898f744f7c3df92635409a136809473b4de0c1)) +* **TheHive Node:** Treat `ApiKey` as a secret ([#6786](https://github.com/n8n-io/n8n/issues/6786)) ([11a3965](https://github.com/n8n-io/n8n/commit/11a3965ba89e75daaccf39596272eef70f1fc316)) +* **Todoist Node:** Fix issue with section id being ignored ([#6799](https://github.com/n8n-io/n8n/issues/6799)) ([749468e](https://github.com/n8n-io/n8n/commit/749468e4fa3d823785a01e79b02f4897bc40c943)) + + +### Features + +* Clean up onboarding experiment ([#6873](https://github.com/n8n-io/n8n/issues/6873)) ([3619345](https://github.com/n8n-io/n8n/commit/36193451692b878cea703e3a743d9ac8f3ba5086)) +* **core:** Add metrics option to cache ([#6846](https://github.com/n8n-io/n8n/issues/6846)) ([adcf5a9](https://github.com/n8n-io/n8n/commit/adcf5a96e83d8b43b155f5a834a74b3a1fd4274b)) +* **core:** Add unique id to instances ([#6863](https://github.com/n8n-io/n8n/issues/6863)) ([6499f42](https://github.com/n8n-io/n8n/commit/6499f424811188fa4785db9d86b1299e4fb39540)) +* **core:** Create a dsl for writing db agnostic migrations ([#6853](https://github.com/n8n-io/n8n/issues/6853)) ([75be1a9](https://github.com/n8n-io/n8n/commit/75be1a9c0d223a5d866551e836d7ab1b952d03ca)) +* **core:** Credentials for popular SecOps services, Part 1 ([#6775](https://github.com/n8n-io/n8n/issues/6775)) ([11567f9](https://github.com/n8n-io/n8n/commit/11567f946be411f081b2ba25c2ac60d5dcf56835)) +* **core:** Make Redis available for backend communication ([#6719](https://github.com/n8n-io/n8n/issues/6719)) ([3cad60e](https://github.com/n8n-io/n8n/commit/3cad60e9184de7ad73893062964366a09d4487df)) +* **editor:** Add "Download" button if JSON data is to large ([#6850](https://github.com/n8n-io/n8n/issues/6850)) ([efe08cc](https://github.com/n8n-io/n8n/commit/efe08cced3979e40bbf011301f51f9f2f69117de)) +* **editor:** Migrate Design System and Editor UI to Vue 3 ([#6476](https://github.com/n8n-io/n8n/issues/6476)) ([dd6a4c9](https://github.com/n8n-io/n8n/commit/dd6a4c956adb997b59ee0174eedd9b8f6265ec6b)), closes [#6571](https://github.com/n8n-io/n8n/issues/6571) +* **Facebook Graph API Node:** Add support for v16 and v17 ([#6808](https://github.com/n8n-io/n8n/issues/6808)) ([46a41c1](https://github.com/n8n-io/n8n/commit/46a41c1c91c7ebadd72fb51b6f749a542a7393fc)) +* **Pipedrive Node:** Add option to update the file name and description ([#6883](https://github.com/n8n-io/n8n/issues/6883)) ([f8ad543](https://github.com/n8n-io/n8n/commit/f8ad543af5c5d680d23d5dab861a6b3fe47b4ccc)) + + +### Performance Improvements + +* **core:** Add filtering and pagination to `GET /workflows` ([#6845](https://github.com/n8n-io/n8n/issues/6845)) ([dceff67](https://github.com/n8n-io/n8n/commit/dceff675ecd1d9f72ca2614c42da2bfaa758f305)), closes [#6876](https://github.com/n8n-io/n8n/issues/6876) +* **core:** Cache roles ([#6803](https://github.com/n8n-io/n8n/issues/6803)) ([e4f0418](https://github.com/n8n-io/n8n/commit/e4f041815a9a919add9ad616e17ed31d4b645c2c)) +* **core:** Cache webhooks ([#6825](https://github.com/n8n-io/n8n/issues/6825)) ([0511458](https://github.com/n8n-io/n8n/commit/0511458d41c65f81edc17b5ff58a0be911a7629d)) +* **editor:** Memoize locale translate calls during actions generation ([#6773](https://github.com/n8n-io/n8n/issues/6773)) ([2d47e8d](https://github.com/n8n-io/n8n/commit/2d47e8dc4ad2ec75d25cd5e67ab89f29766e0a7f)) + + + +# [1.1.0](https://github.com/n8n-io/n8n/compare/n8n@1.0.1...n8n@1.1.0) (2023-07-26) + + +### Bug Fixes + +* Add missing indices on sqlite ([#6673](https://github.com/n8n-io/n8n/issues/6673)) ([b1838f7](https://github.com/n8n-io/n8n/commit/b1838f7fab3e6d2983fa4d2ba1a480c79f8fe2c5)) +* **API:** Do not add starting node on workflow creation ([#6686](https://github.com/n8n-io/n8n/issues/6686)) ([92192fb](https://github.com/n8n-io/n8n/commit/92192fbd6108a71fca591e5284269239d4347621)) +* **API:** Fix issue with workflow setting not supporting newer nanoids ([#6699](https://github.com/n8n-io/n8n/issues/6699)) ([c7e1013](https://github.com/n8n-io/n8n/commit/c7e10130d694347982f50bc0ad1024101e27beea)) +* **AwsS3 Node:** Fix issue if bucket name contains a '.' ([#6542](https://github.com/n8n-io/n8n/issues/6542)) ([540d32d](https://github.com/n8n-io/n8n/commit/540d32dee4b8927199e047c77acf516d5b824bc3)) +* **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)) +* **Code Node:** Install python modules always in a user-writable folder ([#6568](https://github.com/n8n-io/n8n/issues/6568)) ([bf35124](https://github.com/n8n-io/n8n/commit/bf351243dfa69095699596e8828904fda025d45c)) +* **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:** Deleting manual executions should defer deleting binary data ([#6680](https://github.com/n8n-io/n8n/issues/6680)) ([462a674](https://github.com/n8n-io/n8n/commit/462a674d1759dc5ae9849daa48cc8c4130dd030e)) +* **core:** Filter out workflows that failed to activate on startup ([#6676](https://github.com/n8n-io/n8n/issues/6676)) ([667c15d](https://github.com/n8n-io/n8n/commit/667c15d0dfbdab0c29114d5002eed75031c79b42)) +* **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:** Handle all uncaught exception, not just the ones from Axios ([#6666](https://github.com/n8n-io/n8n/issues/6666)) ([ff07595](https://github.com/n8n-io/n8n/commit/ff0759530df3780e8eb53a29ec4ab4e98e5755c6)) +* **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:** Load SAML libraries dynamically ([#6690](https://github.com/n8n-io/n8n/issues/6690)) ([fce5609](https://github.com/n8n-io/n8n/commit/fce5609fa32b81ff8e44567233c77a7a1d6232df)) +* **core:** Redirect user to previous url after SSO signin ([#6710](https://github.com/n8n-io/n8n/issues/6710)) ([08331c6](https://github.com/n8n-io/n8n/commit/08331c63fbc0d69d2203f14165b15e1a596788b0)) +* **core:** Reduce memory consumption on `BinaryDataManager.init` ([#6633](https://github.com/n8n-io/n8n/issues/6633)) ([329d22f](https://github.com/n8n-io/n8n/commit/329d22f5d1f484d899df885a871f0f32839f0098)) +* **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:** 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)) +* **core:** Support redis cluster in queue mode ([#6708](https://github.com/n8n-io/n8n/issues/6708)) ([4029386](https://github.com/n8n-io/n8n/commit/40293863492d73865f805870937e46f3d0bdaa56)) +* **core:** Update docker compose setup for V1 images ([#6642](https://github.com/n8n-io/n8n/issues/6642)) ([05007d8](https://github.com/n8n-io/n8n/commit/05007d894e1bfa3550cfd865f85c2844b3db6eea)) +* **core:** Upgrade semver to address CVE-2022-25883 ([#6689](https://github.com/n8n-io/n8n/issues/6689)) ([9daf944](https://github.com/n8n-io/n8n/commit/9daf944ba53937ddd41bd640a6d473d235f0e16f)) +* **core:** Use `exec` in docker images to forward signals correctly ([#6732](https://github.com/n8n-io/n8n/issues/6732)) ([ed9f86b](https://github.com/n8n-io/n8n/commit/ed9f86bb95f7080335a336c866cd477669b02002)) +* **core:** Use JWT as reset password token ([#6714](https://github.com/n8n-io/n8n/issues/6714)) ([89f4402](https://github.com/n8n-io/n8n/commit/89f44021b919181ad58c555a31071c286b866975)) +* **core:** Use lower cased email for SAML email attribute ([#6663](https://github.com/n8n-io/n8n/issues/6663)) ([eedde24](https://github.com/n8n-io/n8n/commit/eedde24cc046ea517c6d6e455bcdd46a97e4c05b)) +* **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)) +* **crowd.dev Node:** Fix documentation urls for crowd.dev credentials and nodes ([#6696](https://github.com/n8n-io/n8n/issues/6696)) ([acda7f2](https://github.com/n8n-io/n8n/commit/acda7f269f7b227d430e2308767e1676fda8e165)) +* **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:** Add paywall state to non owner users for Variables ([#6679](https://github.com/n8n-io/n8n/issues/6679)) ([e7091d6](https://github.com/n8n-io/n8n/commit/e7091d6726eb98a194bc2614c0da332d348180dc)) +* **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)) +* **editor:** Ensure default credential values are not detected as dirty state ([#6677](https://github.com/n8n-io/n8n/issues/6677)) ([c7b74c3](https://github.com/n8n-io/n8n/commit/c7b74c3c1fdd2f11b8650adf79052681c2f1e248)) +* **editor:** Extend menu item and use it as a recursive component ([#6618](https://github.com/n8n-io/n8n/issues/6618)) ([d617f63](https://github.com/n8n-io/n8n/commit/d617f63ae9155c03dbf5e597db8e102c1f7a025f)) +* **editor:** Hide Execute Node button for unknown nodes ([#6684](https://github.com/n8n-io/n8n/issues/6684)) ([6887b4e](https://github.com/n8n-io/n8n/commit/6887b4edeae6536f5b670c55637fde1c5dbd6e40)) +* **editor:** Increase contrast ratio in execution list workflow names ([#6661](https://github.com/n8n-io/n8n/issues/6661)) ([c0b1cb2](https://github.com/n8n-io/n8n/commit/c0b1cb273e56c04abff5d9893a5e37cda6eb3383)) +* **editor:** Make Source control branch select required ([#6619](https://github.com/n8n-io/n8n/issues/6619)) ([20737b5](https://github.com/n8n-io/n8n/commit/20737b532423c964bd70e0aa2aac2c9533b5c3d4)) +* **editor:** Prevent keyboard shortcuts to edit workflows in readonly mode ([#6613](https://github.com/n8n-io/n8n/issues/6613)) ([7383e7f](https://github.com/n8n-io/n8n/commit/7383e7fd48b5c86d43fab4fa47cb1e7a4a4b4043)) +* **editor:** Prevent RMC from loading schema if it's already cached ([#6695](https://github.com/n8n-io/n8n/issues/6695)) ([a79aa19](https://github.com/n8n-io/n8n/commit/a79aa198330808b7e45b217748fffdabb68098e2)) +* **editor:** Remove global link styling in v1 banner ([#6705](https://github.com/n8n-io/n8n/issues/6705)) ([76a765a](https://github.com/n8n-io/n8n/commit/76a765a1517f53cb0c4f16a57bdd6f55bc3be0bd)) +* **editor:** Show appropriate empty workflow list content when instance environment is readonly ([#6610](https://github.com/n8n-io/n8n/issues/6610)) ([7515f7d](https://github.com/n8n-io/n8n/commit/7515f7d52ae72fa1361687c43a9c29f585b3c6ce)) +* **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)) +* **editor:** Skip error line highlighting if out of range ([#6721](https://github.com/n8n-io/n8n/issues/6721)) ([a62d00a](https://github.com/n8n-io/n8n/commit/a62d00a4795d02a5905c5ddbae569f122a46a023)) +* **editor:** Update design system menu item component ([#6659](https://github.com/n8n-io/n8n/issues/6659)) ([84466e9](https://github.com/n8n-io/n8n/commit/84466e983336a3cfa8c20b30ead8d58abbf07cf0)) +* **FileMaker Node:** Improve returned error responses ([#6585](https://github.com/n8n-io/n8n/issues/6585)) ([91a052e](https://github.com/n8n-io/n8n/commit/91a052e4c53b6717d3a4e4d1034339012ce39c41)) +* **FTP Node:** List recursive ignore . and .. to prevent infinite loops ([#6707](https://github.com/n8n-io/n8n/issues/6707)) ([995d5cc](https://github.com/n8n-io/n8n/commit/995d5cc47095e584c2a064dd5aa3841b3054ffd4)) +* **GitLab Trigger Node:** Fix trigger activation 404 error ([#6711](https://github.com/n8n-io/n8n/issues/6711)) ([8ceb832](https://github.com/n8n-io/n8n/commit/8ceb8322ebfc1384291e8a0d5d07ee5c22d52142)) +* **Gmail Trigger Node:** Early returns in case of no data ([#6727](https://github.com/n8n-io/n8n/issues/6727)) ([c2511a8](https://github.com/n8n-io/n8n/commit/c2511a829cfa40d4d9d570ad7724845c93544c3b)) +* **Google BigQuery Node:** Error description improvement ([#6715](https://github.com/n8n-io/n8n/issues/6715)) ([95837d2](https://github.com/n8n-io/n8n/commit/95837d260466e8a4bd19c44390ce4557fa5c51cb)) +* **Google Drive Node:** Fix regex in file RLC ([#6607](https://github.com/n8n-io/n8n/issues/6607)) ([5672146](https://github.com/n8n-io/n8n/commit/56721468dff51bb60ececc0472e2b9ca0740fcb1)) +* **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)) +* **HTTP Request Node:** Cleanup circular references in response ([#6590](https://github.com/n8n-io/n8n/issues/6590)) ([aecc05b](https://github.com/n8n-io/n8n/commit/aecc05b787d09ba778adc5b4bf96abacc5a64204)) +* **Merge Node:** Enrich input 2 fix ([#6526](https://github.com/n8n-io/n8n/issues/6526)) ([c82c7f1](https://github.com/n8n-io/n8n/commit/c82c7f19128df3a11d6d0f18e8d8dab57e6a3b8f)) +* **Microsoft Outlook Node:** Fix issue with category not correctly applying ([#6583](https://github.com/n8n-io/n8n/issues/6583)) ([fc8ed55](https://github.com/n8n-io/n8n/commit/fc8ed55c0de7db4cf17f4f1bf417f6cbf48444c2)) +* **Notion Node:** Version fix ([#6531](https://github.com/n8n-io/n8n/issues/6531)) ([38dc784](https://github.com/n8n-io/n8n/commit/38dc784d2eed25aae777c5c3c3fda1a35e20bd24)) +* **Postgres Node:** Arrays in query replacement fix ([#6718](https://github.com/n8n-io/n8n/issues/6718)) ([4cae091](https://github.com/n8n-io/n8n/commit/4cae091cfb54701dfa51b5204799df0a8b4929cd)) +* **Postgres Node:** For select queries, empty result should be be replaced with `{"success":true}` ([#6703](https://github.com/n8n-io/n8n/issues/6703)) ([250175d](https://github.com/n8n-io/n8n/commit/250175d066d6759f4a787371b47ed91ee62aab71)) +* **Postgres Node:** Upsert does not fetch columns when schema other then public ([#6643](https://github.com/n8n-io/n8n/issues/6643)) ([aaa9ee3](https://github.com/n8n-io/n8n/commit/aaa9ee3949529a745e3b624716da1549ed571604)) +* **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)) +* **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)) +* Stop n8n from complaining about credentials when saving a new workflow form a template ([#6671](https://github.com/n8n-io/n8n/issues/6671)) ([486d16b](https://github.com/n8n-io/n8n/commit/486d16bcdb6be12dd85f4af5f7de878d9d178fd6)) +* **Strapi Node:** Fix issue with pagination ([#4991](https://github.com/n8n-io/n8n/issues/4991)) ([54444fa](https://github.com/n8n-io/n8n/commit/54444fa388da12d75553e66e53a8cf6f8a99b6fc)) +* **Telegram Trigger Node:** Add guard to 'include' call on null or undefined ([#6730](https://github.com/n8n-io/n8n/issues/6730)) ([533b0ba](https://github.com/n8n-io/n8n/commit/533b0bac13d275753fee329bb4abe0c7aae5b48b)) +* **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 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)) +* Add various source control improvements ([#6533](https://github.com/n8n-io/n8n/issues/6533)) ([68fdc20](https://github.com/n8n-io/n8n/commit/68fdc2078928be478a286774f2889feba1c3f5fe)) +* **Airtable Node:** Overhaul ([#6200](https://github.com/n8n-io/n8n/issues/6200)) ([b69d20c](https://github.com/n8n-io/n8n/commit/b69d20c12ec1ad0395e23747ce5f1d437de0231b)) +* Allow `eslint-config` to be externally consumable ([#6694](https://github.com/n8n-io/n8n/issues/6694)) ([3566c13](https://github.com/n8n-io/n8n/commit/3566c13afc9795206b5a4af7b06159e35b046e12)) +* Allow hiding credential params on cloud ([#6687](https://github.com/n8n-io/n8n/issues/6687)) ([2af1c24](https://github.com/n8n-io/n8n/commit/2af1c24ead4c02e0588d4c2dfcf4a289f54388d4)) +* **API:** Implement users account quota guards ([#6434](https://github.com/n8n-io/n8n/issues/6434)) ([e5620ab](https://github.com/n8n-io/n8n/commit/e5620ab1e49548e4c2ffd296e055f1caee94d2ba)), closes [#6636](https://github.com/n8n-io/n8n/issues/6636) +* **core:** Add cache service ([#6729](https://github.com/n8n-io/n8n/issues/6729)) ([c0d2bac](https://github.com/n8n-io/n8n/commit/c0d2bac94d732d4166633adf9c3b8eaf5d8046be)) +* **core:** Only show V1 banner to users who migrated ([#6622](https://github.com/n8n-io/n8n/issues/6622)) ([071e56f](https://github.com/n8n-io/n8n/commit/071e56f7fde2fc4ffc7e3da92a069d8ed593bc60)) +* **editor:** Implement new banners framework ([#6603](https://github.com/n8n-io/n8n/issues/6603)) ([4240e76](https://github.com/n8n-io/n8n/commit/4240e76253e02da13942d4b84a83ec22fd30aca3)) +* **editor:** Load fixed template list as experiment ([#6632](https://github.com/n8n-io/n8n/issues/6632)) ([e996622](https://github.com/n8n-io/n8n/commit/e9966224ea555ce8f3a48872887a73ef879e47c3)) +* **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:** Removing `ph-no-capture` class from some elements ([#6674](https://github.com/n8n-io/n8n/issues/6674)) ([c3455a4](https://github.com/n8n-io/n8n/commit/c3455a4ad82d965c41267fea173feaad4800c43f)) +* Environments release using source control ([#6653](https://github.com/n8n-io/n8n/issues/6653)) ([fc7aa8b](https://github.com/n8n-io/n8n/commit/fc7aa8bd664553c463536b6abe58a488e5e11147)) +* **Google Cloud Storage Node:** Use streaming for file uploads ([#6462](https://github.com/n8n-io/n8n/issues/6462)) ([cd0e41a](https://github.com/n8n-io/n8n/commit/cd0e41a6b49a58bee95721363dd7e6c43de28725)) +* **Google Drive Node:** Overhaul ([#5941](https://github.com/n8n-io/n8n/issues/5941)) ([d70a1cb](https://github.com/n8n-io/n8n/commit/d70a1cb0c82ee0a4b92776684c6c9079020d028f)) +* **HTML Node:** 'Convert to table operation ([#6540](https://github.com/n8n-io/n8n/issues/6540)) ([8abb03d](https://github.com/n8n-io/n8n/commit/8abb03d7cf02ad7f03a0adffa646870df5f1a02c)) +* **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)) +* **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)) +* **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)) +* **OpenAI Node:** Update max token limit to support newer model limits ([#6644](https://github.com/n8n-io/n8n/issues/6644)) ([26046f6](https://github.com/n8n-io/n8n/commit/26046f6fe8df9e6fe799f6253d086142f6ce7e53)) +* **Read PDF Node:** Replace pdf-parse with pdfjs, and add support for streaming and encrypted PDFs ([#6640](https://github.com/n8n-io/n8n/issues/6640)) ([0a31b8e](https://github.com/n8n-io/n8n/commit/0a31b8e2b4aab8d74d80f76598900109fe19a0e8)) +* **Rundeck Node:** Add support for node filters ([#5633](https://github.com/n8n-io/n8n/issues/5633)) ([1f70f49](https://github.com/n8n-io/n8n/commit/1f70f49ce5784baba7fd779b23209bae4f6b039a)) +* **Slack Node:** Add option to include link to workflow in Slack node ([#6611](https://github.com/n8n-io/n8n/issues/6611)) ([aa53c46](https://github.com/n8n-io/n8n/commit/aa53c46367480e31642e807ad1abf149fd13eb28)) +* **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)) +* **Telegram Node:** Add support for sending messages to forum topics ([#5746](https://github.com/n8n-io/n8n/issues/5746)) ([e6a81f0](https://github.com/n8n-io/n8n/commit/e6a81f0008fddfcfd5f1102c8e6e58650020d930)) +* **Twitter Node:** Node overhaul ([#4788](https://github.com/n8n-io/n8n/issues/4788)) ([42721db](https://github.com/n8n-io/n8n/commit/42721dba80077fb796086a2bf0ecce256bf3a50f)) + + +### Reverts + +* Revert "test(editor): Add canvas actions E2E tests" (#6736) ([ed09e9c](https://github.com/n8n-io/n8n/commit/ed09e9c695109a715a4c8d47338bbb0b794eb009)), closes [#6736](https://github.com/n8n-io/n8n/issues/6736) [#6723](https://github.com/n8n-io/n8n/issues/6723) + + + +## [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) + + +### Bug Fixes + +* **core:** Fix OAuth2 callback for `grantType=clientCredentials` ([#6500](https://github.com/n8n-io/n8n/issues/6500)) ([25b9216](https://github.com/n8n-io/n8n/commit/25b92169aefc47dc028a11dc008abb4a36d037d3)) +* **core:** Fix pairedItem for alwaysOutputData & multi identical resolve ([#6405](https://github.com/n8n-io/n8n/issues/6405)) ([4b0e0b7](https://github.com/n8n-io/n8n/commit/4b0e0b797071cb20bd2b042eb0d8591fe801ae76)) +* **core:** Fix the url sent in the password-reset emails ([#6466](https://github.com/n8n-io/n8n/issues/6466)) ([9978e27](https://github.com/n8n-io/n8n/commit/9978e2760bc0b8f8c7e5c1ef6610670611d8efe9)) +* **core:** Improve the error returned to users on SSL issues ([#6494](https://github.com/n8n-io/n8n/issues/6494)) ([1b084bc](https://github.com/n8n-io/n8n/commit/1b084bc56b90a25b399c084b69efdb48ff01dfb5)) +* **core:** Use correct scopes-separator when generating authorization urls ([#6502](https://github.com/n8n-io/n8n/issues/6502)) ([5bf83f8](https://github.com/n8n-io/n8n/commit/5bf83f8bf6889e0154000ad774f292e98b87729d)) +* **editor:** Fix DNV header disappearing when scrolling the code editor content ([#6459](https://github.com/n8n-io/n8n/issues/6459)) ([ad9fd12](https://github.com/n8n-io/n8n/commit/ad9fd12615a7050c6fec0caf041e49c972693d1f)) +* **editor:** Fix resource mapper dropdown width and disabled styling ([#6493](https://github.com/n8n-io/n8n/issues/6493)) ([da330f0](https://github.com/n8n-io/n8n/commit/da330f0648b85c39e89ca2eeaba94f9b2c3a1b13)) +* **editor:** Remove `$if`, `$min` and `$max` from code node autocomplete ([#6460](https://github.com/n8n-io/n8n/issues/6460)) ([16f707d](https://github.com/n8n-io/n8n/commit/16f707d5c479c72f87c129ec870fd00c329a9ade)) +* **editor:** Show confirm on pull only when http response status is 409 ([#6451](https://github.com/n8n-io/n8n/issues/6451)) ([5819be5](https://github.com/n8n-io/n8n/commit/5819be5ced53adc643b2bb18dbd1be0096a05395)) +* **editor:** Show execution error as message ([#6431](https://github.com/n8n-io/n8n/issues/6431)) ([d3b78e2](https://github.com/n8n-io/n8n/commit/d3b78e291ece5355b0829c935a3142b6078287c9)) +* **editor:** Update data pinning tooltip to match current behaviour ([#6436](https://github.com/n8n-io/n8n/issues/6436)) ([ee10ac8](https://github.com/n8n-io/n8n/commit/ee10ac8da9ad6533995361f51cab97da35501855)) +* **editor:** Update git repo URL validation to prevent using https protocol ([#6475](https://github.com/n8n-io/n8n/issues/6475)) ([8b50625](https://github.com/n8n-io/n8n/commit/8b50625fb15fc86840485e167627a2ab1802d512)) +* **editor:** Update version control docs links ([#6440](https://github.com/n8n-io/n8n/issues/6440)) ([da105f4](https://github.com/n8n-io/n8n/commit/da105f468bd69b3d572f422d7270204e5823200e)) +* **Google Sheets Node:** Return empty response if no rows updated ([#6407](https://github.com/n8n-io/n8n/issues/6407)) ([32fb419](https://github.com/n8n-io/n8n/commit/32fb4190111c60c8bd05d47d5bace2f9043d1f06)) +* **HTML Node:** Prevent XSS in execution-data preview ([#6432](https://github.com/n8n-io/n8n/issues/6432)) ([16e0df5](https://github.com/n8n-io/n8n/commit/16e0df553c4e068768bf7dd7bbac1d7c2b88fec2)) +* **HTTP Request Node:** "Ignore SSL issues" should also ignore legacy renegotiation issues ([#6492](https://github.com/n8n-io/n8n/issues/6492)) ([7a95e08](https://github.com/n8n-io/n8n/commit/7a95e08bfd04cd53a9d531fdb715f289bfdd64b6)) +* **LinkedIn Node:** Remove unsupported description from image posts ([#6446](https://github.com/n8n-io/n8n/issues/6446)) ([529f0e4](https://github.com/n8n-io/n8n/commit/529f0e499670450f8cb0dda539176d1b07c39190)) +* Make MySQL migration for nano id change compatible with version 5.7 ([#6498](https://github.com/n8n-io/n8n/issues/6498)) ([044c710](https://github.com/n8n-io/n8n/commit/044c710a8ecca3d705ebfb53cef204482428fbec)) +* **Split In Batches Node:** Add "done" context to allow simple reset ([#6437](https://github.com/n8n-io/n8n/issues/6437)) ([ffd13f4](https://github.com/n8n-io/n8n/commit/ffd13f4541794832792ecf33e24d5dc4960ff005)) +* **Zendesk Node:** Fix issue with group assignment not working ([#6501](https://github.com/n8n-io/n8n/issues/6501)) ([772ed7f](https://github.com/n8n-io/n8n/commit/772ed7ff10f6071816ad8cb4543ae9bcaf3813d3)) +* **Zulip Node:** Remove trailing slash from the url ([#6427](https://github.com/n8n-io/n8n/issues/6427)) ([cc1b249](https://github.com/n8n-io/n8n/commit/cc1b249d58618c11bb22ae4458aaf22ef77e6493)) + + +### Features + +* Add support for large files with declarative nodes ([#6461](https://github.com/n8n-io/n8n/issues/6461)) ([e0f109f](https://github.com/n8n-io/n8n/commit/e0f109fa7ee3b1502d68f648504e12a61bff7c93)) +* **AwsS3 Node:** Small overhaul of the node with multipart uploading ([#6017](https://github.com/n8n-io/n8n/issues/6017)) ([109442f](https://github.com/n8n-io/n8n/commit/109442f38f9868d51d85ba4f88d185910f4f2688)) +* **core:** Add GET /users endpoints to public API ([#6360](https://github.com/n8n-io/n8n/issues/6360)) ([6ab3502](https://github.com/n8n-io/n8n/commit/6ab350209d0fa7ee37c9e4425d987517aeba3d98)) +* **core:** Add PKCE for OAuth2 ([#6324](https://github.com/n8n-io/n8n/issues/6324)) ([fc7261a](https://github.com/n8n-io/n8n/commit/fc7261aca6485141fca95b6eccffe1f1a9a8c0c4)) +* **DebugHelper Node:** Fix and include in main app ([#6406](https://github.com/n8n-io/n8n/issues/6406)) ([18f5884](https://github.com/n8n-io/n8n/commit/18f588444f7f126ec1c4867d2ac1f2d3cbc99b88)) +* **Gmail Node:** Add reply to email ([#6453](https://github.com/n8n-io/n8n/issues/6453)) ([fddc69e](https://github.com/n8n-io/n8n/commit/fddc69ee2c2324aff783172c959af9e40ad89696)) +* **Item Lists Node:** Improvements ([#6190](https://github.com/n8n-io/n8n/issues/6190)) ([1dbca44](https://github.com/n8n-io/n8n/commit/1dbca4402579ce1e323ab30c5caebef247a19c8d)) +* Migrate integer primary keys to nanoids ([#6345](https://github.com/n8n-io/n8n/issues/6345)) ([c3ba012](https://github.com/n8n-io/n8n/commit/c3ba0123ad0913140707dbf56fafa1d4dd0f3de3)), closes [#6323](https://github.com/n8n-io/n8n/issues/6323) +* **Stripe Trigger Node:** Add action required trigger for payment intents ([#6490](https://github.com/n8n-io/n8n/issues/6490)) ([f2154fb](https://github.com/n8n-io/n8n/commit/f2154fba60cbbaa9b6c9e72523801573fe3b6baf)) +* **Webhook Node:** Stream binary response in `lastNode.firstEntryBinary` mode ([#6463](https://github.com/n8n-io/n8n/issues/6463)) ([6ccab3e](https://github.com/n8n-io/n8n/commit/6ccab3eaaa883e17d00607e180e14edf9ce33688)) + + + +# [0.233.0](https://github.com/n8n-io/n8n/compare/n8n@0.232.0...n8n@0.233.0) (2023-06-14) + + +### Bug Fixes + +* **core:** Allow all executions to be stopped ([#6386](https://github.com/n8n-io/n8n/issues/6386)) ([cc44af9](https://github.com/n8n-io/n8n/commit/cc44af9243457a5dfa904c205ad5428f793104b4)) +* **core:** Prevent arbitrary code execution via expressions ([#6420](https://github.com/n8n-io/n8n/issues/6420)) ([da7ae2b](https://github.com/n8n-io/n8n/commit/da7ae2beef0375aa914aee251aeff5f7ff334b34)) +* **editor:** Hide version control main menu component if no feature flag ([#6419](https://github.com/n8n-io/n8n/issues/6419)) ([75c0ab0](https://github.com/n8n-io/n8n/commit/75c0ab03f8379bb5426cef87045b67b458c97d70)) +* **LinkedIn Node:** Fix issue with posting as user or organization ([#6414](https://github.com/n8n-io/n8n/issues/6414)) ([d041602](https://github.com/n8n-io/n8n/commit/d041602754a694e67e81c1bbff87c13bf91bdd5d)) +* **Schedule Trigger Node:** Follow the correct Unix cron format for month and days of the week ([#6401](https://github.com/n8n-io/n8n/issues/6401)) ([2aef9de](https://github.com/n8n-io/n8n/commit/2aef9de14830c56199fd19bc09382424c55a2b9b)) + + + +# [0.232.0](https://github.com/n8n-io/n8n/compare/n8n@0.231.0...n8n@0.232.0) (2023-06-07) + + +### Bug Fixes + +* **core:** RMC boolean value fix ([#6397](https://github.com/n8n-io/n8n/issues/6397)) ([28bb797](https://github.com/n8n-io/n8n/commit/28bb797bb0ea59b66a7641fc116f47c25564c21a)) +* **Date & Time Node:** Reset responseData at end of loop ([#6385](https://github.com/n8n-io/n8n/issues/6385)) ([eaa8648](https://github.com/n8n-io/n8n/commit/eaa8648f2bf61074eae6dcd7355f8f107a31388e)) +* **editor:** Add button to refresh branches ([#6387](https://github.com/n8n-io/n8n/issues/6387)) ([ce57816](https://github.com/n8n-io/n8n/commit/ce578162f4e44a6cc1774ab217967110b254ab3f)) +* **editor:** Add secondary icon to menu items ([#6351](https://github.com/n8n-io/n8n/issues/6351)) ([3dd2601](https://github.com/n8n-io/n8n/commit/3dd260168eb627fd7fbed740bc97fa7f6289628f)) +* **editor:** Add Set up version control CTA ([#6356](https://github.com/n8n-io/n8n/issues/6356)) ([e72521d](https://github.com/n8n-io/n8n/commit/e72521d5ec7a5e57dc311defb70f1fe19054b0f0)) +* **editor:** Adding branch color ([#6380](https://github.com/n8n-io/n8n/issues/6380)) ([dba3f44](https://github.com/n8n-io/n8n/commit/dba3f44bc00de68113cc98db9afc6267f56ec04c)) +* **editor:** Fix an issue with connections breaking during renaming ([#6358](https://github.com/n8n-io/n8n/issues/6358)) ([0f2bc6b](https://github.com/n8n-io/n8n/commit/0f2bc6b73711597fdf008ee54665d9bed82a1a9e)) +* **editor:** Fix hard-coded parameter names for code editors ([#6372](https://github.com/n8n-io/n8n/issues/6372)) ([f61b776](https://github.com/n8n-io/n8n/commit/f61b776beac961fa58c6c69371c69ae1e74ef83e)) +* **editor:** Fix typing `$` in inline expression field reloading node parameters form ([#6374](https://github.com/n8n-io/n8n/issues/6374)) ([4c0d4eb](https://github.com/n8n-io/n8n/commit/4c0d4ebd9917e52512e85a5cad2c93b554e0e212)) +* **editor:** Pin all data regardless of pagination ([#6346](https://github.com/n8n-io/n8n/issues/6346)) ([f88029f](https://github.com/n8n-io/n8n/commit/f88029f308356c1c8271d7345ecbbd6e91c41b3d)) +* **editor:** Remove explicit parameter name scanning for code editors ([#6390](https://github.com/n8n-io/n8n/issues/6390)) ([97295f6](https://github.com/n8n-io/n8n/commit/97295f67f0f8509ac6ba0d4ce38ce12582dff074)) +* **editor:** Remove root level tag selector from css module to avoid making it a global style ([#6392](https://github.com/n8n-io/n8n/issues/6392)) ([cc37f21](https://github.com/n8n-io/n8n/commit/cc37f21aa27f3536f2043b5ff5da944388ac5504)) +* **editor:** Update version control setup CTA tooltip ([#6393](https://github.com/n8n-io/n8n/issues/6393)) ([385b3e8](https://github.com/n8n-io/n8n/commit/385b3e871a9307c36428f8239a5db318d71948c1)) +* Improve executions list polling performance ([#6355](https://github.com/n8n-io/n8n/issues/6355)) ([b5cabfe](https://github.com/n8n-io/n8n/commit/b5cabfef54e186f59580112a90566099bb79305e)) +* **Ldap Node:** Add DN field to update operation ([#6371](https://github.com/n8n-io/n8n/issues/6371)) ([9396e7e](https://github.com/n8n-io/n8n/commit/9396e7eb585ab9d6fda742b0d234c4262570af93)) +* Show actual execution data for production executions even if pin data exists ([#6302](https://github.com/n8n-io/n8n/issues/6302)) ([4eb8437](https://github.com/n8n-io/n8n/commit/4eb8437196a298a64f039aff51ba030dc21abb08)) + + +### Features + +* **Crypto Node:** Add support for hash and hmac on binary data ([#6359](https://github.com/n8n-io/n8n/issues/6359)) ([406a405](https://github.com/n8n-io/n8n/commit/406a405dd153833057286a27d04278ef71ceef3d)) +* **editor:** Make WF name a link on /executions ([#6354](https://github.com/n8n-io/n8n/issues/6354)) ([6ddf161](https://github.com/n8n-io/n8n/commit/6ddf16128b4ab47db716eeab89f7526558073f56)) +* New trigger PostgreSQL ([#5495](https://github.com/n8n-io/n8n/issues/5495)) ([4488f93](https://github.com/n8n-io/n8n/commit/4488f93c39b0ec0b4a0eff98391b46db6a2eed78)) +* Version control mvp ([#6271](https://github.com/n8n-io/n8n/issues/6271)) ([1b32141](https://github.com/n8n-io/n8n/commit/1b321416c0ba5371e0016398ae660ce298b8cdd6)) + + +# [0.231.0](https://github.com/n8n-io/n8n/compare/n8n@0.230.0...n8n@0.231.0) (2023-05-31) + + +### Bug Fixes + +* **Code Node:** Fix `item` and `items` alias regression ([#6331](https://github.com/n8n-io/n8n/issues/6331)) ([54e3838](https://github.com/n8n-io/n8n/commit/54e3838daed1f0931a04ba76cfd1ea7519c0e382)) +* **Code Node:** Update vm2 to address CVE-2023-32313 ([#6318](https://github.com/n8n-io/n8n/issues/6318)) ([bcbec52](https://github.com/n8n-io/n8n/commit/bcbec52552d52b0509659cab13112e1377a256b3)) +* **core:** Optimize getSharedWorkflowIds query ([#6314](https://github.com/n8n-io/n8n/issues/6314)) ([0631f69](https://github.com/n8n-io/n8n/commit/0631f69d98e5420faebba1a54d9ad47a2664d110)) +* **core:** Prevent prototype pollution on injectable services ([#6309](https://github.com/n8n-io/n8n/issues/6309)) ([d94c20a](https://github.com/n8n-io/n8n/commit/d94c20ada543767f700475b40ef7174c433c26c5)) +* **editor:** Fix locale plularisation if count is 0 ([#6312](https://github.com/n8n-io/n8n/issues/6312)) ([0d88bd7](https://github.com/n8n-io/n8n/commit/0d88bd7c1ae95cf077c2fa231d942204ff3b8f68)) +* **editor:** Fix Luxon date parsing of ExecutionsUsage component ([#6333](https://github.com/n8n-io/n8n/issues/6333)) ([8f0ff46](https://github.com/n8n-io/n8n/commit/8f0ff460b11999f4d78f8313910358aa87311713)) +* **editor:** Update SSO settings styles ([#6342](https://github.com/n8n-io/n8n/issues/6342)) ([5ae1124](https://github.com/n8n-io/n8n/commit/5ae1124106e7597d0943c371eae6aba6c105fd6b)) +* **Execute Command Node:** Block executions when `command` is empty ([#6308](https://github.com/n8n-io/n8n/issues/6308)) ([011d577](https://github.com/n8n-io/n8n/commit/011d5778b15232cff94a321dfee18c3d7489f93d)) +* Show `Ask AI` only on Code Node ([#6336](https://github.com/n8n-io/n8n/issues/6336)) ([da856d1](https://github.com/n8n-io/n8n/commit/da856d1c6593b43e1ce8d1becb1464c19c908e78)) + + +### Features + +* Add manual login option and password reset link for SSO ([#6328](https://github.com/n8n-io/n8n/issues/6328)) ([77e3f15](https://github.com/n8n-io/n8n/commit/77e3f1551dd7473a69f8833be5678d98964142e1)) +* **core:** Add metadata (customdata) to event log ([#6334](https://github.com/n8n-io/n8n/issues/6334)) ([792b1c1](https://github.com/n8n-io/n8n/commit/792b1c1ffb0eb279bc3451787891ca3835f59d9f)) +* **editor:** Implement Resource Mapper component ([#6207](https://github.com/n8n-io/n8n/issues/6207)) ([04cfa54](https://github.com/n8n-io/n8n/commit/04cfa548af3c7a25f1f0a36ddfb1de6a9e3f2169)), closes [#5752](https://github.com/n8n-io/n8n/issues/5752) [#5814](https://github.com/n8n-io/n8n/issues/5814) + + + +# [0.230.0](https://github.com/n8n-io/n8n/compare/n8n@0.229.0...n8n@0.230.0) (2023-05-24) + + +### Bug Fixes + +* **core:** Optimize SharedWorkflow queries ([#6297](https://github.com/n8n-io/n8n/issues/6297)) ([ed7f3b8](https://github.com/n8n-io/n8n/commit/ed7f3b845fe9a7aa0f6e1ff57ae9197057cd8aa1)) +* **core:** Prevent app crashes because of unhandled promises in poll and trigger nodes ([#6278](https://github.com/n8n-io/n8n/issues/6278)) ([3750605](https://github.com/n8n-io/n8n/commit/37506050c3f49cececa9da3cf7d420b367f2e055)) +* **editor:** Fix canvas loading when page gets restored from bfcache ([#6304](https://github.com/n8n-io/n8n/issues/6304)) ([11477f0](https://github.com/n8n-io/n8n/commit/11477f0a20f82b02d763dbf53e29083dd1e95e87)) +* **editor:** Fix design system button with icon vertical alignment ([#6284](https://github.com/n8n-io/n8n/issues/6284)) ([fc580f7](https://github.com/n8n-io/n8n/commit/fc580f7ee80b48a62fc2bebaec9902cd15944ad7)) +* **editor:** Fix inverted checks on modal confirmation results ([#6285](https://github.com/n8n-io/n8n/issues/6285)) ([5d2f474](https://github.com/n8n-io/n8n/commit/5d2f4746ea5da977a6532db90c624ded1520ec3e)) +* **ERPNext Node:** Fix issue with credential test and add frappe cloud url ([#6283](https://github.com/n8n-io/n8n/issues/6283)) ([2a2b645](https://github.com/n8n-io/n8n/commit/2a2b6452dc326d8979bed0cf47fc54becd746619)) +* **Google Calendar Node:** All day option fix ([#6274](https://github.com/n8n-io/n8n/issues/6274)) ([5bef91e](https://github.com/n8n-io/n8n/commit/5bef91e3c84a15a30a893ce3b321b7a8ea926963)) +* Initialize license in queue mode correctly ([#6301](https://github.com/n8n-io/n8n/issues/6301)) ([42c79cd](https://github.com/n8n-io/n8n/commit/42c79cd6f1e495e60a9f038403d9a8a761318f52)) +* **OpenAI Node:** Descriptive errors ([#6270](https://github.com/n8n-io/n8n/issues/6270)) ([8fdfa3b](https://github.com/n8n-io/n8n/commit/8fdfa3b6b83c6a008d1be778dadcc2172e8a708d)) +* Prevent removing manual executions when setting says to save ([#6300](https://github.com/n8n-io/n8n/issues/6300)) ([55b755c](https://github.com/n8n-io/n8n/commit/55b755cb44a3bf9fc9d41af37b7818d3626baf0d)) +* **SSH Node:** Private key field as password and credential test ([#6298](https://github.com/n8n-io/n8n/issues/6298)) ([d5c7e6f](https://github.com/n8n-io/n8n/commit/d5c7e6f2cff63337948122a11c3707316c937b9f)) +* **SSH Node:** Replace ~ with /home/username ([#6269](https://github.com/n8n-io/n8n/issues/6269)) ([4219490](https://github.com/n8n-io/n8n/commit/421949067b47a25e859fbd45364ba657e7286599)) +* **Strapi Node:** Strapi credentials notice ([#6289](https://github.com/n8n-io/n8n/issues/6289)) ([bbe6d4c](https://github.com/n8n-io/n8n/commit/bbe6d4c4dbda0b2cba447cf1b0aa4a7f808096fb)) +* **Strava Trigger Node:** Fix issue with delete events failing to display data ([#6277](https://github.com/n8n-io/n8n/issues/6277)) ([8a8fed0](https://github.com/n8n-io/n8n/commit/8a8fed08407f20791ae01ab83e1ce3d99715dc5c)) +* **Wekan Node:** Handle response correctly ([#6296](https://github.com/n8n-io/n8n/issues/6296)) ([4d9c8b0](https://github.com/n8n-io/n8n/commit/4d9c8b07a93ea4f2e5ad913358bafb682f6f5506)) + + +### Features + +* Add SSO SAML metadataUrl support and various improvements ([#6139](https://github.com/n8n-io/n8n/issues/6139)) ([e3a53fd](https://github.com/n8n-io/n8n/commit/e3a53fd19d8c258a08baab9c090968104327a13b)) +* **core:** Remove all floating promises. Enforce `@typescript-eslint/no-floating-promises` ([#6281](https://github.com/n8n-io/n8n/issues/6281)) ([e046f65](https://github.com/n8n-io/n8n/commit/e046f656fefe951af71ab031a440729a5eb1c7cb)) +* **core:** Replace client-oauth2 with an in-repo package ([#6266](https://github.com/n8n-io/n8n/issues/6266)) ([a1b1f24](https://github.com/n8n-io/n8n/commit/a1b1f24ddfd4da36f8dd04e34e2675a3993755ca)) +* **Execution Data Node:** New node ([#6247](https://github.com/n8n-io/n8n/issues/6247)) ([3f7c4f0](https://github.com/n8n-io/n8n/commit/3f7c4f0ad485a0a4049f371723b01847077f7ccd)) +* **Gotify Node:** Add support for self signed certificates ([#6053](https://github.com/n8n-io/n8n/issues/6053)) ([401cffd](https://github.com/n8n-io/n8n/commit/401cffde57aa153bc2d1589bc8d11d7951f2ade1)) +* **Ldap Node:** Add LDAP node ([#4783](https://github.com/n8n-io/n8n/issues/4783)) ([ec393bc](https://github.com/n8n-io/n8n/commit/ec393bc041e9e7590e7b0a2821976f104f5c23bb)) +* **LoneScale Node:** Add LoneScale node and Trigger node ([#5146](https://github.com/n8n-io/n8n/issues/5146)) ([4b85433](https://github.com/n8n-io/n8n/commit/4b854333d49c661fe11f19a176a147dbf28e697f)) +* **RabbitMQ Node:** Add mode for acknowledging and deleting from queue later in workflow ([#6225](https://github.com/n8n-io/n8n/issues/6225)) ([f5950b2](https://github.com/n8n-io/n8n/commit/f5950b201c6ff412b9a304052f05eb2c3b8a7c51)) +* **Send Email Node:** Add content-id for email attachments ([#3632](https://github.com/n8n-io/n8n/issues/3632)) ([8fe8aad](https://github.com/n8n-io/n8n/commit/8fe8aad6a77bbec7a26c87f8bad9593852e8d464)) +* **SSH Node:** Credentials test ([#6279](https://github.com/n8n-io/n8n/issues/6279)) ([3569d53](https://github.com/n8n-io/n8n/commit/3569d53917b41b758a96293a2b33a06cbf2c0a70)) + + + +# [0.229.0](https://github.com/n8n-io/n8n/compare/n8n@0.228.0...n8n@0.229.0) (2023-05-17) + + +### Bug Fixes + +* **Code Node:** Restore help text ([#6231](https://github.com/n8n-io/n8n/issues/6231)) ([e72d564](https://github.com/n8n-io/n8n/commit/e72d564bf84460ce085f022f186e8101524f2e7b)) +* **core:** Make sure that special polling parameters are available on community nodes as well ([#6230](https://github.com/n8n-io/n8n/issues/6230)) ([9db49d0](https://github.com/n8n-io/n8n/commit/9db49d0c18f007b974594b918728e430ec510f54)) +* Remove workflow execution credential error message when instance owner ([#6116](https://github.com/n8n-io/n8n/issues/6116)) ([e81a964](https://github.com/n8n-io/n8n/commit/e81a96483af6e0d0a3cf62481f5e9a37d6a62b6e)) + + +### Features + +* **core:** Reduce the number of events sent to Sentry ([#6235](https://github.com/n8n-io/n8n/issues/6235)) ([a4c0cc9](https://github.com/n8n-io/n8n/commit/a4c0cc9b5c56639067918f1bad1baf4eb201e48b)) +* **core:** Return OAuth2 error body if available ([#5794](https://github.com/n8n-io/n8n/issues/5794)) ([79d0a0f](https://github.com/n8n-io/n8n/commit/79d0a0f2470905f98b71fb5c8ca007244004f99a)) +* **editor:** Add cloud ExecutionsUsage and API blocking using licenses ([#6159](https://github.com/n8n-io/n8n/issues/6159)) ([cd7c312](https://github.com/n8n-io/n8n/commit/cd7c312fbd172b5d3c8bbeaf775f7b5bb4611aa5)), closes [#6187](https://github.com/n8n-io/n8n/issues/6187) +* **editor:** Add color picker design system component ([#6179](https://github.com/n8n-io/n8n/issues/6179)) ([823e885](https://github.com/n8n-io/n8n/commit/823e88500c35508d6322242bca5749d711fb2b31)) +* **editor:** Drop support for legacy browsers that do not have native ESM support ([#6239](https://github.com/n8n-io/n8n/issues/6239)) ([9182d15](https://github.com/n8n-io/n8n/commit/9182d1558a1f98e0ea1b9fbaddabf7c8b4836e94)) +* **editor:** Updating node reference pattern in expression editor ([#6228](https://github.com/n8n-io/n8n/issues/6228)) ([13bcec1](https://github.com/n8n-io/n8n/commit/13bcec1661e1da736ff9c93869bc49a3038bdf1b)) +* **editor:** Version Control settings update (WIP) ([#6233](https://github.com/n8n-io/n8n/issues/6233)) ([0666377](https://github.com/n8n-io/n8n/commit/0666377ef8074ad093391d41e08e194704a25dbd)) +* **Google Ads Node:** Update to support v13 ([#6212](https://github.com/n8n-io/n8n/issues/6212)) ([bd1bffc](https://github.com/n8n-io/n8n/commit/bd1bffcd536eedde8582a354c11349dbddd2b9a2)) +* **Respond to Webhook Node:** Move from Binary Buffer to Binary streaming ([#5613](https://github.com/n8n-io/n8n/issues/5613)) ([8ae2d80](https://github.com/n8n-io/n8n/commit/8ae2d801d8e2c881fcff5f7cf4fcb699c10b2be2)) + + + +# [0.228.0](https://github.com/n8n-io/n8n/compare/n8n@0.227.0...n8n@0.228.0) (2023-05-11) + + +### Bug Fixes + +* **AWS Rekognition Node:** Fix all different action type ([#6136](https://github.com/n8n-io/n8n/issues/6136)) ([22b82a4](https://github.com/n8n-io/n8n/commit/22b82a43a24bb8415eb8f4a941b05647ef8904c2)) +* **core:** Ensure DB repositories are initialized before the DB migrations are run ([#6220](https://github.com/n8n-io/n8n/issues/6220)) ([500c0eb](https://github.com/n8n-io/n8n/commit/500c0ebce34dc2d21a531176dee965a70abec5f8)) +* **core:** Move nodeExecute InternalHook calls to hookFunctionsSave ([#6193](https://github.com/n8n-io/n8n/issues/6193)) ([f00b2ae](https://github.com/n8n-io/n8n/commit/f00b2ae3eaa225e483abc8defdc58d27b7d2c5e8)) +* Correctly save executions that failed when polling as error instead of new ([#6192](https://github.com/n8n-io/n8n/issues/6192)) ([06948b5](https://github.com/n8n-io/n8n/commit/06948b5ba5775b4f03d1ce46d57a461014317d51)) +* **editor:** Add loading skeletons to Executions list page ([#6184](https://github.com/n8n-io/n8n/issues/6184)) ([eae3a55](https://github.com/n8n-io/n8n/commit/eae3a55cc6b87bc8998d18d3d32d0d03013996b1)) +* **editor:** Display SSO entry in Settings on Cloud ([#6181](https://github.com/n8n-io/n8n/issues/6181)) ([b0a1899](https://github.com/n8n-io/n8n/commit/b0a1899e7141e5726d4fcbca6bba47e8e4b5cef7)) +* **editor:** Fix polling trigger check for `runData` ([#6130](https://github.com/n8n-io/n8n/issues/6130)) ([80831cd](https://github.com/n8n-io/n8n/commit/80831cd7c60f77557c37317600690a289d966448)) +* **editor:** Fix viewing and downloading of binary data ([#6218](https://github.com/n8n-io/n8n/issues/6218)) ([b9779c3](https://github.com/n8n-io/n8n/commit/b9779c32936c8b5e2385226ecab1025ff88d7044)) +* **editor:** Flag issues only on workflow activation ([#6127](https://github.com/n8n-io/n8n/issues/6127)) ([1b49c17](https://github.com/n8n-io/n8n/commit/1b49c17f38e97547430c407e21284b0c508469fa)) +* **editor:** Remove duplicate mapping of `item.json` key in data pinning ([#6135](https://github.com/n8n-io/n8n/issues/6135)) ([91fee0c](https://github.com/n8n-io/n8n/commit/91fee0ca667f233c0bde0dc6089bbed7d7db5b5f)) +* **editor:** Show the correct actions count in the nodes list ([#6183](https://github.com/n8n-io/n8n/issues/6183)) ([751e132](https://github.com/n8n-io/n8n/commit/751e132968470144cfca1c1b7ca22caf2c4c1de4)) +* **editor:** Update and add design system checkbox component to Editor ([#6178](https://github.com/n8n-io/n8n/issues/6178)) ([13c143e](https://github.com/n8n-io/n8n/commit/13c143eb6df41457fbd361674f2063b983a0e077)) +* **editor:** Update and fix storybook (was failing to run in local dev mode) ([#6180](https://github.com/n8n-io/n8n/issues/6180)) ([1e6a75f](https://github.com/n8n-io/n8n/commit/1e6a75f3416cdfcce1299dc9d242e23d0ea97ad3)) +* **FTP Node:** Use filename instead of remote filepath for downloaded binary data ([#6170](https://github.com/n8n-io/n8n/issues/6170)) ([be08933](https://github.com/n8n-io/n8n/commit/be089331b372e029ab5516b91e63a2d5d9033719)) +* **Google Sheets Node:** Upgrade xlsx to address CVE-2023-30533 ([#6172](https://github.com/n8n-io/n8n/issues/6172)) ([45dc985](https://github.com/n8n-io/n8n/commit/45dc985af742b049dc5673cf972da704d1f1f220)) +* **HTTP Request Node:** Correctly doesn't redirect on non GET method ([#6132](https://github.com/n8n-io/n8n/issues/6132)) ([3f5c606](https://github.com/n8n-io/n8n/commit/3f5c6062542f3b2b8a02cf0820e03da3f01d8bf2)) +* **MySQL Node:** Node should return date types as strings ([#6169](https://github.com/n8n-io/n8n/issues/6169)) ([5d77ec7](https://github.com/n8n-io/n8n/commit/5d77ec76e3c47fe9f9d7f31fe6c03827685ec576)) +* **Postgres Node:** Always return TIMESTAMP and TIMESTAMPZ as ISO string ([#6145](https://github.com/n8n-io/n8n/issues/6145)) ([0eb4d9f](https://github.com/n8n-io/n8n/commit/0eb4d9fc16aad1d1a350ba074c4b86712fbd90a1)) +* Prevent overflow when rendering expression hints ([#6214](https://github.com/n8n-io/n8n/issues/6214)) ([c717771](https://github.com/n8n-io/n8n/commit/c7177719e5f60813f4d15f7f97f1b4f253e29b07)) +* Prevent unnecessary error messages also for data loaded flag ([#6201](https://github.com/n8n-io/n8n/issues/6201)) ([d5e62ff](https://github.com/n8n-io/n8n/commit/d5e62ff096ddefd52dae742166fe92ceef17ded6)) + + +### Features + +* **Airtable Node:** Access token support ([#6160](https://github.com/n8n-io/n8n/issues/6160)) ([f9fd820](https://github.com/n8n-io/n8n/commit/f9fd82040ac09914a03e5b9f8f84fce5f6a99835)) +* **Code Node:** Add Python support ([#4295](https://github.com/n8n-io/n8n/issues/4295)) ([35c8510](https://github.com/n8n-io/n8n/commit/35c8510ab6d607fe59056a4aa1d8d148e194d12c)) +* **core:** Improve health check ([#6205](https://github.com/n8n-io/n8n/issues/6205)) ([9e7b9fb](https://github.com/n8n-io/n8n/commit/9e7b9fb443046c73135efb70d0d1894207125f66)) +* Create NPM node ([#6177](https://github.com/n8n-io/n8n/issues/6177)) ([f3bc6f1](https://github.com/n8n-io/n8n/commit/f3bc6f19b68f6bd4bd99614f60bd6833bd15813f)) +* **Date & Time Node:** Overhaul of the node ([#5904](https://github.com/n8n-io/n8n/issues/5904)) ([7d1d1f7](https://github.com/n8n-io/n8n/commit/7d1d1f7872163cecb468c317670da2d8b89a7651)) +* **HubSpot Node:** Overhaul the HubSpot Node ([#4337](https://github.com/n8n-io/n8n/issues/4337)) ([2913e67](https://github.com/n8n-io/n8n/commit/2913e676e639757cdf1a513ad35a7df0e494fa6f)) +* **JotForm Trigger Node:** Add support for hipaa-api.jotform.com ([#6171](https://github.com/n8n-io/n8n/issues/6171)) ([3074f42](https://github.com/n8n-io/n8n/commit/3074f42b3b98cf0dbdc13ad7b927d6b7fc726fab)) +* **Kafka Trigger Node:** Add non-parallel execution ([#6175](https://github.com/n8n-io/n8n/issues/6175)) ([814ea51](https://github.com/n8n-io/n8n/commit/814ea5185ce82e0a7687b41161602b45f92bee93)) + + + +# [0.227.0](https://github.com/n8n-io/n8n/compare/n8n@0.226.0...n8n@0.227.0) (2023-05-03) + + +### Bug Fixes + +* **AWS S3 Node:** Fix File upload, and add node tests ([#6153](https://github.com/n8n-io/n8n/issues/6153)) ([deb4c04](https://github.com/n8n-io/n8n/commit/deb4c04f346f8e5985b5f6c3f3a3e929fde13e5b)) +* **Compression Node:** Fix issue with decompression failing with uppercase extensions ([#6098](https://github.com/n8n-io/n8n/issues/6098)) ([aa59329](https://github.com/n8n-io/n8n/commit/aa593298365eabd6eb1dda9fe3f06e7eae7c5ea9)) +* **core:** Account for nodes with renamable content ([#6109](https://github.com/n8n-io/n8n/issues/6109)) ([c99f158](https://github.com/n8n-io/n8n/commit/c99f158120b3c1ffca1718be337afc73d6ec9e65)) +* **core:** Assign Unknown Error only if message or description not present in error ([8aedc03](https://github.com/n8n-io/n8n/commit/8aedc03ddad3f83ffd2569be5b61710f27d2f672)) +* **core:** Avoid using `Object.keys` on Buffer and other non-plain objects ([#6131](https://github.com/n8n-io/n8n/issues/6131)) ([a3aba83](https://github.com/n8n-io/n8n/commit/a3aba835a15a8a32acc1e1ff0b972007df2b2b34)) +* **core:** Better error message in Webhook node when using the POST method ([a0dd17e](https://github.com/n8n-io/n8n/commit/a0dd17e1151e668b95dc57367a0b100d00913ea3)) +* **core:** Better errors for common status codes fix ([700cc39](https://github.com/n8n-io/n8n/commit/700cc39cbc7da3c70513ff586dc97319456308ae)) +* **core:** Fix `hasOwnProperty` on augmented objects ([#6124](https://github.com/n8n-io/n8n/issues/6124)) ([206b6b9](https://github.com/n8n-io/n8n/commit/206b6b90b860ceaab58b9bdd5ff20ffc741c13fa)) +* **core:** Fix bug running addUserActivatedColumn migration on MariaDB ([#6157](https://github.com/n8n-io/n8n/issues/6157)) ([570790e](https://github.com/n8n-io/n8n/commit/570790ed0c9521e09b6414bc1da2c596f17ff072)) +* **core:** Fix canceled execution status ([#6142](https://github.com/n8n-io/n8n/issues/6142)) ([839a56a](https://github.com/n8n-io/n8n/commit/839a56a682674baf44d5beececdbe677d18c0d89)) +* **core:** Improve saml endpoints and audit events ([#6107](https://github.com/n8n-io/n8n/issues/6107)) ([c0b1cdd](https://github.com/n8n-io/n8n/commit/c0b1cddc91fe199377c301f02f230827f231ba73)) +* **core:** Remove SAML config metadataUrl if XML metadata is set directly ([#6143](https://github.com/n8n-io/n8n/issues/6143)) ([25fe14b](https://github.com/n8n-io/n8n/commit/25fe14be56482477c00a360914730b25c9028443)) +* **core:** Skip auth for controllers/routes that don't use the `Authorized` decorator, or use `Authorized('none')` ([#6106](https://github.com/n8n-io/n8n/issues/6106)) ([59aee22](https://github.com/n8n-io/n8n/commit/59aee2270bdc0c8360aa534237b7f6015d382346)) +* Correctly allow sharees to test credential when opening the modal ([#6111](https://github.com/n8n-io/n8n/issues/6111)) ([2e73f4a](https://github.com/n8n-io/n8n/commit/2e73f4abd04ba7ab929b0fce57bf12651a0a2e49)) +* **Date & Time Node:** Numbers conversions fix ([14f7114](https://github.com/n8n-io/n8n/commit/14f71146e21026721fc9d5883bb9d73d38afcf8c)) +* **editor:** Change execution list tab loader design ([#6120](https://github.com/n8n-io/n8n/issues/6120)) ([188ef04](https://github.com/n8n-io/n8n/commit/188ef042cd58b9194dadef4cc68deb3510688c26)) +* **editor:** Disable changing of email and pw when SAML login enabled ([#6104](https://github.com/n8n-io/n8n/issues/6104)) ([3e9ecd9](https://github.com/n8n-io/n8n/commit/3e9ecd939742df8d8ced9179aaa26b081139befa)) +* **editor:** Fix `Show details` summary ([#6113](https://github.com/n8n-io/n8n/issues/6113)) ([90a62cc](https://github.com/n8n-io/n8n/commit/90a62ccfb5b4a959d72336d284ad4ac3b17af582)) +* **editor:** Fix copy selection behavior ([#6112](https://github.com/n8n-io/n8n/issues/6112)) ([1607aeb](https://github.com/n8n-io/n8n/commit/1607aeb9f94700793d58604ea4f89c5555d43981)) +* **editor:** Fix cropped off completions docstrings ([#6129](https://github.com/n8n-io/n8n/issues/6129)) ([85e8145](https://github.com/n8n-io/n8n/commit/85e8145439f89e76fe5fe3a659430c03738d6e2b)) +* **editor:** Fix focus jumping when using chrome autofill ([#6140](https://github.com/n8n-io/n8n/issues/6140)) ([c63181b](https://github.com/n8n-io/n8n/commit/c63181b3171040c3dd3051c2a1358aea0af6bae0)) +* **editor:** Fix missing `Stop Listening` button ([#6125](https://github.com/n8n-io/n8n/issues/6125)) ([20a72bb](https://github.com/n8n-io/n8n/commit/20a72bb28b981e9c8d12dd6398d843b39d80daac)) +* **editor:** Fix quote handling on dollar-sign variable completions ([#6128](https://github.com/n8n-io/n8n/issues/6128)) ([51f5990](https://github.com/n8n-io/n8n/commit/51f59905591fa492017fc3ced46601eeca5fb057)) +* **editor:** Fix sidebar button styling ([#6138](https://github.com/n8n-io/n8n/issues/6138)) ([a72a511](https://github.com/n8n-io/n8n/commit/a72a5112f34a0d8ab248f687c74b758c8db6729c)) +* **editor:** Fix unique names for node duplication ([#6134](https://github.com/n8n-io/n8n/issues/6134)) ([71ae6c6](https://github.com/n8n-io/n8n/commit/71ae6c66ef32ba86edf0bb9cdb9f24a6d40ee80c)) +* **editor:** Fix unscrollable node settings ([#6133](https://github.com/n8n-io/n8n/issues/6133)) ([c8ff368](https://github.com/n8n-io/n8n/commit/c8ff368fc7be58e7c42746f7e7a4c5f6a4149d3e)) +* **editor:** Loading state for executions tab ([#6100](https://github.com/n8n-io/n8n/issues/6100)) ([4cbb05b](https://github.com/n8n-io/n8n/commit/4cbb05b0017ffd77eca51fc5b9c5c4868515a60d)) +* **editor:** Remove pagination from binary data output ([#6093](https://github.com/n8n-io/n8n/issues/6093)) ([c6e665a](https://github.com/n8n-io/n8n/commit/c6e665a975958c433d7991c057a3e4be644daff1)) +* **editor:** Restrict `[empty]` in parameter input hint to zero-length string ([#6003](https://github.com/n8n-io/n8n/issues/6003)) ([8862e1e](https://github.com/n8n-io/n8n/commit/8862e1e7df0be62ab3746b70e613ffd2ab26bc4a)) +* **editor:** Show error in RLC if credentials are not set ([#6108](https://github.com/n8n-io/n8n/issues/6108)) ([2c240a0](https://github.com/n8n-io/n8n/commit/2c240a0e4ecd9157dca612d98a8a7c68a65a9909)) +* **HTTP Request Node:** Add description for 'Specify Body' option ([#6114](https://github.com/n8n-io/n8n/issues/6114)) ([af097ae](https://github.com/n8n-io/n8n/commit/af097ae22c7e87918ada2527c6a2fe62cb8f318a)) +* **HTTP Request Node:** Always lowercase headers ([983e6e1](https://github.com/n8n-io/n8n/commit/983e6e124eb9557eec55c5f2e2b834a926243955)) +* **Mattermost Node:** Fix base url trailing slash error ([#6097](https://github.com/n8n-io/n8n/issues/6097)) ([25a386d](https://github.com/n8n-io/n8n/commit/25a386dd70df516090e622d921a79456fc7d16e3)) +* **Merge Node:** Do not error if expected key is missing ([d219af7](https://github.com/n8n-io/n8n/commit/d219af75cf37c603c34b1ca5851cafd4a490889c)) +* Prevent displaying an endless timer in the execution list for finished executions ([#6137](https://github.com/n8n-io/n8n/issues/6137)) ([701105e](https://github.com/n8n-io/n8n/commit/701105edcf5284f276fe146d8267e1a5560ab186)) +* Prevent invocations of 'GET /rest/license' from returning an error when ephemeral licenses are used ([#6154](https://github.com/n8n-io/n8n/issues/6154)) ([a3d26ef](https://github.com/n8n-io/n8n/commit/a3d26eff79013642865fa59078732526850b96a6)) +* **Slack Node:** Restore ability to send text in addition of blocks or attachments ([8669f95](https://github.com/n8n-io/n8n/commit/8669f95736797da4f3efd33468cdeac5d28667b0)) + + +### Features + +* **core:** Add notice to alert users a new version is available ([cb497fb](https://github.com/n8n-io/n8n/commit/cb497fbbecdba670d5121fa2c6eaf7c66d8a8a38)) +* **editor:** Add support for `loadOptionsDependsOn` to RLC ([#6101](https://github.com/n8n-io/n8n/issues/6101)) ([b17d5f9](https://github.com/n8n-io/n8n/commit/b17d5f9aa086bf408e8450244460ada57de0d7c3)) +* **editor:** Add version controls settings (WIP) ([#6036](https://github.com/n8n-io/n8n/issues/6036)) ([0c9ce3a](https://github.com/n8n-io/n8n/commit/0c9ce3a2ec9487b4eb9130651927e91dcd0f85af)) +* **Item Lists Node:** Split out items work on objects as well as arrays ([c65ac03](https://github.com/n8n-io/n8n/commit/c65ac0336821868c289adc55abab40017b1856da)) +* **Microsoft Excel 365 Node:** Overhaul ([5364a2d](https://github.com/n8n-io/n8n/commit/5364a2dff32e05147b8e9dd392038eb36791e5dc)) + + + +## [0.226.2](https://github.com/n8n-io/n8n/compare/n8n@0.226.1...n8n@0.226.2) (2023-05-03) + + +### Bug Fixes + +* **core:** Fix bug running addUserActivatedColumn migration on MariaDB ([#6157](https://github.com/n8n-io/n8n/issues/6157)) ([aa8e96d](https://github.com/n8n-io/n8n/commit/aa8e96dd6b19f105a957da71a5c4d7ab5caecc01)) + + + +## [0.226.1](https://github.com/n8n-io/n8n/compare/n8n@0.226.0...n8n@0.226.1) (2023-05-02) + + +### Bug Fixes + +* **Compression Node:** Fix issue with decompression failing with uppercase extensions ([#6098](https://github.com/n8n-io/n8n/issues/6098)) ([7136500](https://github.com/n8n-io/n8n/commit/71365002daa71c5fa5e68a5bb373ee200a05b7b9)) +* **core:** Account for nodes with renamable content ([#6109](https://github.com/n8n-io/n8n/issues/6109)) ([b561d46](https://github.com/n8n-io/n8n/commit/b561d463265831f3cd370ec99982847f2bddca41)) +* **core:** Fix `hasOwnProperty` on augmented objects ([#6124](https://github.com/n8n-io/n8n/issues/6124)) ([2f015c0](https://github.com/n8n-io/n8n/commit/2f015c0f153785384b009e71bf4994e18b5d06b8)) +* **core:** Fix canceled execution status ([#6142](https://github.com/n8n-io/n8n/issues/6142)) ([1796101](https://github.com/n8n-io/n8n/commit/1796101fed03d04906f2341a0488b60b7b5bf71c)) +* **core:** Skip auth for controllers/routes that don't use the `Authorized` decorator, or use `Authorized('none')` ([#6106](https://github.com/n8n-io/n8n/issues/6106)) ([9d44991](https://github.com/n8n-io/n8n/commit/9d44991b2a8c9384e2bf32204ac47a4ecb0131be)) +* Correctly allow sharees to test credential when opening the modal ([#6111](https://github.com/n8n-io/n8n/issues/6111)) ([240bb47](https://github.com/n8n-io/n8n/commit/240bb47e8e2cd99c56b8837079073bbf2bf5f687)) +* **Date & Time Node:** Numbers conversions fix ([e11e7cd](https://github.com/n8n-io/n8n/commit/e11e7cd603a145c83c36f6a6deb821a56ccabd6f)) +* **editor:** Change execution list tab loader design ([#6120](https://github.com/n8n-io/n8n/issues/6120)) ([ffc033f](https://github.com/n8n-io/n8n/commit/ffc033ff8ff391be09da8dbd62f8eb06a94f6cb0)) +* **editor:** Fix `Show details` summary ([#6113](https://github.com/n8n-io/n8n/issues/6113)) ([e12bafb](https://github.com/n8n-io/n8n/commit/e12bafb9473393dd9e139d0e0a4a21241b417645)) +* **editor:** Fix copy selection behavior ([#6112](https://github.com/n8n-io/n8n/issues/6112)) ([0efd94a](https://github.com/n8n-io/n8n/commit/0efd94a875fe9e3cac5da3421effcf6c8f6eaae8)) +* **editor:** Fix cropped off completions docstrings ([#6129](https://github.com/n8n-io/n8n/issues/6129)) ([06594cc](https://github.com/n8n-io/n8n/commit/06594cc36f1a0d4069e556d6dbb4e268a78301c6)) +* **editor:** Fix missing `Stop Listening` button ([#6125](https://github.com/n8n-io/n8n/issues/6125)) ([dcbd2d2](https://github.com/n8n-io/n8n/commit/dcbd2d2bc1c59ca8bbe6a802a2a8f482b3d20d74)) +* **editor:** Fix quote handling on dollar-sign variable completions ([#6128](https://github.com/n8n-io/n8n/issues/6128)) ([c23ad35](https://github.com/n8n-io/n8n/commit/c23ad3502d0a8c4bcbd60cce2fc13a91981fb119)) +* **editor:** Fix sidebar button styling ([#6138](https://github.com/n8n-io/n8n/issues/6138)) ([d3f4bc1](https://github.com/n8n-io/n8n/commit/d3f4bc1859104f11d2cf38ef3f3405d62c88bc6d)) +* **editor:** Fix unique names for node duplication ([#6134](https://github.com/n8n-io/n8n/issues/6134)) ([48a4068](https://github.com/n8n-io/n8n/commit/48a4068d7ec1ddf5d5a4d10620dce1c89cb8fa34)) +* **editor:** Fix unscrollable node settings ([#6133](https://github.com/n8n-io/n8n/issues/6133)) ([f762f16](https://github.com/n8n-io/n8n/commit/f762f16afb7b9ea54e29950c06133a292dc87b3f)) +* **editor:** Loading state for executions tab ([#6100](https://github.com/n8n-io/n8n/issues/6100)) ([2e12c50](https://github.com/n8n-io/n8n/commit/2e12c50477014c57fa665ba36f48ff658cf7ee94)) +* **editor:** Remove pagination from binary data output ([#6093](https://github.com/n8n-io/n8n/issues/6093)) ([7b7d9de](https://github.com/n8n-io/n8n/commit/7b7d9de7586905b2740541e7f0289bc78f8f2ad7)) +* **editor:** Show error in RLC if credentials are not set ([#6108](https://github.com/n8n-io/n8n/issues/6108)) ([5bf3400](https://github.com/n8n-io/n8n/commit/5bf3400ca7ffa04bb51214bc539435b847614cbb)) +* **HTTP Request Node:** Add description for 'Specify Body' option ([#6114](https://github.com/n8n-io/n8n/issues/6114)) ([69b6ba8](https://github.com/n8n-io/n8n/commit/69b6ba85202c5a28052a56c041b880bfa9fcf0a0)) +* **HTTP Request Node:** Always lowercase headers ([31c56a1](https://github.com/n8n-io/n8n/commit/31c56a12f273d5569ed3ac4d72cf74c90cc24b31)) +* **Mattermost Node:** Fix base url trailing slash error ([#6097](https://github.com/n8n-io/n8n/issues/6097)) ([788fda1](https://github.com/n8n-io/n8n/commit/788fda1b7dbf29d7a7e614a959ac3630b2a6559f)) +* **Merge Node:** Do not error if expected key is missing ([8b59564](https://github.com/n8n-io/n8n/commit/8b59564776ebaf2c54b9e2dae5b77109895da883)) +* Prevent displaying an endless timer in the execution list for finished executions ([#6137](https://github.com/n8n-io/n8n/issues/6137)) ([2672896](https://github.com/n8n-io/n8n/commit/2672896c8e2de7d1f92899a392a3f2b3f60aaef3)) +* **Slack Node:** Restore ability to send text in addition of blocks or attachments ([625d672](https://github.com/n8n-io/n8n/commit/625d6729b4158fbc811941ce45819f32372e6265)) + + + +# [0.226.0](https://github.com/n8n-io/n8n/compare/n8n@0.225.0...n8n@0.226.0) (2023-04-26) + + +### Bug Fixes + +* **Code Node:** Update vm2 to address CVE-2023-30547 ([#6039](https://github.com/n8n-io/n8n/issues/6039)) ([8268f23](https://github.com/n8n-io/n8n/commit/8268f235abf5277480c215ea953fc3db1c275c95)) +* **core:** Improve domain and url matching for extractDomain and extractUrl ([#6010](https://github.com/n8n-io/n8n/issues/6010)) ([33fb732](https://github.com/n8n-io/n8n/commit/33fb73217dca68244c93296f1a4be96cc83e4480)) +* **core:** Serialize dates and regexps when reading from augmented objects ([#6086](https://github.com/n8n-io/n8n/issues/6086)) ([a4eb46a](https://github.com/n8n-io/n8n/commit/a4eb46acc178533e3c63fedcf0d884a5b66bae28)) +* **core:** Skip license activation when instance was already activated ([#6064](https://github.com/n8n-io/n8n/issues/6064)) ([eaf7090](https://github.com/n8n-io/n8n/commit/eaf70909197ed511efe9add956eb5e4f78b27e20)) +* **editor:** Clean up demo and template callouts from workflows page ([#6023](https://github.com/n8n-io/n8n/issues/6023)) ([4ee5083](https://github.com/n8n-io/n8n/commit/4ee508385ab5ac379925b315006a2d9389183751)) +* **editor:** Fix memory leak in Node Detail View by correctly unsubscribing from event buses ([#6021](https://github.com/n8n-io/n8n/issues/6021)) ([0970ec0](https://github.com/n8n-io/n8n/commit/0970ec066d8f80082f49f0b0f8987b95392102bf)) +* **editor:** Fix typo in SSO upgrade link ([#6031](https://github.com/n8n-io/n8n/issues/6031)) ([9b59f1d](https://github.com/n8n-io/n8n/commit/9b59f1df9c358d3677b4cbc3e80a73af03b7981d)) +* **editor:** Resolve expressions for grandparent nodes ([#5859](https://github.com/n8n-io/n8n/issues/5859)) ([a19d444](https://github.com/n8n-io/n8n/commit/a19d4447ac38e40d1fd1da83beb6c20fb7b2d0ed)) +* **editor:** SettingsSidebar should disconnect from push when navigating away ([#6025](https://github.com/n8n-io/n8n/issues/6025)) ([41660d9](https://github.com/n8n-io/n8n/commit/41660d9e281432b87d875c98992bfaf54d25b37f)) +* **editor:** Update LDAP and Log streaming paywalls ([#6069](https://github.com/n8n-io/n8n/issues/6069)) ([8a3b3e5](https://github.com/n8n-io/n8n/commit/8a3b3e53e1ac0a2a1864b42b24ab46f25253a9d3)) +* **editor:** Update SSO upgrade link ([#6016](https://github.com/n8n-io/n8n/issues/6016)) ([953198e](https://github.com/n8n-io/n8n/commit/953198e092a44029805ff85e4607355444ea8b2c)) +* **Notion Node:** Update credential test to not require user permissions ([#6022](https://github.com/n8n-io/n8n/issues/6022)) ([a68330f](https://github.com/n8n-io/n8n/commit/a68330ff66744551270f40399a6fd7fe330e6f27)) + + +### Features + +* **core:** Add license:info command ([#6047](https://github.com/n8n-io/n8n/issues/6047)) ([ab12d3e](https://github.com/n8n-io/n8n/commit/ab12d3e3278745b290de82c16f358841b20850b4)) +* **core:** Add SSH key generation ([#6006](https://github.com/n8n-io/n8n/issues/6006)) ([71ed1f4](https://github.com/n8n-io/n8n/commit/71ed1f410c5a80f35ecaf913a5522b7788998695)) +* **core:** Add support for digestAuth to httpRequest and declarative style ([#5676](https://github.com/n8n-io/n8n/issues/5676)) ([62f993c](https://github.com/n8n-io/n8n/commit/62f993c84f6ecf2f4d0431d505ba18a0253bd244)) +* **core:** Manage version control settings ([#6079](https://github.com/n8n-io/n8n/issues/6079)) ([f3b4701](https://github.com/n8n-io/n8n/commit/f3b470186360dc3c3a3df599f0a9740183e86696)) +* **core:** Upgrade google-timezones-json to use the correct timezone for Sao Paulo ([#6042](https://github.com/n8n-io/n8n/issues/6042)) ([b8cb5d7](https://github.com/n8n-io/n8n/commit/b8cb5d7f0b11fb138d5c4714bcc9e1d9b6366d76)), closes [#2647](https://github.com/n8n-io/n8n/issues/2647) +* **editor:** Add disable template experiment ([#5963](https://github.com/n8n-io/n8n/issues/5963)) ([a74284b](https://github.com/n8n-io/n8n/commit/a74284bac387338e870dc81ac33748af55521274)) +* **editor:** Add SQL editor support ([#5517](https://github.com/n8n-io/n8n/issues/5517)) ([70aaf24](https://github.com/n8n-io/n8n/commit/70aaf2478461d9ceea98bc91dc935493fd6dbe24)) +* **editor:** Enhance Node Creator actions view ([#5954](https://github.com/n8n-io/n8n/issues/5954)) ([390841b](https://github.com/n8n-io/n8n/commit/390841bbf0fdd4d536101593711a6658ea2784e4)) +* **editor:** Version control (WIP) ([#6013](https://github.com/n8n-io/n8n/issues/6013)) ([0e0a064](https://github.com/n8n-io/n8n/commit/0e0a064fa7ae54a8a6b695bd4bb19da71334fea0)) +* **editor:** Version control paywall (WIP) ([#6030](https://github.com/n8n-io/n8n/issues/6030)) ([ef79b03](https://github.com/n8n-io/n8n/commit/ef79b03f38460a20658c62fd35dbcaf6d266582f)) +* **Google BigQuery Node:** Node improvements ([#4877](https://github.com/n8n-io/n8n/issues/4877)) ([9817a15](https://github.com/n8n-io/n8n/commit/9817a15da4c80425fb77273ed7c9acbe020f0f48)) + + + +## [0.225.2](https://github.com/n8n-io/n8n/compare/n8n@0.225.1...n8n@0.225.2) (2023-04-25) + + +### Bug Fixes + +* **core:** Upgrade google-timezones-json to use the correct timezone for Sao Paulo ([#6042](https://github.com/n8n-io/n8n/issues/6042)) ([f93fd5a](https://github.com/n8n-io/n8n/commit/f93fd5aba2583e1c62efe8dcb423559413cf9c6a)), closes [#2647](https://github.com/n8n-io/n8n/issues/2647) +* **Code Node:** Update vm2 to address CVE-2023-30547 ([#6039](https://github.com/n8n-io/n8n/issues/6039)) ([f1ca4e2](https://github.com/n8n-io/n8n/commit/f1ca4e232865a0ee6ac178b11515de115b0bfd09)) + + + +# [0.224.4](https://github.com/n8n-io/n8n/compare/n8n@0.224.2...n8n@0.224.4) (2023-04-24) + + +### Bug Fixes + +* **core:** Upgrade google-timezones-json to use the correct timezone for Sao Paulo ([#6042](https://github.com/n8n-io/n8n/issues/6042)) ([c23a592](https://github.com/n8n-io/n8n/commit/c23a5923f8df787758f756c529518c9e8b93b96e)), closes [#2647](https://github.com/n8n-io/n8n/issues/2647) +* **Code Node:** Update vm2 to address CVE-2023-30547 ([#6039](https://github.com/n8n-io/n8n/issues/6039)) ([6cd15bd](https://github.com/n8n-io/n8n/commit/6cd15bd889b6eec3b050cfe3c3b190707a09e898)) + + + ## [0.225.1](https://github.com/n8n-io/n8n/compare/n8n@0.225.0...n8n@0.225.1) (2023-04-20) diff --git a/CHECKLIST.yml b/CHECKLIST.yml deleted file mode 100644 index a1c3fa8407508..0000000000000 --- a/CHECKLIST.yml +++ /dev/null @@ -1,50 +0,0 @@ -paths: - 'packages/**': - - If fixing bug, added test to cover scenario. - - If addressing forum or Github issue, added link to description. - 'packages/**/*.ts': - - Added unit tests to cover new or updated functionality. - '**/*.vue': - - Used composition API for all new components. - - Added component or unit tests to cover functionality. - - # cli - 'packages/cli/src/databases/migrations/**': - - Requested review from at least two engineers on migration. - - Avoided irreversible data migrations. - - Avoided deleting or updating data keys. - - Wrote 'down' migration if possible. - 'n8n/packages/cli/src/api/**': - - Added integration tests for new endpoints. - - # editor ui - 'packages/editor-ui/**/*.vue': - - Added E2E if adding new features. - - Used design system tokens (colors, spacings...) where possible. - 'packages/editor-ui/src/mixins/restApi.ts': - - Avoided adding new methods. Only deleted from here. - 'packages/editor-ui/src/mixins/**': - - Avoided adding new mixins (use composables instead). Only removed code from here. - 'packages/editor-ui/src/views/NodeView.vue': - - Avoided adding code here. Only refactored to make it smaller. - 'packages/editor-ui/src/hooks/**': - - Avoided adding new hooks. Only refactored to move hooks to relevant store instead. - - # nodes-base - 'packages/nodes-base/nodes/**': - - Added workflow tests for nodes if possible. - 'packages/nodes-base/package.json': - - Avoided adding dependencies for nodes if not absolutely necessary. - - # design-system - 'packages/design-system/**/*.vue': - - Used design system tokens (colors, spacings...) where possible. - - Updated Storybook with new component or updated functionality. - - # e2e - 'cypress/e2e/**': - - Avoided chaining commands more than two or three times (to avoid flakiness because only last one will be retried). - - Spoofed endpoints that are not critical for the test (to avoid flakiness). - - Picked most efficient path to start the test (for example skipped account setup and starting at /workflow/new for a canvas test). - - Avoided adding waits on time (use request intercepts instead). - - Ensured each spec does not depend on any another spec to pass. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6960d5ce28f6f..7480e601315cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,24 +5,27 @@ Great that you are here and you want to contribute to n8n ## Contents - [Contributing to n8n](#contributing-to-n8n) - - [Contents](#contents) - - [Code of conduct](#code-of-conduct) - - [Directory structure](#directory-structure) - - [Development setup](#development-setup) - - [Requirements](#requirements) - - [Node.js](#nodejs) - - [pnpm](#pnpm) - - [pnpm workspaces](#pnpm-workspaces) - - [corepack](#corepack) - - [Build tools](#build-tools) - - [Actual n8n setup](#actual-n8n-setup) - - [Start](#start) - - [Development cycle](#development-cycle) - - [Test suite](#test-suite) - - [Releasing](#releasing) - - [Create custom nodes](#create-custom-nodes) - - [Extend documentation](#extend-documentation) - - [Contributor License Agreement](#contributor-license-agreement) + - [Contents](#contents) + - [Code of conduct](#code-of-conduct) + - [Directory structure](#directory-structure) + - [Development setup](#development-setup) + - [Requirements](#requirements) + - [Node.js](#nodejs) + - [pnpm](#pnpm) + - [pnpm workspaces](#pnpm-workspaces) + - [corepack](#corepack) + - [Build tools](#build-tools) + - [Actual n8n setup](#actual-n8n-setup) + - [Start](#start) + - [Development cycle](#development-cycle) + - [Test suite](#test-suite) + - [Unit tests](#unit-tests) + - [E2E tests](#e2e-tests) + - [Releasing](#releasing) + - [Create custom nodes](#create-custom-nodes) + - [Extend documentation](#extend-documentation) + - [Contribute workflow templates](#contribute-workflow-templates) + - [Contributor License Agreement](#contributor-license-agreement) ## Code of conduct @@ -54,8 +57,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 @@ -65,11 +68,11 @@ dependencies are installed and the packages get linked correctly. Here a short g #### pnpm -[pnpm](https://pnpm.io/) version 7.18 or newer is required for development purposes. We recommend installing it with [corepack](#corepack). +[pnpm](https://pnpm.io/) version 8.9 or newer is required for development purposes. We recommend installing it with [corepack](#corepack). ##### 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 +116,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//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 +175,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 ``` @@ -186,7 +189,9 @@ automatically build your code, restart the backend and refresh the frontend ### Test suite -The tests can be started via: +#### Unit tests + +Unit tests can be started via: ``` pnpm test @@ -196,9 +201,21 @@ If that gets executed in one of the package folders it will only run the tests of this package. If it gets executed in the n8n-root folder it will run all tests of all packages. +#### E2E tests + +⚠️ You have to run `pnpm cypress:install` to install cypress before running the tests for the first time and to update cypress. + +E2E tests can be started via one of the following commands: + +- `pnpm test:e2e:ui`: Start n8n and run e2e tests interactively using built UI code. Does not react to code changes (i.e. runs `pnpm start` and `cypress open`) +- `pnpm test:e2e:dev`: Start n8n in development mode and run e2e tests interactively. Reacts to code changes (i.e. runs `pnpm dev` and `cypress open`) +- `pnpm test:e2e:all`: Start n8n and run e2e tests headless (i.e. runs `pnpm start` and `cypress run --headless`) + +⚠️ Remember to stop your dev server before. Otherwise port binding will fail. + ## 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 +223,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 @@ -220,10 +237,18 @@ Learn about [building nodes](https://docs.n8n.io/integrations/creating-nodes/) t The repository for the n8n documentation on [docs.n8n.io](https://docs.n8n.io) can be found [here](https://github.com/n8n-io/n8n-docs). +## Contribute workflow templates + +You can submit your workflows to n8n's template library. + +n8n is working on a creator program, and developing a marketplace of templates. This is an ongoing project, and details are likely to change. + +Refer to [n8n Creator hub](https://www.notion.so/n8n/n8n-Creator-hub-7bd2cbe0fce0449198ecb23ff4a2f76f) for information on how to submit templates and become a creator. + ## Contributor License Agreement That we do not have any potential problems later it is sadly necessary to sign a [Contributor License Agreement](CONTRIBUTOR_LICENSE_AGREEMENT.md). That can be done literally with the push of a button. 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. diff --git a/LICENSE.md b/LICENSE.md index c1d74239754fd..aab68b6d9301b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,8 +3,9 @@ Portions of this software are licensed as follows: - Content of branches other than the main branch (i.e. "master") are not licensed. -- All source code files that contain ".ee." in their filename are licensed under the - "n8n Enterprise License" defined in "LICENSE_EE.md". +- Source code files that contain ".ee." in their filename are NOT licensed under the Sustainable Use License. + To use source code files that contain ".ee." in their filename you must hold a valid n8n Enterprise License + specifically allowing you access to such source code files and as defined in "LICENSE_EE.md". - All third party components incorporated into the n8n Software are licensed under the original license provided by the owner of the applicable component. - Content outside of the above mentioned files or restrictions is available under the "Sustainable Use diff --git a/README.md b/README.md index 25a8199b69cf2..f37cffd70f775 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ESA_POSTMAN_ID_COLLISION_RETRY_COUNT= # n8n - Workflow automation tool -n8n is an extendable workflow automation tool. With a [fair-code](http://faircode.io) distribution model, n8n +n8n is an extendable workflow automation tool. With a [fair-code](https://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything. @@ -24,7 +24,7 @@ anything to everything. ## Demo -[:tv: A short video (< 4 min)](https://www.youtube.com/watch?v=RpjQTGKm-ok) that goes over key concepts of +[:tv: A short video (< 5 min)](https://www.youtube.com/watch?v=1MwSoB0gnM4) that goes over key concepts of creating workflows in n8n. ## Available integrations @@ -49,6 +49,7 @@ changes [here](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-C [how to run n8n in **Docker**](https://docs.n8n.io/hosting/installation/docker/) ## Start + You can try n8n without installing it using npx. You must have [Node.js](https://nodejs.org/en/) installed. From the terminal, run: @@ -66,6 +67,15 @@ While n8n cloud and n8n are the same in terms of features, n8n cloud provides ce - Managed OAuth for authentication - Easily upgrading to the newer n8n versions +## Build with LangChain and AI in n8n (beta) + +With n8n's LangChain nodes you can build AI-powered functionality within your workflows. The LangChain nodes are configurable, meaning you can choose your preferred agent, LLM, memory, and so on. Alongside the LangChain nodes, you can connect any n8n node as normal: this means you can integrate your LangChain logic with other data sources and services. + +Learn more in the [documentation](https://docs.n8n.io/langchain/). + +- [LangChain nodes package](https://www.npmjs.com/package/@n8n/n8n-nodes-langchain) +- [Chatbot package](https://www.npmjs.com/package/@n8n/chat) + ## Support If you have problems or questions go to our forum, we will then try to help you asap: @@ -97,7 +107,7 @@ development environment ready in minutes. ## License -n8n is [fair-code](http://faircode.io) distributed under the +n8n is [fair-code](https://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) and the [**n8n Enterprise License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE_EE.md). diff --git a/cypress.config.js b/cypress.config.js index d1451fcd06fc2..f01672c6f9b8b 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,17 +1,16 @@ -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, }, defaultCommandTimeout: 10000, requestTimeout: 12000, - numTestsKeptInMemory: 0, + numTestsKeptInMemory: 2, experimentalMemoryManagement: true, e2e: { baseUrl: BASE_URL, @@ -19,26 +18,10 @@ 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 - } - }, - 'enable-feature': (feature) => - fetch(BASE_URL + `/e2e/enable-feature/${feature}`, { method: 'POST' }), - }); - }, + }, + env: { + MAX_PINNED_DATA_SIZE: process.env.VUE_APP_MAX_PINNED_DATA_SIZE + ? parseInt(process.env.VUE_APP_MAX_PINNED_DATA_SIZE, 10) + : 16 * 1024, }, }); - diff --git a/cypress/composables/becomeTemplateCreatorCta.ts b/cypress/composables/becomeTemplateCreatorCta.ts new file mode 100644 index 0000000000000..55fc985c745bc --- /dev/null +++ b/cypress/composables/becomeTemplateCreatorCta.ts @@ -0,0 +1,18 @@ +//#region Getters + +export const getBecomeTemplateCreatorCta = () => cy.getByTestId('become-template-creator-cta'); + +export const getCloseBecomeTemplateCreatorCtaButton = () => + cy.getByTestId('close-become-template-creator-cta'); + +//#endregion + +//#region Actions + +export const interceptCtaRequestWithResponse = (becomeCreator: boolean) => { + return cy.intercept('GET', `/rest/cta/become-creator`, { + body: becomeCreator, + }); +}; + +//#endregion diff --git a/cypress/composables/featureFlags.ts b/cypress/composables/featureFlags.ts new file mode 100644 index 0000000000000..ea8e95064e4d1 --- /dev/null +++ b/cypress/composables/featureFlags.ts @@ -0,0 +1,12 @@ +export const overrideFeatureFlag = (name: string, value: boolean | string) => { + cy.window().then((win) => { + // If feature flags hasn't been initialized yet, we store the override + // in local storage and it gets loaded when the feature flags are + // initialized. + win.localStorage.setItem('N8N_EXPERIMENT_OVERRIDES', JSON.stringify({ [name]: value })); + + if (win.featureFlags) { + win.featureFlags.override(name, value); + } + }); +}; diff --git a/cypress/composables/modals/chat-modal.ts b/cypress/composables/modals/chat-modal.ts new file mode 100644 index 0000000000000..31e139c93e6bc --- /dev/null +++ b/cypress/composables/modals/chat-modal.ts @@ -0,0 +1,51 @@ +/** + * Getters + */ + +export function getManualChatModal() { + return cy.getByTestId('lmChat-modal'); +} + +export function getManualChatInput() { + return cy.getByTestId('workflow-chat-input'); +} + +export function getManualChatSendButton() { + return getManualChatModal().getByTestId('workflow-chat-send-button'); +} + +export function getManualChatMessages() { + return getManualChatModal().get('.messages .message'); +} + +export function getManualChatModalCloseButton() { + return getManualChatModal().get('.el-dialog__close'); +} + +export function getManualChatModalLogs() { + return getManualChatModal().getByTestId('lm-chat-logs'); +} +export function getManualChatDialog() { + return getManualChatModal().getByTestId('workflow-lm-chat-dialog'); +} + +export function getManualChatModalLogsTree() { + return getManualChatModalLogs().getByTestId('lm-chat-logs-tree'); +} + +export function getManualChatModalLogsEntries() { + return getManualChatModalLogs().getByTestId('lm-chat-logs-entry'); +} + +/** + * Actions + */ + +export function sendManualChatMessage(message: string) { + getManualChatInput().type(message); + getManualChatSendButton().click(); +} + +export function closeManualChatModal() { + getManualChatModalCloseButton().click(); +} diff --git a/cypress/composables/modals/credential-modal.ts b/cypress/composables/modals/credential-modal.ts new file mode 100644 index 0000000000000..bfcbf89251059 --- /dev/null +++ b/cypress/composables/modals/credential-modal.ts @@ -0,0 +1,54 @@ +/** + * Getters + */ + +export function getCredentialConnectionParameterInputs() { + return cy.getByTestId('credential-connection-parameter'); +} + +export function getCredentialConnectionParameterInputByName(name: string) { + return cy.getByTestId(`parameter-input-${name}`); +} + +export function getEditCredentialModal() { + return cy.getByTestId('editCredential-modal', { timeout: 5000 }); +} + +export function getCredentialSaveButton() { + return cy.getByTestId('credential-save-button', { timeout: 5000 }); +} + +export function getCredentialDeleteButton() { + return cy.getByTestId('credential-delete-button'); +} + +export function getCredentialModalCloseButton() { + return getEditCredentialModal().find('.el-dialog__close').first(); +} + +/** + * Actions + */ + +export function setCredentialConnectionParameterInputByName(name: string, value: string) { + getCredentialConnectionParameterInputByName(name).type(value); +} + +export function saveCredential() { + getCredentialSaveButton().click({ force: true }); +} + +export function closeCredentialModal() { + getCredentialModalCloseButton().click(); +} + +export function setCredentialValues(values: Record, save = true) { + Object.entries(values).forEach(([key, value]) => { + setCredentialConnectionParameterInputByName(key, value); + }); + + if (save) { + saveCredential(); + closeCredentialModal(); + } +} diff --git a/cypress/composables/modals/workflow-credential-setup-modal.ts b/cypress/composables/modals/workflow-credential-setup-modal.ts new file mode 100644 index 0000000000000..88bbd133486e5 --- /dev/null +++ b/cypress/composables/modals/workflow-credential-setup-modal.ts @@ -0,0 +1,13 @@ +/** + * Getters + */ + +export const getWorkflowCredentialsModal = () => cy.getByTestId('setup-workflow-credentials-modal'); + +export const getContinueButton = () => cy.getByTestId('continue-button'); + +/** + * Actions + */ + +export const closeModalFromContinueButton = () => getContinueButton().click(); diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts new file mode 100644 index 0000000000000..e2fc03d7afe61 --- /dev/null +++ b/cypress/composables/ndv.ts @@ -0,0 +1,84 @@ +/** + * Getters + */ + +import { getVisibleSelect } from "../utils"; + +export function getCredentialSelect(eq = 0) { + return cy.getByTestId('node-credentials-select').eq(eq); +} + +export function getCreateNewCredentialOption() { + return cy.getByTestId('node-credentials-select-item-new'); +} + +export function getBackToCanvasButton() { + return cy.getByTestId('back-to-canvas'); +} + +export function getExecuteNodeButton() { + return cy.getByTestId('node-execute-button'); +} + +export function getParameterInputByName(name: string) { + return cy.getByTestId(`parameter-input-${name}`); +} + +export function getInputPanel() { + return cy.getByTestId('input-panel'); +} + +export function getMainPanel() { + return cy.getByTestId('node-parameters'); +} + +export function getOutputPanel() { + return cy.getByTestId('output-panel'); +} + +export function getOutputPanelDataContainer() { + return getOutputPanel().getByTestId('ndv-data-container'); +} + +export function getOutputPanelTable() { + return getOutputPanelDataContainer().get('table'); +} + +/** + * Actions + */ + +export function openCredentialSelect(eq = 0) { + getCredentialSelect(eq).click(); +} + +export function setCredentialByName(name: string) { + openCredentialSelect(); + getCredentialSelect().contains(name).click(); +} + +export function clickCreateNewCredential() { + openCredentialSelect(); + getCreateNewCredentialOption().click(); +} + +export function clickGetBackToCanvas() { + getBackToCanvasButton().click(); +} + +export function clickExecuteNode() { + getExecuteNodeButton().click(); +} + +export function setParameterInputByName(name: string, value: string) { + getParameterInputByName(name).clear().type(value); +} + +export function toggleParameterCheckboxInputByName(name: string) { + getParameterInputByName(name).find('input[type="checkbox"]').realClick() +} + +export function setParameterSelectByContent(name: string, content: string) { + getParameterInputByName(name).realClick(); + getVisibleSelect().find('.option-headline').contains(content).click(); +} diff --git a/cypress/composables/setup-template-form-step.ts b/cypress/composables/setup-template-form-step.ts new file mode 100644 index 0000000000000..6f0166278367f --- /dev/null +++ b/cypress/composables/setup-template-form-step.ts @@ -0,0 +1,14 @@ +/** + * Getters + */ + +export const getFormStep = () => cy.getByTestId('setup-credentials-form-step'); + +export const getStepHeading = ($el: JQuery) => + cy.wrap($el).findChildByTestId('credential-step-heading'); + +export const getStepDescription = ($el: JQuery) => + cy.wrap($el).findChildByTestId('credential-step-description'); + +export const getCreateAppCredentialsButton = (appName: string) => + cy.get(`button:contains("Create new ${appName} credential")`); diff --git a/cypress/composables/setup-workflow-credentials-button.ts b/cypress/composables/setup-workflow-credentials-button.ts new file mode 100644 index 0000000000000..6b1b9b69d4582 --- /dev/null +++ b/cypress/composables/setup-workflow-credentials-button.ts @@ -0,0 +1,5 @@ +/** + * Getters + */ + +export const getSetupWorkflowCredentialsButton = () => cy.get(`button:contains("Set up template")`); diff --git a/cypress/composables/versions.ts b/cypress/composables/versions.ts new file mode 100644 index 0000000000000..f96ea8152fd31 --- /dev/null +++ b/cypress/composables/versions.ts @@ -0,0 +1,32 @@ +/** + * Getters + */ + +export function getVersionUpdatesPanelOpenButton() { + return cy.getByTestId('version-updates-panel-button'); +} + +export function getVersionUpdatesPanel() { + return cy.getByTestId('version-updates-panel'); +} + +export function getVersionUpdatesPanelCloseButton() { + return getVersionUpdatesPanel().get('.el-drawer__close-btn').first(); +} + +export function getVersionCard() { + return cy.getByTestId('version-card'); +} + +/** + * Actions + */ + +export function openVersionUpdatesPanel() { + getVersionUpdatesPanelOpenButton().click(); + getVersionUpdatesPanel().should('be.visible'); +} + +export function closeVersionUpdatesPanel() { + getVersionUpdatesPanelCloseButton().click(); +} diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts new file mode 100644 index 0000000000000..1aa469b19458f --- /dev/null +++ b/cypress/composables/workflow.ts @@ -0,0 +1,159 @@ +import { ROUTES } from '../constants'; +import { getManualChatModal } from './modals/chat-modal'; + +/** + * Types + */ + +export type EndpointType = + | 'ai_chain' + | 'ai_document' + | 'ai_embedding' + | 'ai_languageModel' + | 'ai_memory' + | 'ai_outputParser' + | 'ai_tool' + | 'ai_retriever' + | 'ai_textSplitter' + | 'ai_vectorRetriever' + | 'ai_vectorStore'; + +/** + * Getters + */ + +export function getAddInputEndpointByType(nodeName: string, endpointType: EndpointType) { + return cy.get( + `.add-input-endpoint[data-jtk-scope-${endpointType}][data-endpoint-name="${nodeName}"]`, + ); +} + +export function getNodeCreatorItems() { + return cy.getByTestId('item-iterator-item'); +} + +export function getExecuteWorkflowButton() { + return cy.getByTestId('execute-workflow-button'); +} + +export function getManualChatButton() { + return cy.getByTestId('workflow-chat-button'); +} + +export function getNodes() { + return cy.getByTestId('canvas-node'); +} + +export function getNodeByName(name: string) { + return cy.getByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0); +} + +export function disableNode(name: string) { + const target = getNodeByName(name); + target.rightclick(name ? 'center' : 'topLeft', { force: true }); + cy.getByTestId(`context-menu-item-toggle_activation`).click(); +} + +export function getConnectionBySourceAndTarget(source: string, target: string) { + return cy + .get('.jtk-connector') + .filter(`[data-source-node="${source}"][data-target-node="${target}"]`) + .eq(0); +} + +export function getNodeCreatorSearchBar() { + return cy.getByTestId('node-creator-search-bar'); +} + +export function getNodeCreatorPlusButton() { + return cy.getByTestId('node-creator-plus-button'); +} + +/** + * Actions + */ + +export function addNodeToCanvas( + nodeDisplayName: string, + plusButtonClick = true, + preventNdvClose?: boolean, + action?: string, +) { + if (plusButtonClick) { + getNodeCreatorPlusButton().click(); + } + + getNodeCreatorSearchBar().type(nodeDisplayName); + getNodeCreatorSearchBar().type('{enter}'); + cy.wait(500); + cy.get('body').then((body) => { + if (body.find('[data-test-id=node-creator]').length > 0) { + if (action) { + cy.contains(action).click(); + } else { + // Select the first action + cy.get('[data-keyboard-nav-type="action"]').eq(0).click(); + } + } + }); + + if (!preventNdvClose) cy.get('body').type('{esc}'); +} + +export function navigateToNewWorkflowPage(preventNodeViewUnload = true) { + cy.visit(ROUTES.NEW_WORKFLOW_PAGE); + cy.waitForLoad(); + cy.window().then((win) => { + win.preventNodeViewBeforeUnload = preventNodeViewUnload; + }); +} + +export function addSupplementalNodeToParent( + nodeName: string, + endpointType: EndpointType, + parentNodeName: string, + exactMatch = false, +) { + getAddInputEndpointByType(parentNodeName, endpointType).click({ force: true }); + if (exactMatch) { + getNodeCreatorItems() + .contains(new RegExp('^' + nodeName + '$', 'g')) + .click(); + } else { + getNodeCreatorItems().contains(nodeName).click(); + } + getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist'); +} + +export function addLanguageModelNodeToParent( + nodeName: string, + parentNodeName: string, + exactMatch = false, +) { + addSupplementalNodeToParent(nodeName, 'ai_languageModel', parentNodeName, exactMatch); +} + +export function addMemoryNodeToParent(nodeName: string, parentNodeName: string) { + addSupplementalNodeToParent(nodeName, 'ai_memory', parentNodeName); +} + +export function addToolNodeToParent(nodeName: string, parentNodeName: string) { + addSupplementalNodeToParent(nodeName, 'ai_tool', parentNodeName); +} + +export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) { + addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName); +} + +export function clickExecuteWorkflowButton() { + getExecuteWorkflowButton().click(); +} + +export function clickManualChatButton() { + getManualChatButton().click(); + getManualChatModal().should('be.visible'); +} + +export function openNode(nodeName: string) { + getNodeByName(nodeName).dblclick(); +} diff --git a/cypress/constants.ts b/cypress/constants.ts index 5118696bc81d3..9711a7fc0294e 100644 --- a/cypress/constants.ts +++ b/cypress/constants.ts @@ -1,14 +1,47 @@ +import { randFirstName, randLastName } from '@ngneat/falso'; + +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_ADMIN = { + email: 'admin@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"'; +export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Test workflow"'; +export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger'; export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger'; export const CODE_NODE_NAME = 'Code'; export const SET_NODE_NAME = 'Set'; -export const IF_NODE_NAME = 'IF'; +export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields'; +export const IF_NODE_NAME = 'If'; export const MERGE_NODE_NAME = 'Merge'; export const SWITCH_NODE_NAME = 'Switch'; export const GMAIL_NODE_NAME = 'Gmail'; @@ -16,6 +49,14 @@ export const TRELLO_NODE_NAME = 'Trello'; export const NOTION_NODE_NAME = 'Notion'; export const PIPEDRIVE_NODE_NAME = 'Pipedrive'; export const HTTP_REQUEST_NODE_NAME = 'HTTP Request'; +export const AGENT_NODE_NAME = 'AI Agent'; +export const BASIC_LLM_CHAIN_NODE_NAME = 'Basic LLM Chain'; +export const AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME = 'Window Buffer Memory'; +export const AI_TOOL_CALCULATOR_NODE_NAME = 'Calculator'; +export const AI_TOOL_CODE_NODE_NAME = 'Custom Code Tool'; +export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia'; +export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'; +export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser'; export const META_KEY = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}'; @@ -23,3 +64,7 @@ export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account'; export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account'; export const NEW_NOTION_ACCOUNT_NAME = 'Notion account'; export const NEW_QUERY_AUTH_ACCOUNT_NAME = 'Query Auth account'; + +export const ROUTES = { + NEW_WORKFLOW_PAGE: '/workflow/new', +}; diff --git a/cypress/e2e/0-smoke.cy.ts b/cypress/e2e/0-smoke.cy.ts deleted file mode 100644 index 09d9842922743..0000000000000 --- a/cypress/e2e/0-smoke.cy.ts +++ /dev/null @@ -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 }); - }); -}); diff --git a/cypress/e2e/1-workflows.cy.ts b/cypress/e2e/1-workflows.cy.ts index d10f3ac850ea5..25f4f3cb0a697 100644 --- a/cypress/e2e/1-workflows.cy.ts +++ b/cypress/e2e/1-workflows.cy.ts @@ -8,11 +8,6 @@ const WorkflowPage = new WorkflowPageClass(); const multipleWorkflowsCount = 5; describe('Workflows', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { cy.visit(WorkflowsPage.url); }); diff --git a/cypress/e2e/10-settings-log-streaming.cy.ts b/cypress/e2e/10-settings-log-streaming.cy.ts index 0126667a699b5..9acec76f420bc 100644 --- a/cypress/e2e/10-settings-log-streaming.cy.ts +++ b/cypress/e2e/10-settings-log-streaming.cy.ts @@ -1,23 +1,10 @@ -import { randFirstName, randLastName } from '@ngneat/falso'; -import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; import { SettingsLogStreamingPage } from '../pages'; +import { getVisibleModalOverlay } from '../utils/modal'; +import { getVisibleDropdown } from '../utils'; -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.resetAll(); - 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'); @@ -26,7 +13,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'); @@ -34,6 +21,7 @@ describe('Log Streaming Settings', () => { }); it('should show the add destination modal', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); @@ -42,7 +30,7 @@ describe('Log Streaming Settings', () => { settingsLogStreamingPage.getters.getSelectDestinationButton().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationButton().should('have.attr', 'disabled'); settingsLogStreamingPage.getters - .getDestinationModalDialog() + .getDestinationModal() .invoke('css', 'width') .then((widthStr) => parseInt((widthStr as unknown as string).replace('px', ''))) .should('be.lessThan', 500); @@ -51,65 +39,67 @@ describe('Log Streaming Settings', () => { settingsLogStreamingPage.getters .getSelectDestinationButton() .should('not.have.attr', 'disabled'); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); settingsLogStreamingPage.getters.getDestinationModal().should('not.exist'); }); it('should create a destination and delete it', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); + cy.wait(1000); // Race condition with getDestinationDataFromBackend() settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); settingsLogStreamingPage.getters.getDestinationModal().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationType().click(); settingsLogStreamingPage.getters.getSelectDestinationTypeItems().eq(0).click(); settingsLogStreamingPage.getters.getSelectDestinationButton().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().click() + settingsLogStreamingPage.getters.getDestinationNameInput().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().find('input').clear().type('Destination 0'); + settingsLogStreamingPage.getters + .getDestinationNameInput() + .find('input') + .clear() + .type('Destination 0'); settingsLogStreamingPage.getters.getDestinationSaveButton().click(); cy.wait(100); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); cy.reload(); settingsLogStreamingPage.getters.getDestinationCards().eq(0).click(); settingsLogStreamingPage.getters.getDestinationDeleteButton().should('be.visible').click(); cy.get('.el-message-box').should('be.visible').find('.btn--cancel').click(); settingsLogStreamingPage.getters.getDestinationDeleteButton().click(); cy.get('.el-message-box').should('be.visible').find('.btn--confirm').click(); - cy.reload(); }); it('should create a destination and delete it via card actions', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); + cy.wait(1000); // Race condition with getDestinationDataFromBackend() settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); settingsLogStreamingPage.getters.getDestinationModal().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationType().click(); settingsLogStreamingPage.getters.getSelectDestinationTypeItems().eq(0).click(); settingsLogStreamingPage.getters.getSelectDestinationButton().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().click() - settingsLogStreamingPage.getters.getDestinationNameInput().find('input').clear().type('Destination 1'); + settingsLogStreamingPage.getters.getDestinationNameInput().click(); + settingsLogStreamingPage.getters + .getDestinationNameInput() + .find('input') + .clear() + .type('Destination 1'); settingsLogStreamingPage.getters.getDestinationSaveButton().should('not.have.attr', 'disabled'); settingsLogStreamingPage.getters.getDestinationSaveButton().click(); cy.wait(100); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); cy.reload(); - settingsLogStreamingPage.getters - .getDestinationCards() - .eq(0) - .find('.el-dropdown-selfdefine') - .click(); - cy.get('.el-dropdown-menu').find('.el-dropdown-menu__item').eq(0).click(); + settingsLogStreamingPage.getters.getDestinationCards().eq(0).find('.el-dropdown').click(); + getVisibleDropdown().find('.el-dropdown-menu__item').eq(0).click(); settingsLogStreamingPage.getters.getDestinationSaveButton().should('not.exist'); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); - settingsLogStreamingPage.getters - .getDestinationCards() - .eq(0) - .find('.el-dropdown-selfdefine') - .click(); - cy.get('.el-dropdown-menu').find('.el-dropdown-menu__item').eq(1).click(); + settingsLogStreamingPage.getters.getDestinationCards().eq(0).find('.el-dropdown').click(); + getVisibleDropdown().find('.el-dropdown-menu__item').eq(1).click(); cy.get('.el-message-box').should('be.visible').find('.btn--confirm').click(); - cy.reload(); }); }); diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index 8f9b90d68c322..3190987541673 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -1,20 +1,17 @@ -import { CODE_NODE_NAME, SET_NODE_NAME } from './../constants'; +import { CODE_NODE_NAME, SET_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from './../constants'; import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { MessageBox as MessageBoxClass } from '../pages/modals/message-box'; import { NDV } from '../pages/ndv'; // Suite-specific constants const CODE_NODE_NEW_NAME = 'Something else'; const WorkflowPage = new WorkflowPageClass(); +const messageBox = new MessageBoxClass(); const ndv = new NDV(); describe('Undo/Redo', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); }); @@ -46,21 +43,34 @@ describe('Undo/Redo', () => { SET_NODE_NAME, ); WorkflowPage.actions.zoomToFit(); + WorkflowPage.getters + .canvasNodeByName('Code') + .should('have.css', 'left', '860px') + .should('have.css', 'top', '220px'); + WorkflowPage.actions.hitUndo(); WorkflowPage.getters.canvasNodes().should('have.have.length', 2); WorkflowPage.getters.nodeConnections().should('have.length', 1); + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 1); + WorkflowPage.getters.nodeConnections().should('have.length', 0); + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); WorkflowPage.actions.hitRedo(); WorkflowPage.getters.canvasNodes().should('have.have.length', 3); WorkflowPage.getters.nodeConnections().should('have.length', 2); + // Last node should be added back to original position + WorkflowPage.getters + .canvasNodeByName('Code') + .should('have.css', 'left', '860px') + .should('have.css', 'top', '220px'); }); - it('should undo/redo deleting node using delete button', () => { + it('should undo/redo deleting node using context menu', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodeByName(CODE_NODE_NAME) - .find('[data-test-id=delete-node-button]') - .click({ force: true }); + WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().should('have.have.length', 1); WorkflowPage.getters.nodeConnections().should('have.length', 0); WorkflowPage.actions.hitUndo(); @@ -122,28 +132,32 @@ describe('Undo/Redo', () => { it('should undo/redo moving nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true }); WorkflowPage.getters - .canvasNodes() - .last() - .should('have.attr', 'style', 'left: 740px; top: 360px;'); + .canvasNodeByName('Code') + .should('have.css', 'left', '740px') + .should('have.css', 'top', '320px'); + WorkflowPage.actions.hitUndo(); WorkflowPage.getters - .canvasNodes() - .last() - .should('have.attr', 'style', 'left: 640px; top: 260px;'); + .canvasNodeByName('Code') + .should('have.css', 'left', '640px') + .should('have.css', 'top', '220px'); WorkflowPage.actions.hitRedo(); WorkflowPage.getters - .canvasNodes() - .last() - .should('have.attr', 'style', 'left: 740px; top: 360px;'); + .canvasNodeByName('Code') + .should('have.css', 'left', '740px') + .should('have.css', 'top', '320px'); }); - it('should undo/redo deleting a connection by pressing delete button', () => { + it('should undo/redo deleting a connection using context menu', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.nodeConnections().realHover(); - cy.get('.connection-actions .delete').filter(':visible').should('be.visible').click(); + cy.get('.connection-actions .delete') + .filter(':visible') + .should('be.visible') + .click({ force: true }); WorkflowPage.getters.nodeConnections().should('have.length', 0); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.nodeConnections().should('have.length', 1); @@ -162,14 +176,10 @@ describe('Undo/Redo', () => { WorkflowPage.getters.nodeConnections().should('have.length', 0); }); - it('should undo/redo disabling a node using disable button', () => { + it('should undo/redo disabling a node using context menu', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodes() - .last() - .find('[data-test-id="disable-node-button"]') - .click({ force: true }); + WorkflowPage.actions.disableNode(CODE_NODE_NAME); WorkflowPage.getters.disabledNodes().should('have.length', 1); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.disabledNodes().should('have.length', 0); @@ -237,11 +247,7 @@ describe('Undo/Redo', () => { it('should undo/redo duplicating a node', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodes() - .last() - .find('[data-test-id="duplicate-node-button"]') - .click({ force: true }); + WorkflowPage.actions.duplicateNode(CODE_NODE_NAME); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.canvasNodes().should('have.length', 2); WorkflowPage.actions.hitRedo(); @@ -252,57 +258,134 @@ describe('Undo/Redo', () => { cy.fixture('Test_workflow-actions_paste-data.json').then((data) => { cy.get('body').paste(JSON.stringify(data)); WorkflowPage.actions.zoomToFit(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 2); + WorkflowPage.getters.canvasNodes().should('have.have.length', 5); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.canvasNodes().should('have.have.length', 0); WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes().should('have.have.length', 2); + WorkflowPage.getters.canvasNodes().should('have.have.length', 5); }); }); it('should undo/redo multiple steps', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + // WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.zoomToFit(); // Disable last node WorkflowPage.getters.canvasNodes().last().click(); WorkflowPage.actions.hitDisableNodeShortcut(); + // Move first one - WorkflowPage.getters.canvasNodes().first().click(); - cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); - // Delete the set node - WorkflowPage.getters.canvasNodeByName(SET_NODE_NAME).click().click(); - cy.get('body').type('{backspace}'); + WorkflowPage.actions + .getNodePosition(WorkflowPage.getters.canvasNodes().first()) + .then((initialPosition) => { + WorkflowPage.getters.canvasNodes().first().click(); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { + clickToFinish: true, + }); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left + 120}px`) + .should('have.css', 'top', `${initialPosition.top + 140}px`); - // First undo: Should return deleted node - WorkflowPage.actions.hitUndo(); + // Delete the set node + WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click().click(); + cy.get('body').type('{backspace}'); + + // First undo: Should return deleted node + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.length', 4); + WorkflowPage.getters.nodeConnections().should('have.length', 3); + // Second undo: Should move first node to it's original position + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left}px`) + .should('have.css', 'top', `${initialPosition.top}px`); + // Third undo: Should enable last node + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.disabledNodes().should('have.length', 0); + + // First redo: Should disable last node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters.disabledNodes().should('have.length', 1); + // Second redo: Should move the first node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left + 120}px`) + .should('have.css', 'top', `${initialPosition.top + 140}px`); + // Third redo: Should delete the Set node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters.canvasNodes().should('have.length', 3); + WorkflowPage.getters.nodeConnections().should('have.length', 2); + }); + }); + + it('should be able to copy and paste pinned data nodes in workflows with dynamic Switch node', () => { + cy.fixture('Test_workflow_form_switch.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + WorkflowPage.actions.zoomToFit(); + + WorkflowPage.getters.canvasNodes().should('have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')) + .should('have.css', 'left', `637px`) + .should('have.css', 'top', `501px`); + + cy.fixture('Test_workflow_form_switch.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); WorkflowPage.getters.canvasNodes().should('have.length', 4); - WorkflowPage.getters.nodeConnections().should('have.length', 3); - // Second undo: Should move first node to it's original position + WorkflowPage.getters.nodeConnections().should('have.length', 2); + WorkflowPage.actions.hitUndo(); - WorkflowPage.getters - .canvasNodes() - .first() - .should('have.attr', 'style', 'left: 420px; top: 260px;'); - // Third undo: Should enable last node + + WorkflowPage.getters.canvasNodes().should('have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')) + .should('have.css', 'left', `637px`) + .should('have.css', 'top', `501px`); + }); + + it('should not undo/redo when NDV or a modal is open', () => { + WorkflowPage.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME, { keepNdvOpen: true }); + // Try while NDV is open WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.disabledNodes().should('have.length', 0); + WorkflowPage.getters.canvasNodes().should('have.have.length', 1); + ndv.getters.backToCanvas().click(); + // Try while modal is open + cy.getByTestId('menu-item').contains('About n8n').click({ force: true }); + cy.getByTestId('about-modal').should('be.visible'); + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 1); + cy.getByTestId('close-about-modal-button').click(); + // Should work now + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 0); + }); - // First redo: Should disable last node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.disabledNodes().should('have.length', 1); - // Second redo: Should move the first node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters - .canvasNodes() - .first() - .should('have.attr', 'style', 'left: 540px; top: 400px;'); - // Third redo: Should delete the Set node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes().should('have.length', 3); - WorkflowPage.getters.nodeConnections().should('have.length', 2); + it('should not undo/redo when NDV or a prompt is open', () => { + WorkflowPage.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME, { keepNdvOpen: false }); + WorkflowPage.getters.workflowMenu().click(); + WorkflowPage.getters.workflowMenuItemImportFromURLItem().should('be.visible'); + WorkflowPage.getters.workflowMenuItemImportFromURLItem().click(); + // Try while prompt is open + messageBox.getters.header().click(); + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 1); + // Close prompt and try again + messageBox.actions.cancel(); + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.have.length', 0); }); }); diff --git a/cypress/e2e/11-inline-expression-editor.cy.ts b/cypress/e2e/11-inline-expression-editor.cy.ts index de5594a4f429a..1d95b2db8e889 100644 --- a/cypress/e2e/11-inline-expression-editor.cy.ts +++ b/cypress/e2e/11-inline-expression-editor.cy.ts @@ -1,74 +1,136 @@ +import { NDV } from '../pages/ndv'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +const ndv = new NDV(); const WorkflowPage = new WorkflowPageClass(); describe('Inline expression editor', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual'); - WorkflowPage.actions.addNodeToCanvas('Hacker News'); - WorkflowPage.actions.openNode('Hacker News'); - WorkflowPage.actions.openInlineExpressionEditor(); + WorkflowPage.actions.addInitialNodeToCanvas('Schedule'); + cy.on('uncaught:exception', (err) => err.name !== 'ExpressionError'); }); - it('should resolve primitive resolvables', () => { - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('1 + 2'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^3$/); - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('"ab"'); - WorkflowPage.getters.inlineExpressionEditorInput().type('{rightArrow}+'); - WorkflowPage.getters.inlineExpressionEditorInput().type('"cd"'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^abcd$/); - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('true && false'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^false$/); - }); + describe('Static data', () => { + beforeEach(() => { + WorkflowPage.actions.addNodeToCanvas('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openInlineExpressionEditor(); + }); - it('should resolve object resolvables', () => { - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters - .inlineExpressionEditorInput() - .type('{ a: 1 }', { parseSpecialCharSequences: false }); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Object: \{"a": 1\}\]$/); - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters - .inlineExpressionEditorInput() - .type('{ a: 1 }.a', { parseSpecialCharSequences: false }); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/); - }); + it('should resolve primitive resolvables', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('1 + 2'); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^3$/); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('"ab"'); + WorkflowPage.getters.inlineExpressionEditorInput().type('{rightArrow}+'); + WorkflowPage.getters.inlineExpressionEditorInput().type('"cd"'); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^abcd$/); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('true && false'); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^false$/); + }); - it('should resolve array resolvables', () => { - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Array: \[1,2,3\]\]$/); + it('should resolve object resolvables', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters + .inlineExpressionEditorInput() + .type('{ a: 1 }', { parseSpecialCharSequences: false }); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Object: \{"a": 1\}\]$/); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); - WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters + .inlineExpressionEditorInput() + .type('{ a: 1 }.a', { parseSpecialCharSequences: false }); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/); + }); - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]'); - WorkflowPage.getters.inlineExpressionEditorInput().type('[0]'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/); + it('should resolve array resolvables', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]'); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Array: \[1,2,3\]\]$/); + + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]'); + WorkflowPage.getters.inlineExpressionEditorInput().type('[0]'); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/); + }); }); - it('should resolve $parameter[]', () => { - WorkflowPage.getters.inlineExpressionEditorInput().clear(); - WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); - WorkflowPage.getters.inlineExpressionEditorInput().type('$parameter["operation"]'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^getAll$/); + describe('Dynamic data', () => { + beforeEach(() => { + WorkflowPage.actions.openNode('Schedule Trigger'); + ndv.actions.setPinnedData([{ myStr: 'Monday' }]); + ndv.actions.close(); + WorkflowPage.actions.addNodeToCanvas('No Operation'); + WorkflowPage.actions.addNodeToCanvas('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openInlineExpressionEditor(); + }); + + it('should resolve $parameter[]', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + // Resolving $parameter is slow, especially on CI runner + WorkflowPage.getters.inlineExpressionEditorInput().type('$parameter["operation"]'); + WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'getAll'); + }); + + it('should resolve input: $json,$input,$(nodeName)', () => { + // Previous nodes have not run, input is empty + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr'); + WorkflowPage.getters + .inlineExpressionEditorOutput() + .should('have.text', '[Execute previous nodes for preview]'); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr'); + WorkflowPage.getters + .inlineExpressionEditorOutput() + .should('have.text', '[Execute previous nodes for preview]'); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters + .inlineExpressionEditorInput() + .type("$('Schedule Trigger').item.json.myStr"); + WorkflowPage.getters + .inlineExpressionEditorOutput() + .should('have.text', '[Execute previous nodes for preview]'); + + // Run workflow + ndv.actions.close(); + WorkflowPage.actions.executeNode('No Operation'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openInlineExpressionEditor(); + + // Previous nodes have run, input can be resolved + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr'); + WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday'); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr'); + WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday'); + WorkflowPage.getters.inlineExpressionEditorInput().clear(); + WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); + WorkflowPage.getters + .inlineExpressionEditorInput() + .type("$('Schedule Trigger').item.json.myStr"); + WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday'); + }); }); }); diff --git a/cypress/e2e/12-canvas-actions.cy.ts b/cypress/e2e/12-canvas-actions.cy.ts index 518857106fdcb..3c517b6c9840f 100644 --- a/cypress/e2e/12-canvas-actions.cy.ts +++ b/cypress/e2e/12-canvas-actions.cy.ts @@ -3,7 +3,7 @@ import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, CODE_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME, - SET_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, IF_NODE_NAME, HTTP_REQUEST_NODE_NAME, } from './../constants'; @@ -11,11 +11,6 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; const WorkflowPage = new WorkflowPageClass(); describe('Canvas Actions', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); }); @@ -30,24 +25,29 @@ describe('Canvas Actions', () => { }); it('should connect and disconnect a simple node', () => { - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); WorkflowPage.getters.nodeViewBackground().click(600, 200, { force: true }); cy.get('.jtk-connector').should('have.length', 1); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + + WorkflowPage.getters.nodeViewBackground().click(600, 400, { force: true }); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); // Change connection from Set to Set1 cy.draganddrop( - WorkflowPage.getters.getEndpointSelector('input', SET_NODE_NAME), - WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`), + WorkflowPage.getters.getEndpointSelector('input', EDIT_FIELDS_SET_NODE_NAME), + WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`), ); WorkflowPage.getters - .canvasNodeInputEndpointByName(`${SET_NODE_NAME}1`) + .canvasNodeInputEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}1`) .should('have.class', 'jtk-endpoint-connected'); cy.get('.jtk-connector').should('have.length', 1); // Disconnect Set1 - cy.drag(WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`), [-200, 100]); + cy.drag( + WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`), + [-200, 100], + ); cy.get('.jtk-connector').should('have.length', 0); }); @@ -71,7 +71,6 @@ describe('Canvas Actions', () => { WorkflowPage.getters.nodeViewBackground().click({ force: true }); }); - it('should add a connected node using plus endpoint', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); cy.get('.plus-endpoint').should('be.visible').click(); @@ -83,6 +82,34 @@ describe('Canvas Actions', () => { WorkflowPage.getters.nodeConnections().should('have.length', 1); }); + it('should add a connected node dragging from node creator', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + cy.get('.plus-endpoint').should('be.visible').click(); + WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + WorkflowPage.getters.nodeCreatorSearchBar().type(CODE_NODE_NAME); + cy.drag(WorkflowPage.getters.nodeCreatorNodeItems().first(), [100, 100], { + realMouse: true, + abs: true, + }); + cy.get('body').type('{esc}'); + WorkflowPage.getters.canvasNodes().should('have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); + }); + + it('should open a category when trying to drag and drop it on the canvas', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + cy.get('.plus-endpoint').should('be.visible').click(); + WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + WorkflowPage.getters.nodeCreatorSearchBar().type(CODE_NODE_NAME); + cy.drag(WorkflowPage.getters.nodeCreatorActionItems().first(), [100, 100], { + realMouse: true, + abs: true, + }); + WorkflowPage.getters.nodeCreatorCategoryItems().its('length').should('be.gt', 0); + WorkflowPage.getters.canvasNodes().should('have.length', 1); + WorkflowPage.getters.nodeConnections().should('have.length', 0); + }); + it('should add disconnected node if nothing is selected', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); // Deselect nodes @@ -95,16 +122,21 @@ describe('Canvas Actions', () => { it('should add node between two connected nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); WorkflowPage.actions.zoomToFit(); - WorkflowPage.actions.addNodeBetweenNodes(CODE_NODE_NAME, SET_NODE_NAME, HTTP_REQUEST_NODE_NAME); + WorkflowPage.actions.addNodeBetweenNodes( + CODE_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, + HTTP_REQUEST_NODE_NAME, + ); WorkflowPage.getters.canvasNodes().should('have.length', 4); WorkflowPage.getters.nodeConnections().should('have.length', 3); // And last node should be pushed to the right WorkflowPage.getters .canvasNodes() .last() - .should('have.attr', 'style', 'left: 860px; top: 260px;'); + .should('have.css', 'left', '860px') + .should('have.css', 'top', '220px'); }); it('should delete connections by pressing the delete button', () => { @@ -124,30 +156,64 @@ describe('Canvas Actions', () => { WorkflowPage.getters.nodeConnections().should('have.length', 0); }); - it('should execute node', () => { - WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodes() - .last() - .find('[data-test-id="execute-node-button"]') - .click({ force: true }); - WorkflowPage.getters.successToast().should('contain', 'Node executed successfully'); + describe('Node hover actions', () => { + it('should execute node', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + WorkflowPage.getters + .canvasNodes() + .last() + .findChildByTestId('execute-node-button') + .click({ force: true }); + WorkflowPage.actions.executeNode(CODE_NODE_NAME); + WorkflowPage.getters.successToast().should('have.length', 2); + WorkflowPage.getters.successToast().should('contain.text', 'Node executed successfully'); + }); + + it('should disable and enable node', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + const disableButton = WorkflowPage.getters + .canvasNodes() + .last() + .findChildByTestId('disable-node-button'); + disableButton.click({ force: true }); + WorkflowPage.getters.disabledNodes().should('have.length', 1); + disableButton.click({ force: true }); + WorkflowPage.getters.disabledNodes().should('have.length', 0); + }); + + it('should delete node', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + WorkflowPage.getters + .canvasNodes() + .last() + .find('[data-test-id="delete-node-button"]') + .click({ force: true }); + WorkflowPage.getters.canvasNodes().should('have.length', 1); + }); }); it('should copy selected nodes', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.selectAll(); + WorkflowPage.actions.hitCopy(); WorkflowPage.getters.successToast().should('contain', 'Copied!'); + + WorkflowPage.actions.copyNode(CODE_NODE_NAME); + WorkflowPage.getters.successToast().should('contain', 'Copied!'); }); - it('should select all nodes', () => { + it('should select/deselect all nodes', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.selectAll(); WorkflowPage.getters.selectedNodes().should('have.length', 2); + WorkflowPage.actions.deselectAll(); + WorkflowPage.getters.selectedNodes().should('have.length', 0); }); it('should select nodes using arrow keys', () => { @@ -169,4 +235,29 @@ describe('Canvas Actions', () => { cy.get('body').type('{shift}', { release: false }).type('{leftArrow}'); WorkflowPage.getters.selectedNodes().should('have.length', 2); }); + + it('should not break lasso selection when dragging node action buttons', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters + .canvasNodes() + .last() + .findChildByTestId('execute-node-button') + .as('executeNodeButton'); + cy.drag('@executeNodeButton', [200, 200]); + WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]); + }); + + it('should not break lasso selection with multiple clicks on node action buttons', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]); + WorkflowPage.getters.canvasNodes().last().as('lastNode'); + cy.get('@lastNode').findChildByTestId('execute-node-button').as('executeNodeButton'); + for (let i = 0; i < 20; i++) { + cy.get('@lastNode').realHover(); + cy.get('@executeNodeButton').should('be.visible'); + cy.get('@executeNodeButton').realTouch(); + cy.getByTestId('execute-workflow-button').realHover(); + WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]); + } + }); }); diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 5f2fe8db07c37..c1e06c107d636 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -3,50 +3,61 @@ import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, CODE_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME, - SET_NODE_NAME, + EDIT_FIELDS_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'; +import { NDV, WorkflowExecutionsTab } from '../pages'; const WorkflowPage = new WorkflowPageClass(); - +const ExecutionsTab = new WorkflowExecutionsTab(); +const NDVDialog = new NDV(); const DEFAULT_ZOOM_FACTOR = 1; const ZOOM_IN_X1_FACTOR = 1.25; // Zoom in factor after one click const ZOOM_IN_X2_FACTOR = 1.5625; // Zoom in factor after two clicks const ZOOM_OUT_X1_FACTOR = 0.8; const ZOOM_OUT_X2_FACTOR = 0.64; + +const PINCH_ZOOM_IN_FACTOR = 1.05702; +const PINCH_ZOOM_OUT_FACTOR = 0.946058; const RENAME_NODE_NAME = 'Something else'; +const RENAME_NODE_NAME2 = 'Something different'; describe('Canvas Node Manipulation and Navigation', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); }); - it('should add switch node and test connections', () => { - WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true); + const desiredOutputs = 4; + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true, true); + + for (let i = 0; i < desiredOutputs; i++) { + cy.contains('Add Routing Rule').click(); + } - // Switch has 4 output endpoints - for (let i = 0; i < 4; i++) { + NDVDialog.actions.close(); + for (let i = 0; i < desiredOutputs; i++) { WorkflowPage.getters.canvasNodePlusEndpointByName(SWITCH_NODE_NAME, i).click({ force: true }); WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, false); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); WorkflowPage.actions.zoomToFit(); } + WorkflowPage.getters.nodeViewBackground().click({ force: true }); + WorkflowPage.getters.canvasNodePlusEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}3`).click(); + WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, false); WorkflowPage.actions.saveWorkflowOnButtonClick(); cy.reload(); cy.waitForLoad(); + // Make sure outputless switch was connected correctly + cy.get( + `[data-target-node="${SWITCH_NODE_NAME}1"][data-source-node="${EDIT_FIELDS_SET_NODE_NAME}3"]`, + ).should('be.visible'); // Make sure all connections are there after reload - for (let i = 0; i < 4; i++) { - const setName = `${SET_NODE_NAME}${i > 0 ? i : ''}`; + for (let i = 0; i < desiredOutputs; i++) { + const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`; WorkflowPage.getters .canvasNodeInputEndpointByName(setName) .should('have.class', 'jtk-endpoint-connected'); @@ -57,7 +68,7 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); for (let i = 0; i < 2; i++) { - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); WorkflowPage.getters.nodeViewBackground().click(600 + i * 100, 200, { force: true }); } WorkflowPage.actions.zoomToFit(); @@ -68,18 +79,18 @@ describe('Canvas Node Manipulation and Navigation', () => { // Connect manual to Set1 cy.draganddrop( WorkflowPage.getters.getEndpointSelector('output', MANUAL_TRIGGER_NODE_DISPLAY_NAME), - WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`), + WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`), ); cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 2); // Connect Set1 and Set2 to merge cy.draganddrop( - WorkflowPage.getters.getEndpointSelector('plus', SET_NODE_NAME), + WorkflowPage.getters.getEndpointSelector('plus', EDIT_FIELDS_SET_NODE_NAME), WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 0), ); cy.draganddrop( - WorkflowPage.getters.getEndpointSelector('plus', `${SET_NODE_NAME}1`), + WorkflowPage.getters.getEndpointSelector('plus', `${EDIT_FIELDS_SET_NODE_NAME}1`), WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1), ); @@ -102,7 +113,7 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); for (let i = 0; i < 3; i++) { - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME, true); } WorkflowPage.actions.zoomToFit(); WorkflowPage.actions.executeWorkflow(); @@ -111,7 +122,7 @@ describe('Canvas Node Manipulation and Navigation', () => { cy.get('.data-count').should('have.length', 4); cy.get('.plus-draggable-endpoint').should('have.class', 'ep-success'); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.zoomToFit(); cy.get('.plus-draggable-endpoint').filter(':visible').should('not.have.class', 'ep-success'); @@ -119,13 +130,10 @@ describe('Canvas Node Manipulation and Navigation', () => { cy.get('.jtk-connector').should('have.length', 4); }); - it('should delete node using node action button', () => { + it('should delete node using context menu', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodeByName(CODE_NODE_NAME) - .find('[data-test-id=delete-node-button]') - .click({ force: true }); + WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().should('have.length', 1); WorkflowPage.getters.nodeConnections().should('have.length', 0); }); @@ -142,7 +150,7 @@ describe('Canvas Node Manipulation and Navigation', () => { it('should delete node between two connected nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); WorkflowPage.getters.canvasNodes().should('have.length', 3); WorkflowPage.getters.nodeConnections().should('have.length', 2); WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click(); @@ -152,13 +160,38 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.getters.nodeConnections().should('have.length', 1); }); - it('should delete multiple nodes', () => { + it('should delete multiple nodes (context menu or shortcut)', () => { + WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + cy.wait(500); + WorkflowPage.actions.selectAll(); + cy.get('body').type('{backspace}'); + WorkflowPage.getters.canvasNodes().should('have.length', 0); + + WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + cy.wait(500); + WorkflowPage.actions.selectAllFromContextMenu(); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('delete'); + WorkflowPage.getters.canvasNodes().should('have.length', 0); + }); + + it('should delete multiple nodes (context menu or shortcut)', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); cy.wait(500); WorkflowPage.actions.selectAll(); cy.get('body').type('{backspace}'); WorkflowPage.getters.canvasNodes().should('have.length', 0); + + WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + cy.wait(500); + WorkflowPage.actions.selectAllFromContextMenu(); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('delete'); + WorkflowPage.getters.canvasNodes().should('have.length', 0); }); it('should move node', () => { @@ -166,11 +199,13 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.zoomToFit(); - cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); + + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true }); WorkflowPage.getters .canvasNodes() .last() - .should('have.attr', 'style', 'left: 740px; top: 360px;'); + .should('have.css', 'left', '740px') + .should('have.css', 'top', '320px'); }); it('should zoom in', () => { @@ -211,6 +246,30 @@ describe('Canvas Node Manipulation and Navigation', () => { ); }); + it('should zoom using scroll or pinch gesture', () => { + WorkflowPage.actions.pinchToZoom(1, 'zoomIn'); + WorkflowPage.getters + .nodeView() + .should( + 'have.css', + 'transform', + `matrix(${PINCH_ZOOM_IN_FACTOR}, 0, 0, ${PINCH_ZOOM_IN_FACTOR}, 0, 0)`, + ); + + WorkflowPage.actions.pinchToZoom(1, 'zoomOut'); + // Zoom in 1x + Zoom out 1x should reset to default (=1) + WorkflowPage.getters.nodeView().should('have.css', 'transform', `matrix(1, 0, 0, 1, 0, 0)`); + + WorkflowPage.actions.pinchToZoom(1, 'zoomOut'); + WorkflowPage.getters + .nodeView() + .should( + 'have.css', + 'transform', + `matrix(${PINCH_ZOOM_OUT_FACTOR}, 0, 0, ${PINCH_ZOOM_OUT_FACTOR}, 0, 0)`, + ); + }); + it('should reset zoom', () => { // Reset zoom should not appear until zoom level changed WorkflowPage.getters.resetZoomButton().should('not.exist'); @@ -236,39 +295,59 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.getters.canvasNodes().last().should('be.visible'); }); - it('should disable node by pressing the disable button', () => { - WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); - WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); - WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodes() - .last() - .find('[data-test-id="disable-node-button"]') - .click({ force: true }); - WorkflowPage.getters.disabledNodes().should('have.length', 1); - }); - - it('should disable node using keyboard shortcut', () => { + it('should disable node (context menu or shortcut)', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().last().click(); WorkflowPage.actions.hitDisableNodeShortcut(); WorkflowPage.getters.disabledNodes().should('have.length', 1); + + WorkflowPage.actions.disableNode(CODE_NODE_NAME); + WorkflowPage.getters.disabledNodes().should('have.length', 0); }); - it('should disable multiple nodes', () => { + it('should disable multiple nodes (context menu or shortcut)', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); cy.get('body').type('{esc}'); cy.get('body').type('{esc}'); + + // Keyboard shortcut WorkflowPage.actions.selectAll(); WorkflowPage.actions.hitDisableNodeShortcut(); WorkflowPage.getters.disabledNodes().should('have.length', 2); + WorkflowPage.actions.hitDisableNodeShortcut(); + WorkflowPage.getters.disabledNodes().should('have.length', 0); + WorkflowPage.actions.deselectAll(); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); + WorkflowPage.actions.hitDisableNodeShortcut(); + WorkflowPage.getters.disabledNodes().should('have.length', 1); + WorkflowPage.actions.selectAll(); + WorkflowPage.actions.hitDisableNodeShortcut(); + WorkflowPage.getters.disabledNodes().should('have.length', 2); + + // Context menu + WorkflowPage.actions.selectAll(); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('toggle_activation'); + WorkflowPage.getters.disabledNodes().should('have.length', 0); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('toggle_activation'); + WorkflowPage.getters.disabledNodes().should('have.length', 2); + WorkflowPage.actions.deselectAll(); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('toggle_activation'); + WorkflowPage.getters.disabledNodes().should('have.length', 1); + WorkflowPage.actions.selectAll(); + WorkflowPage.actions.openContextMenu(); + WorkflowPage.actions.contextMenuAction('toggle_activation'); + WorkflowPage.getters.disabledNodes().should('have.length', 2); }); - it('should rename node using keyboard shortcut', () => { + it('should rename node (context menu or shortcut)', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().last().click(); @@ -277,18 +356,97 @@ describe('Canvas Node Manipulation and Navigation', () => { cy.get('body').type(RENAME_NODE_NAME); cy.get('body').type('{enter}'); WorkflowPage.getters.canvasNodeByName(RENAME_NODE_NAME).should('exist'); + + WorkflowPage.actions.renameNode(RENAME_NODE_NAME); + cy.get('.rename-prompt').should('be.visible'); + cy.get('body').type(RENAME_NODE_NAME2); + cy.get('body').type('{enter}'); + WorkflowPage.getters.canvasNodeByName(RENAME_NODE_NAME2).should('exist'); }); - it('should duplicate node', () => { + it('should duplicate nodes (context menu or shortcut)', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters - .canvasNodes() - .last() - .find('[data-test-id="duplicate-node-button"]') - .click({ force: true }); + WorkflowPage.actions.duplicateNode(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().should('have.length', 3); WorkflowPage.getters.nodeConnections().should('have.length', 1); + + WorkflowPage.actions.selectAll(); + WorkflowPage.actions.hitDuplicateNodeShortcut(); + WorkflowPage.getters.canvasNodes().should('have.length', 5); + }); + + // ADO-1240: Connections would get deleted after activating and deactivating NodeView + it('should preserve connections after rename & node-view switch', () => { + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + WorkflowPage.actions.executeWorkflow(); + + ExecutionsTab.actions.switchToExecutionsTab(); + ExecutionsTab.getters.successfulExecutionListItems().should('have.length', 1); + + ExecutionsTab.actions.switchToEditorTab(); + + ExecutionsTab.actions.switchToExecutionsTab(); + ExecutionsTab.getters.successfulExecutionListItems().should('have.length', 1); + + ExecutionsTab.actions.switchToEditorTab(); + WorkflowPage.getters.canvasNodes().should('have.length', 2); + + WorkflowPage.getters.canvasNodes().last().click(); + cy.get('body').trigger('keydown', { key: 'F2' }); + cy.get('.rename-prompt').should('be.visible'); + cy.get('body').type(RENAME_NODE_NAME); + cy.get('body').type('{enter}'); + WorkflowPage.getters.canvasNodeByName(RENAME_NODE_NAME).should('exist'); + // Make sure all connections are there after save & reload + WorkflowPage.actions.saveWorkflowOnButtonClick(); + cy.reload(); + cy.waitForLoad(); + WorkflowPage.getters.canvasNodes().should('have.length', 2); + cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 1); + }); + + it('should remove unknown credentials on pasting workflow', () => { + cy.fixture('workflow-with-unknown-credentials.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + + WorkflowPage.getters.canvasNodes().should('have.have.length', 2); + + WorkflowPage.actions.openNodeFromContextMenu('n8n'); + cy.get('[class*=hasIssues]').should('have.length', 1); + NDVDialog.actions.close(); + }); + }); + + it('should render connections correctly if unkown nodes are present', () => { + const unknownNodeName = 'Unknown node'; + cy.createFixtureWorkflow('workflow-with-unknown-nodes.json', 'Unknown nodes'); + + WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 1`).should('exist'); + WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 2`).should('exist'); + WorkflowPage.actions.zoomToFit(); + + cy.draganddrop( + WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 1`), + WorkflowPage.getters.getEndpointSelector('input', EDIT_FIELDS_SET_NODE_NAME), + ); + + cy.draganddrop( + WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 2`), + WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`), + ); + + WorkflowPage.actions.executeWorkflow(); + cy.contains('Unrecognized node type').should('be.visible'); + + WorkflowPage.actions.deselectAll(); + WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 1`); + WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 2`); + + WorkflowPage.actions.executeWorkflow(); + + cy.contains('Unrecognized node type').should('not.exist'); }); }); diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts index 6f1c328fd9c6b..a9ccc7881857e 100644 --- a/cypress/e2e/13-pinning.cy.ts +++ b/cypress/e2e/13-pinning.cy.ts @@ -1,8 +1,9 @@ import { HTTP_REQUEST_NODE_NAME, - MANUAL_TRIGGER_NODE_DISPLAY_NAME, + MANUAL_TRIGGER_NODE_NAME, PIPEDRIVE_NODE_NAME, - SET_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, + BACKEND_BASE_URL, } from '../constants'; import { WorkflowPage, NDV } from '../pages'; @@ -10,11 +11,6 @@ const workflowPage = new WorkflowPage(); const ndv = new NDV(); describe('Data pinning', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { workflowPage.actions.visit(); }); @@ -67,15 +63,89 @@ describe('Data pinning', () => { workflowPage.actions.saveWorkflowOnButtonClick(); - cy.reload(); workflowPage.actions.openNode('Schedule Trigger'); ndv.getters.outputTableHeaders().first().should('include.text', 'test'); ndv.getters.outputTbodyCell(1, 0).should('include.text', 1); }); + it('should display pin data edit button for Webhook node', () => { + workflowPage.actions.addInitialNodeToCanvas('Webhook', { keepNdvOpen: true }); + + ndv.getters + .runDataPaneHeader() + .find('button') + .filter(':visible') + .should('have.attr', 'title', 'Edit Output'); + }); + + it('Should be duplicating pin data when duplicating node', () => { + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true); + ndv.getters.container().should('be.visible'); + ndv.getters.pinDataButton().should('not.exist'); + ndv.getters.editPinnedDataButton().should('be.visible'); + + ndv.actions.setPinnedData([{ test: 1 }]); + ndv.actions.close(); + + workflowPage.actions.duplicateNode(EDIT_FIELDS_SET_NODE_NAME); + + workflowPage.actions.saveWorkflowOnButtonClick(); + + workflowPage.actions.openNode('Edit Fields1'); + + ndv.getters.outputTableHeaders().first().should('include.text', 'test'); + ndv.getters.outputTbodyCell(1, 0).should('include.text', 1); + }); + + it('Should be able to pin data from canvas (context menu or shortcut)', () => { + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + workflowPage.actions.openContextMenu(EDIT_FIELDS_SET_NODE_NAME, 'overflow-button'); + workflowPage.getters + .contextMenuAction('toggle_pin') + .parent() + .should('have.class', 'is-disabled'); + + // Unpin using context menu + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.actions.setPinnedData([{ test: 1 }]); + ndv.actions.close(); + workflowPage.actions.pinNode(EDIT_FIELDS_SET_NODE_NAME); + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.getters.nodeOutputHint().should('exist'); + ndv.actions.close(); + + // Unpin using shortcut + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.actions.setPinnedData([{ test: 1 }]); + ndv.actions.close(); + workflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click(); + workflowPage.actions.hitPinNodeShortcut(); + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.getters.nodeOutputHint().should('exist'); + }); + + it('Should show an error when maximum pin data size is exceeded', () => { + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true); + ndv.getters.container().should('be.visible'); + ndv.getters.pinDataButton().should('not.exist'); + ndv.getters.editPinnedDataButton().should('be.visible'); + + ndv.actions.pastePinnedData([ + { + test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE')), + }, + ]); + workflowPage.getters + .errorToast() + .should('contain', 'Workflow has reached the maximum allowed pinned data size'); + }); + it('Should be able to reference paired items in a node located before pinned data', () => { - workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_DISPLAY_NAME); + workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true); ndv.actions.setPinnedData([{ http: 123 }]); ndv.actions.close(); @@ -84,24 +154,58 @@ describe('Data pinning', () => { ndv.actions.setPinnedData(Array(3).fill({ pipedrive: 123 })); ndv.actions.close(); - workflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true, true); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true); setExpressionOnStringValueInSet(`{{ $('${HTTP_REQUEST_NODE_NAME}').item`); const output = '[Object: {"json": {"http": 123}, "pairedItem": {"item": 0}}]'; cy.get('div').contains(output).should('be.visible'); }); + + it('should use pin data in manual executions that are started by a webhook', () => { + cy.createFixtureWorkflow('Test_workflow_webhook_with_pin_data.json', 'Test'); + + workflowPage.actions.executeWorkflow(); + + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/b0d79ddb-df2d-49b1-8555-9fa2b482608f`).then( + (response) => { + expect(response.status).to.eq(200); + }, + ); + + workflowPage.actions.openNode('End'); + + ndv.getters.outputTableRow(1).should('exist'); + ndv.getters.outputTableRow(1).should('have.text', 'pin-overwritten'); + }); + + it('should not use pin data in production executions that are started by a webhook', () => { + cy.createFixtureWorkflow('Test_workflow_webhook_with_pin_data.json', 'Test'); + + workflowPage.actions.activateWorkflow(); + cy.request('GET', `${BACKEND_BASE_URL}/webhook/b0d79ddb-df2d-49b1-8555-9fa2b482608f`).then( + (response) => { + expect(response.status).to.eq(200); + // Assert that we get the data hard coded in the edit fields node, + // instead of the data pinned in said node. + expect(response.body).to.deep.equal({ + nodeData: 'pin', + }); + }, + ); + }); }); function setExpressionOnStringValueInSet(expression: string) { - cy.get('button').contains('Execute node').click(); - cy.get('input[placeholder="Add Value"]').click(); - cy.get('span').contains('String').click(); + cy.get('button').contains('Test step').click(); - ndv.getters.nthParam(3).contains('Expression').invoke('show').click(); + ndv.getters.assignmentCollectionAdd('assignments').click(); + ndv.getters.assignmentValue('assignments').contains('Expression').invoke('show').click(); ndv.getters .inlineExpressionEditorInput() .clear() - .type(expression, { parseSpecialCharSequences: false }); + .type(expression, { parseSpecialCharSequences: false }) + // hide autocomplete + .type('{esc}'); } diff --git a/cypress/e2e/1338-ADO-ndv-missing-input-panel.cy.ts b/cypress/e2e/1338-ADO-ndv-missing-input-panel.cy.ts new file mode 100644 index 0000000000000..046d4d809d1bd --- /dev/null +++ b/cypress/e2e/1338-ADO-ndv-missing-input-panel.cy.ts @@ -0,0 +1,25 @@ +import { v4 as uuid } from 'uuid'; +import { NDV, WorkflowPage as WorkflowPageClass } from '../pages'; + +const workflowPage = new WorkflowPageClass(); +const ndv = new NDV(); + +describe('ADO-1338-ndv-missing-input-panel', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it('should show the input and output panels when node is missing input and output data', () => { + cy.createFixtureWorkflow('Test_ado_1338.json', uuid()); + + // Execute the workflow + workflowPage.getters.zoomToFitButton().click(); + workflowPage.getters.executeWorkflowButton().click(); + // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished) + workflowPage.getters.successToast().should('be.visible'); + + workflowPage.actions.openNode('Discourse1'); + ndv.getters.inputPanel().should('be.visible'); + ndv.getters.outputPanel().should('be.visible'); + }); +}); diff --git a/cypress/e2e/14-data-transformation-expressions.cy.ts b/cypress/e2e/14-data-transformation-expressions.cy.ts index cb08d51e5b1e8..21c958d691fec 100644 --- a/cypress/e2e/14-data-transformation-expressions.cy.ts +++ b/cypress/e2e/14-data-transformation-expressions.cy.ts @@ -1,35 +1,25 @@ import { WorkflowPage, NDV } from '../pages'; +import { getVisibleSelect } from '../utils'; const wf = new WorkflowPage(); const ndv = new NDV(); describe('Data transformation expressions', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { wf.actions.visit(); - - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); }); it('$json + native string methods', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myStr: 'Monday' }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myStr.toLowerCase() + " is " + "today".toUpperCase()'; const output = 'monday is TODAY'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); ndv.getters.outputDataContainer().should('be.visible'); ndv.getters.outputDataContainer().contains(output); @@ -39,12 +29,13 @@ describe('Data transformation expressions', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myStr: 'hello@n8n.io is an email' }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myStr.extractEmail() + " " + $json.myStr.isEmpty()'; const output = 'hello@n8n.io false'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); ndv.getters.outputDataContainer().should('be.visible'); ndv.getters.outputDataContainer().contains(output); @@ -54,12 +45,13 @@ describe('Data transformation expressions', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myNum: 9.123 }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myNum.toPrecision(3)'; const output = '9.12'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); ndv.getters.outputDataContainer().should('be.visible'); ndv.getters.outputDataContainer().contains(output); @@ -69,12 +61,13 @@ describe('Data transformation expressions', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myStr: 'hello@n8n.io is an email' }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myStr.extractEmail() + " " + $json.myStr.isEmpty()'; const output = 'hello@n8n.io false'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); ndv.getters.outputDataContainer().should('be.visible'); ndv.getters.outputDataContainer().contains(output); @@ -84,13 +77,14 @@ describe('Data transformation expressions', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myArr: [1, 2, 3] }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myArr.includes(1) + " " + $json.myArr[2]'; const output = 'true 3'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); - ndv.getters.outputDataContainer().find('[class*=value_]').should('exist') + ndv.getters.outputDataContainer().find('[class*=value_]').should('exist'); ndv.getters.outputDataContainer().find('[class*=value_]').should('contain', output); }); @@ -98,14 +92,15 @@ describe('Data transformation expressions', () => { wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true }); ndv.actions.setPinnedData([{ myArr: [1, 2, 3] }]); ndv.actions.close(); - addSet(); + addEditFields(); const input = '{{$json.myArr.first() + " " + $json.myArr.last()'; const output = '1 3'; ndv.getters.inlineExpressionEditorInput().clear().type(input); + ndv.getters.inlineExpressionEditorOutput().should('have.text', output); ndv.actions.execute(); - ndv.getters.outputDataContainer().find('[class*=value_]').should('exist') + ndv.getters.outputDataContainer().find('[class*=value_]').should('exist'); ndv.getters.outputDataContainer().find('[class*=value_]').should('contain', output); }); }); @@ -114,10 +109,8 @@ describe('Data transformation expressions', () => { // utils // ---------------------------------- -const addSet = () => { - wf.actions.addNodeToCanvas('Set', true, true); - ndv.getters.parameterInput('keepOnlySet').find('div[role=switch]').click(); // shorten output - cy.get('input[placeholder="Add Value"]').click(); - cy.get('span').contains('String').click(); - ndv.getters.nthParam(3).contains('Expression').invoke('show').click(); // Values to Set > String > Value +const addEditFields = () => { + wf.actions.addNodeToCanvas('Edit Fields', true, true); + ndv.getters.assignmentCollectionAdd('assignments').click(); + ndv.getters.assignmentValue('assignments').contains('Expression').invoke('show').click(); }; diff --git a/cypress/e2e/14-mapping.cy.ts b/cypress/e2e/14-mapping.cy.ts index 8c4d6f9cdbe05..bcfeb71ec70a4 100644 --- a/cypress/e2e/14-mapping.cy.ts +++ b/cypress/e2e/14-mapping.cy.ts @@ -4,25 +4,14 @@ import { SCHEDULE_TRIGGER_NODE_NAME, } from './../constants'; import { WorkflowPage, NDV } from '../pages'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); describe('Data mapping', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { workflowPage.actions.visit(); - - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); }); it('maps expressions from table header', () => { @@ -35,11 +24,7 @@ describe('Data mapping', () => { ndv.getters.inputDataContainer().get('table', { timeout: 10000 }).should('exist'); ndv.getters.nodeParameters().find('input[placeholder*="Add Value"]').click(); - ndv.getters - .nodeParameters() - .find('.el-select-dropdown__list li:nth-child(3)') - .should('have.text', 'String') - .click(); + getVisibleSelect().find('li:nth-child(3)').should('have.text', 'String').click(); ndv.getters .parameterInput('name') .should('have.length', 1) @@ -95,14 +80,14 @@ describe('Data mapping', () => { .parameterExpressionPreview('value') .should('include.text', '0') .invoke('css', 'color') - .should('equal', 'rgb(125, 125, 135)'); + .should('equal', 'rgb(113, 116, 122)'); ndv.getters.inputTbodyCell(2, 0).realHover(); ndv.getters .parameterExpressionPreview('value') .should('include.text', '1') .invoke('css', 'color') - .should('equal', 'rgb(125, 125, 135)'); + .should('equal', 'rgb(113, 116, 122)'); ndv.actions.execute(); @@ -111,14 +96,14 @@ describe('Data mapping', () => { .parameterExpressionPreview('value') .should('include.text', '0') .invoke('css', 'color') - .should('equal', 'rgb(125, 125, 135)'); // todo update color + .should('equal', 'rgb(113, 116, 122)'); // todo update color ndv.getters.outputTbodyCell(2, 0).realHover(); ndv.getters .parameterExpressionPreview('value') .should('include.text', '1') .invoke('css', 'color') - .should('equal', 'rgb(125, 125, 135)'); + .should('equal', 'rgb(113, 116, 122)'); }); it('maps expressions from json view', () => { @@ -135,7 +120,7 @@ describe('Data mapping', () => { .find('.json-data') .should( 'have.text', - '[{"input":[{"count":0,"with space":"!!","with.dot":"!!","with"quotes":"!!"}]},{"input":[{"count":1}]}]', + '[{"input": [{"count": 0,"with space": "!!","with.dot": "!!","with"quotes": "!!"}]},{"input": [{"count": 1}]}]', ) .find('span') .contains('"count"') @@ -156,7 +141,7 @@ describe('Data mapping', () => { ndv.getters .inlineExpressionEditorInput() .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0 [object Object]'); + ndv.actions.validateExpressionPreview('value', '0 [object Object]'); }); it('maps expressions from schema view', () => { @@ -172,7 +157,7 @@ describe('Data mapping', () => { ndv.actions.mapToParameter('value'); ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0'); + ndv.actions.validateExpressionPreview('value', '0'); ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown(); @@ -180,11 +165,12 @@ describe('Data mapping', () => { ndv.getters .inlineExpressionEditorInput() .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0 [object Object]'); + ndv.actions.validateExpressionPreview('value', '0 [object Object]'); }); it('maps expressions from previous nodes', () => { cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`); + workflowPage.actions.zoomToFit(); workflowPage.actions.openNode('Set1'); ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME); @@ -194,8 +180,7 @@ describe('Data mapping', () => { ndv.actions.mapToParameter('value'); ndv.getters .inlineExpressionEditorInput() - .should('have.text', `{{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input[0].count }}`); - ndv.getters.parameterExpressionPreview('value').should('not.exist'); + .should('have.text', `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`); ndv.actions.switchInputMode('Table'); ndv.actions.mapDataFromHeader(1, 'value'); @@ -203,26 +188,25 @@ describe('Data mapping', () => { .inlineExpressionEditorInput() .should( 'have.text', - `{{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input[0].count }} {{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input }}`, + `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }} {{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input }}`, ); - ndv.getters.parameterExpressionPreview('value').should('include.text', '[empty]'); ndv.actions.selectInputNode('Set'); ndv.actions.executePrevious(); ndv.getters.executingLoader().should('not.exist'); ndv.getters.inputDataContainer().should('exist'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0 [object Object]'); + ndv.actions.validateExpressionPreview('value', '0 [object Object]'); ndv.getters.inputTbodyCell(2, 0).realHover(); - ndv.getters.parameterExpressionPreview('value').should('include.text', '1 [object Object]'); + ndv.actions.validateExpressionPreview('value', '1 [object Object]'); }); it('maps keys to path', () => { workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); workflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); workflowPage.actions.openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME); - ndv.actions.setPinnedData([ + ndv.actions.pastePinnedData([ { input: [ { @@ -245,10 +229,8 @@ describe('Data mapping', () => { ndv.actions.close(); - workflowPage.actions.addNodeToCanvas('Item Lists'); - workflowPage.actions.openNode('Item Lists'); - - ndv.getters.parameterInput('operation').click().find('li').contains('Sort').click(); + workflowPage.actions.addNodeToCanvas('Sort'); + workflowPage.actions.openNode('Sort'); ndv.getters.nodeParameters().find('button').contains('Add Field To Sort By').click(); @@ -271,18 +253,19 @@ describe('Data mapping', () => { workflowPage.actions.openNode('Set'); ndv.actions.typeIntoParameterInput('value', 'delete me'); - ndv.actions.dismissMappingTooltip(); ndv.actions.typeIntoParameterInput('name', 'test'); + ndv.getters.parameterInput('name').find('input').blur(); ndv.actions.typeIntoParameterInput('value', 'fun'); ndv.actions.clearParameterInput('value'); // keep focus on param + cy.wait(300); ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown(); ndv.actions.mapToParameter('value'); ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $json.input[0].count }}'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0'); + ndv.actions.validateExpressionPreview('value', '0'); ndv.getters.inputDataContainer().find('span').contains('input').realMouseDown(); @@ -290,7 +273,34 @@ describe('Data mapping', () => { ndv.getters .inlineExpressionEditorInput() .should('have.text', '{{ $json.input[0].count }} {{ $json.input }}'); - ndv.getters.parameterExpressionPreview('value').should('include.text', '0 [object Object]'); + ndv.actions.validateExpressionPreview('value', '0 [object Object]'); + }); + + it('renders expression preview when a previous node is selected', () => { + cy.fixture('Test_workflow_3.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + + workflowPage.actions.openNode('Set'); + ndv.actions.typeIntoParameterInput('value', 'test_value'); + ndv.actions.typeIntoParameterInput('name', '{selectall}test_name'); + ndv.actions.close(); + + workflowPage.actions.openNode('Set1'); + ndv.actions.executePrevious(); + ndv.getters.executingLoader().should('not.exist'); + ndv.getters.inputDataContainer().should('exist'); + ndv.getters + .inputDataContainer() + .should('exist') + .find('span') + .contains('test_name') + .realMouseDown(); + ndv.actions.mapToParameter('value'); + + ndv.actions.validateExpressionPreview('value', 'test_value'); + ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME); + ndv.actions.validateExpressionPreview('value', 'test_value'); }); it('shows you can drop to inputs, including booleans', () => { @@ -302,21 +312,33 @@ describe('Data mapping', () => { ndv.actions.clearParameterInput('value'); cy.get('body').type('{esc}'); - ndv.getters.parameterInput('keepOnlySet').find('input[type="checkbox"]').should('exist'); - ndv.getters.parameterInput('keepOnlySet').find('input[type="text"]').should('not.exist'); - ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown().realMouseMove(100, 100); + ndv.getters.parameterInput('includeOtherFields').find('input[type="checkbox"]').should('exist'); + ndv.getters.parameterInput('includeOtherFields').find('input[type="text"]').should('not.exist'); + ndv.getters + .inputDataContainer() + .should('exist') + .find('span') + .contains('count') + .realMouseDown() + .realMouseMove(100, 100); cy.wait(50); - ndv.getters.parameterInput('keepOnlySet').find('input[type="checkbox"]').should('not.exist'); - ndv.getters.parameterInput('keepOnlySet').find('input[type="text"]') + ndv.getters + .parameterInput('includeOtherFields') + .find('input[type="checkbox"]') + .should('not.exist'); + ndv.getters + .parameterInput('includeOtherFields') + .find('input[type="text"]') .should('exist') .invoke('css', 'border') - .then((border) => expect(border).to.include('1.5px dashed rgb(90, 76, 194)')); + .then((border) => expect(border).to.include('dashed rgb(90, 76, 194)')); - ndv.getters.parameterInput('value').find('input[type="text"]') - .should('exist') - .invoke('css', 'border') - .then((border) => expect(border).to.include('1.5px dashed rgb(90, 76, 194)')); + ndv.getters + .parameterInput('value') + .find('input[type="text"]') + .should('exist') + .invoke('css', 'border') + .then((border) => expect(border).to.include('dashed rgb(90, 76, 194)')); }); - }); diff --git a/cypress/e2e/15-scheduler-node.cy.ts b/cypress/e2e/15-scheduler-node.cy.ts index ae3de65c26749..0021455619811 100644 --- a/cypress/e2e/15-scheduler-node.cy.ts +++ b/cypress/e2e/15-scheduler-node.cy.ts @@ -1,4 +1,6 @@ import { WorkflowPage, WorkflowsPage, NDV } from '../pages'; +import { BACKEND_BASE_URL } from '../constants'; +import { getVisibleSelect } from '../utils'; const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPage(); @@ -6,8 +8,7 @@ const ndv = new NDV(); describe('Schedule Trigger node', async () => { beforeEach(() => { - cy.resetAll(); - cy.skipSetup(); + workflowPage.actions.visit(); }); it('should execute and return the execution timestamp', () => { @@ -24,11 +25,7 @@ describe('Schedule Trigger node', async () => { workflowPage.actions.openNode('Schedule Trigger'); cy.getByTestId('parameter-input-field').click(); - cy.getByTestId('parameter-input-field') - .find('.el-select-dropdown') - .find('.option-headline') - .contains('Seconds') - .click(); + getVisibleSelect().find('.option-headline').contains('Seconds').click(); cy.getByTestId('parameter-input-secondsInterval').clear().type('1'); ndv.getters.backToCanvas().click(); @@ -36,44 +33,34 @@ describe('Schedule Trigger node', async () => { workflowPage.actions.activateWorkflow(); workflowPage.getters.activatorSwitch().should('have.class', 'is-checked'); - cy.request('GET', '/rest/workflows') - .then((response) => { + cy.url().then((url) => { + const workflowId = url.split('/').pop(); + + cy.wait(1200); + cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then((response) => { expect(response.status).to.eq(200); - expect(response.body.data).to.have.length(1); - const workflowId = response.body.data[0].id.toString(); - expect(workflowId).to.not.be.empty; - return workflowId; - }) - .then((workflowId) => { + expect(workflowId).to.not.be.undefined; + expect(response.body.data.results.length).to.be.greaterThan(0); + const matchingExecutions = response.body.data.results.filter( + (execution: any) => execution.workflowId === workflowId, + ); + expect(matchingExecutions).to.have.length(1); + cy.wait(1200); - cy.request('GET', '/rest/executions') - .then((response) => { - expect(response.status).to.eq(200); - expect(response.body.data.results.length).to.be.greaterThan(0); - const matchingExecutions = response.body.data.results.filter( - (execution: any) => execution.workflowId === workflowId, - ); - expect(matchingExecutions).to.have.length(1); - return workflowId; - }) - .then((workflowId) => { - cy.wait(1200); - cy.request('GET', '/rest/executions') - .then((response) => { - expect(response.status).to.eq(200); - expect(response.body.data.results.length).to.be.greaterThan(0); - const matchingExecutions = response.body.data.results.filter( - (execution: any) => execution.workflowId === workflowId, - ); - expect(matchingExecutions).to.have.length(2); - }) - .then(() => { - workflowPage.actions.activateWorkflow(); - workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked'); - cy.visit(workflowsPage.url); - workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow'); - }); - }); + cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then((response) => { + expect(response.status).to.eq(200); + expect(response.body.data.results.length).to.be.greaterThan(0); + const matchingExecutions = response.body.data.results.filter( + (execution: any) => execution.workflowId === workflowId, + ); + expect(matchingExecutions).to.have.length(2); + + workflowPage.actions.activateWorkflow(); + workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked'); + cy.visit(workflowsPage.url); + workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow'); + }); }); + }); }); }); diff --git a/cypress/e2e/16-form-trigger-node.cy.ts b/cypress/e2e/16-form-trigger-node.cy.ts new file mode 100644 index 0000000000000..0162479f7c559 --- /dev/null +++ b/cypress/e2e/16-form-trigger-node.cy.ts @@ -0,0 +1,101 @@ +import { WorkflowPage, NDV } from '../pages'; +import { getVisibleSelect } from '../utils'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); + +describe('n8n Form Trigger', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it("add node by clicking on 'On form submission'", () => { + workflowPage.getters.canvasPlusButton().click(); + workflowPage.getters.nodeCreatorNodeItems().contains('On form submission').click(); + ndv.getters.parameterInput('formTitle').type('Test Form'); + ndv.getters.parameterInput('formDescription').type('Test Form Description'); + ndv.getters.parameterInput('fieldLabel').type('Test Field 1'); + ndv.getters.backToCanvas().click(); + workflowPage.getters.nodeIssuesByName('n8n Form Trigger').should('not.exist'); + }); + + it('should fill up form fields', () => { + workflowPage.actions.addInitialNodeToCanvas('n8n Form Trigger'); + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.parameterInput('formTitle').type('Test Form'); + ndv.getters.parameterInput('formDescription').type('Test Form Description'); + //fill up first field of type number + ndv.getters.parameterInput('fieldLabel').type('Test Field 1'); + ndv.getters.parameterInput('fieldType').click(); + getVisibleSelect().contains('Number').click(); + cy.get( + '[data-test-id="parameter-input-requiredField"] > .parameter-input > .el-switch > .el-switch__core', + ).click(); + //fill up second field of type text + cy.get('.fixed-collection-parameter > :nth-child(2) > .button > span').click(); + cy.get('.border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item') + .find('input[placeholder*="e.g. What is your name?"]') + .type('Test Field 2'); + //fill up second field of type date + cy.get('.fixed-collection-parameter > :nth-child(2) > .button > span').click(); + cy.get( + ':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item', + ) + .find('input[placeholder*="e.g. What is your name?"]') + .type('Test Field 3') + .blur(); + cy.get( + ':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item', + ).click(); + getVisibleSelect().contains('Date').click(); + // fill up second field of type dropdown + cy.get('.fixed-collection-parameter > :nth-child(2) > .button').click(); + cy.get( + ':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item', + ) + .find('input[placeholder*="e.g. What is your name?"]') + .type('Test Field 4') + .blur(); + cy.get( + ':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item', + ).click(); + getVisibleSelect().contains('Dropdown').click(); + cy.get( + '.border-top-dashed > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > :nth-child(2) > .button', + ).click(); + cy.get( + ':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(1)', + ) + .find('input') + .type('Option 1') + .blur(); + cy.get( + ':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(2)', + ) + .find('input') + .type('Option 2') + .blur(); + + //add optional submitted message + cy.get('.param-options').click(); + getVisibleSelect().find('span').contains('Form Response').click(); + cy.contains('span', 'Text to Show') + .should('exist') + .parent() + .parent() + .next() + .children() + .children() + .children() + .children() + .children() + .children() + .children() + .first() + .clear() + .type('Your test form was successfully submitted'); + + ndv.getters.backToCanvas().click(); + workflowPage.getters.nodeIssuesByName('n8n Form Trigger').should('not.exist'); + }); +}); diff --git a/cypress/e2e/16-webhook-node.cy.ts b/cypress/e2e/16-webhook-node.cy.ts index d2bceaf22faa1..560fc41056b30 100644 --- a/cypress/e2e/16-webhook-node.cy.ts +++ b/cypress/e2e/16-webhook-node.cy.ts @@ -1,6 +1,8 @@ import { WorkflowPage, NDV, CredentialsModal } from '../pages'; import { v4 as uuid } from 'uuid'; import { cowBase64 } from '../support/binaryTestFiles'; +import { BACKEND_BASE_URL, EDIT_FIELDS_SET_NODE_NAME } from '../constants'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -33,11 +35,7 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { workflowPage.actions.openNode('Webhook'); cy.getByTestId('parameter-input-httpMethod').click(); - cy.getByTestId('parameter-input-httpMethod') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(method) - .click(); + getVisibleSelect().find('.option-headline').contains(method).click(); cy.getByTestId('parameter-input-path') .find('.parameter-input') .find('input') @@ -46,44 +44,31 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { if (authentication) { cy.getByTestId('parameter-input-authentication').click(); - cy.getByTestId('parameter-input-authentication') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(authentication) - .click(); + getVisibleSelect().find('.option-headline').contains(authentication).click(); } if (responseCode) { - cy.getByTestId('parameter-input-responseCode') - .find('.parameter-input') - .find('input') - .clear() - .type(responseCode.toString()); + cy.get('.param-options').click(); + getVisibleSelect().contains('Response Code').click(); + cy.get('.parameter-item-wrapper > .parameter-input-list-wrapper').children().click(); + getVisibleSelect().contains('201').click(); } if (respondWith) { cy.getByTestId('parameter-input-responseMode').click(); - cy.getByTestId('parameter-input-responseMode') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(respondWith) - .click(); + getVisibleSelect().find('.option-headline').contains(respondWith).click(); } if (responseData) { cy.getByTestId('parameter-input-responseData').click(); - cy.getByTestId('parameter-input-responseData') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(responseData) - .click(); + getVisibleSelect().find('.option-headline').contains(responseData).click(); } if (executeNow) { ndv.actions.execute(); cy.wait(waitForWebhook); - cy.request(method, '/webhook-test/' + webhookPath).then((response) => { + cy.request(method, `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(200); ndv.getters.outputPanel().contains('headers'); }); @@ -91,20 +76,8 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { }; describe('Webhook Trigger node', async () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { workflowPage.actions.visit(); - - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); }); it('should listen for a GET request', () => { @@ -139,23 +112,16 @@ describe('Webhook Trigger node', async () => { ndv.getters.backToCanvas().click(); - workflowPage.actions.addNodeToCanvas('Set'); - workflowPage.actions.openNode('Set'); - cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('Number').click(); - cy.get('.fixed-collection-parameter') - .getByTestId('parameter-input-name') - .clear() - .type('MyValue'); - cy.get('.fixed-collection-parameter').getByTestId('parameter-input-value').clear().type('1234'); - ndv.getters.backToCanvas().click(); + addEditFields(); + + ndv.getters.backToCanvas().click({ force: true }); workflowPage.actions.addNodeToCanvas('Respond to Webhook'); workflowPage.actions.executeWorkflow(); cy.wait(waitForWebhook); - cy.request('GET', '/webhook-test/' + webhookPath).then((response) => { + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(200); expect(response.body.MyValue).to.eq(1234); }); @@ -173,7 +139,7 @@ describe('Webhook Trigger node', async () => { ndv.actions.execute(); cy.wait(waitForWebhook); - cy.request('GET', '/webhook-test/' + webhookPath).then((response) => { + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(201); }); }); @@ -188,21 +154,14 @@ describe('Webhook Trigger node', async () => { }); ndv.getters.backToCanvas().click(); - workflowPage.actions.addNodeToCanvas('Set'); - workflowPage.actions.openNode('Set'); - cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('Number').click(); - cy.get('.fixed-collection-parameter') - .getByTestId('parameter-input-name') - .clear() - .type('MyValue'); - cy.get('.fixed-collection-parameter').getByTestId('parameter-input-value').clear().type('1234'); - ndv.getters.backToCanvas().click(); + addEditFields(); + + ndv.getters.backToCanvas().click({ force: true }); workflowPage.actions.executeWorkflow(); cy.wait(waitForWebhook); - cy.request('GET', '/webhook-test/' + webhookPath).then((response) => { + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(200); expect(response.body.MyValue).to.eq(1234); }); @@ -219,35 +178,29 @@ describe('Webhook Trigger node', async () => { }); ndv.getters.backToCanvas().click(); - workflowPage.actions.addNodeToCanvas('Set'); - workflowPage.actions.openNode('Set'); - cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('String').click(); - cy.get('.fixed-collection-parameter').getByTestId('parameter-input-name').clear().type('data'); - cy.get('.fixed-collection-parameter') - .getByTestId('parameter-input-value') - .clear() - .find('input') - .invoke('val', cowBase64) - .trigger('blur'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.getters.assignmentCollectionAdd('assignments').click(); + ndv.getters.assignmentName('assignments').type('data').find('input').blur(); + ndv.getters.assignmentType('assignments').click(); + ndv.getters.assignmentValue('assignments').paste(cowBase64); + ndv.getters.backToCanvas().click(); - workflowPage.actions.addNodeToCanvas('Move Binary Data'); + workflowPage.actions.addNodeToCanvas('Convert to File'); workflowPage.actions.zoomToFit(); - workflowPage.actions.openNode('Move Binary Data'); + workflowPage.actions.openNode('Convert to File'); + cy.getByTestId('parameter-input-operation').click(); + getVisibleSelect().find('.option-headline').contains('Convert to JSON').click(); cy.getByTestId('parameter-input-mode').click(); - cy.getByTestId('parameter-input-mode') - .find('.el-select-dropdown') - .find('.option-headline') - .contains('JSON to Binary') - .click(); + getVisibleSelect().find('.option-headline').contains('Each Item to Separate File').click(); ndv.getters.backToCanvas().click(); workflowPage.actions.executeWorkflow(); cy.wait(waitForWebhook); - cy.request('GET', '/webhook-test/' + webhookPath).then((response) => { + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(200); expect(Object.keys(response.body).includes('data')).to.be.true; }); @@ -264,7 +217,7 @@ describe('Webhook Trigger node', async () => { }); ndv.actions.execute(); cy.wait(waitForWebhook); - cy.request('GET', '/webhook-test/' + webhookPath).then((response) => { + cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => { expect(response.status).to.eq(200); expect(response.body.MyValue).to.be.undefined; }); @@ -280,7 +233,7 @@ describe('Webhook Trigger node', async () => { }); // add credentials workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.actions.fillCredentialsForm(); @@ -288,7 +241,7 @@ describe('Webhook Trigger node', async () => { cy.wait(waitForWebhook); cy.request({ method: 'GET', - url: '/webhook-test/' + webhookPath, + url: `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`, auth: { user: 'username', pass: 'password', @@ -301,7 +254,7 @@ describe('Webhook Trigger node', async () => { .then(() => { cy.request({ method: 'GET', - url: '/webhook-test/' + webhookPath, + url: `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`, auth: { user: 'test', pass: 'test', @@ -323,7 +276,7 @@ describe('Webhook Trigger node', async () => { }); // add credentials workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.actions.fillCredentialsForm(); @@ -331,7 +284,7 @@ describe('Webhook Trigger node', async () => { cy.wait(waitForWebhook); cy.request({ method: 'GET', - url: '/webhook-test/' + webhookPath, + url: `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`, headers: { test: 'wrong', }, @@ -343,7 +296,7 @@ describe('Webhook Trigger node', async () => { .then(() => { cy.request({ method: 'GET', - url: '/webhook-test/' + webhookPath, + url: `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`, headers: { test: 'test', }, @@ -354,3 +307,13 @@ describe('Webhook Trigger node', async () => { }); }); }); + +const addEditFields = () => { + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME); + ndv.getters.assignmentCollectionAdd('assignments').click(); + ndv.getters.assignmentName('assignments').type('MyValue').find('input').blur(); + ndv.getters.assignmentType('assignments').click(); + getVisibleSelect().find('li').contains('Number').click(); + ndv.getters.assignmentValue('assignments').type('1234'); +}; diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index 1a8b0a3d007ba..71f41250eca77 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -1,4 +1,4 @@ -import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; +import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN } from '../constants'; import { CredentialsModal, CredentialsPage, @@ -7,6 +7,7 @@ import { WorkflowSharingModal, WorkflowsPage, } from '../pages'; +import { getVisibleSelect } from '../utils'; /** * User U1 - Instance owner @@ -28,54 +29,18 @@ 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.resetAll(); - 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(); credentialsModal.getters.newCredentialTypeOption('Notion API').click(); credentialsModal.getters.newCredentialTypeButton().click(); - credentialsModal.getters.connectionParameter('API Key').type('1234567890'); + credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); credentialsModal.actions.setName('Credential C1'); credentialsModal.actions.save(); credentialsModal.actions.close(); @@ -84,46 +49,63 @@ describe('Sharing', () => { workflowPage.actions.setWorkflowName('Workflow W1'); workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); workflowPage.actions.addNodeToCanvas('Notion', true, true); - ndv.getters.credentialInput().should('contain', 'Credential C1'); + ndv.getters.credentialInput().find('input').should('have.value', 'Credential C1'); 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(); cy.visit(workflowsPage.url); workflowsPage.getters.createWorkflowButton().click(); cy.createFixtureWorkflow('Test_workflow_1.json', 'Workflow W2'); + workflowPage.actions.saveWorkflowOnButtonClick(); cy.url().then((url) => { workflowW2Url = url; }); }); 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); workflowsPage.getters.workflowCard('Workflow W1').click(); workflowPage.actions.addNodeToCanvas('Airtable', true, true); - ndv.getters.credentialInput().should('contain', 'Credential C2'); + ndv.getters.credentialInput().find('input').should('have.value', 'Credential C2'); + ndv.actions.close(); + workflowPage.actions.saveWorkflowOnButtonClick(); + + workflowPage.actions.openNode('Notion'); + ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled'); + ndv.actions.close(); + }); + + it('should open W1, add node using C2 as U2', () => { + cy.signin(INSTANCE_MEMBERS[0]); + + cy.visit(workflowsPage.url); + workflowsPage.getters.workflowCards().should('have.length', 2); + workflowsPage.getters.workflowCard('Workflow W1').click(); + workflowPage.actions.addNodeToCanvas('Airtable', true, true); + ndv.getters.credentialInput().find('input').should('have.value', 'Credential C2'); ndv.actions.close(); workflowPage.actions.saveWorkflowOnButtonClick(); @@ -132,12 +114,12 @@ describe('Sharing', () => { .credentialInput() .find('input') .should('have.value', 'Credential C1') - .should('be.disabled'); + .should('be.enabled'); ndv.actions.close(); }); it('should not have access to W2, as U3', () => { - cy.signin(users[1]); + cy.signin(INSTANCE_MEMBERS[1]); cy.visit(workflowW2Url); cy.waitForLoad(); @@ -146,22 +128,63 @@ 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); workflowsPage.getters.workflowCard('Workflow W1').click(); workflowPage.actions.openNode('Notion'); - ndv.getters - .credentialInput() - .find('input') - .should('have.value', 'Credential C1') - .should('be.disabled'); + ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled'); ndv.actions.close(); cy.waitForLoad(); cy.visit(workflowsPage.url); - workflowsPage.getters.workflowCard('Workflow W2').click(); + workflowsPage.getters.workflowCard('Workflow W2').click('top'); workflowPage.actions.executeWorkflow(); }); + + it('should automatically test C2 when opened by U2 sharee', () => { + cy.signin(INSTANCE_MEMBERS[0]); + + cy.visit(credentialsPage.url); + credentialsPage.getters.credentialCard('Credential C2').click(); + credentialsModal.getters.testSuccessTag().should('be.visible'); + }); + + it('should work for admin role on credentials created by others (also can share it with themselves)', () => { + cy.signin(INSTANCE_MEMBERS[0]); + + cy.visit(credentialsPage.url); + credentialsPage.getters.createCredentialButton().click(); + credentialsModal.getters.newCredentialTypeOption('Notion API').click(); + credentialsModal.getters.newCredentialTypeButton().click({ force: true }); + credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); + credentialsModal.actions.setName('Credential C3'); + credentialsModal.actions.save(); + credentialsModal.actions.close(); + + cy.signout(); + cy.signin(INSTANCE_ADMIN); + cy.visit(credentialsPage.url); + credentialsPage.getters.credentialCard('Credential C3').click(); + credentialsModal.getters.testSuccessTag().should('be.visible'); + cy.get('input').should('not.have.length'); + credentialsModal.actions.changeTab('Sharing'); + cy.contains( + 'You can view this credential because you have permission to read and share', + ).should('be.visible'); + + credentialsModal.getters.usersSelect().click(); + cy.getByTestId('user-email') + .filter(':visible') + .should('have.length', 3) + .contains(INSTANCE_ADMIN.email) + .should('have.length', 1); + getVisibleSelect().contains(INSTANCE_OWNER.email.toLowerCase()).click(); + + credentialsModal.actions.addUser(INSTANCE_MEMBERS[1].email); + credentialsModal.actions.addUser(INSTANCE_ADMIN.email); + credentialsModal.actions.saveSharing(); + credentialsModal.actions.close(); + }); }); diff --git a/cypress/e2e/17-workflow-tags.cy.ts b/cypress/e2e/17-workflow-tags.cy.ts index 8e14897508aea..dea45d1a9afc8 100644 --- a/cypress/e2e/17-workflow-tags.cy.ts +++ b/cypress/e2e/17-workflow-tags.cy.ts @@ -2,51 +2,49 @@ import { WorkflowPage } from '../pages'; const wf = new WorkflowPage(); -const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3']; +const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5']; describe('Workflow tags', () => { beforeEach(() => { - cy.resetAll(); - cy.skipSetup(); + wf.actions.visit(); }); it('should create and attach tags inline', () => { wf.getters.createTagButton().click(); - wf.actions.addTags(TEST_TAGS); - wf.getters.tagPills().should('have.length', TEST_TAGS.length); + wf.actions.addTags(TEST_TAGS.slice(0, 2)); + wf.getters.tagPills().should('have.length', 2); wf.getters.nthTagPill(1).click(); - wf.actions.addTags('Tag 4'); - wf.getters.tagPills().should('have.length', TEST_TAGS.length + 1); + wf.actions.addTags(TEST_TAGS[1].toUpperCase()); + wf.getters.tagPills().should('have.length', 3); wf.getters.isWorkflowSaved(); }); it('should create tags via modal', () => { wf.actions.openTagManagerModal(); - const [first, second] = TEST_TAGS; - - cy.contains('Create a tag').click(); - cy.getByTestId('tags-table').find('input').type(first).type('{enter}'); - cy.contains('Add new').click(); - cy.wait(300); - cy.getByTestId('tags-table').find('input').type(second).type('{enter}'); + const tags = TEST_TAGS.slice(3); + for (const tag of tags) { + cy.contains('Add new').click(); + cy.getByTestId('tags-table').find('input').type(tag).type('{enter}'); + cy.wait(300); + } cy.contains('Done').click(); wf.getters.createTagButton().click(); - wf.getters.tagsInDropdown().should('have.length', 2); // two stored + wf.getters.tagsDropdown().click(); + wf.getters.tagsInDropdown().should('have.length', 5); wf.getters.tagPills().should('have.length', 0); // none attached }); - it('should delete a tag via modal', () => { + it('should delete all tags via modal', () => { wf.actions.openTagManagerModal(); - const [first] = TEST_TAGS; + TEST_TAGS.forEach(() => { + cy.getByTestId('delete-tag-button').first().click({ force: true }); + cy.contains('Delete tag').click(); + cy.wait(300); + }); - cy.contains('Create a tag').click(); - cy.getByTestId('tags-table').find('input').type(first).type('{enter}'); - cy.getByTestId('delete-tag-button').click({ force: true }); - cy.wait(300); - cy.contains('Delete tag').click(); cy.contains('Done').click(); wf.getters.createTagButton().click(); wf.getters.tagsInDropdown().should('have.length', 0); // none stored @@ -60,7 +58,8 @@ describe('Workflow tags', () => { cy.contains('Create a tag').click(); cy.getByTestId('tags-table').find('input').type(first).type('{enter}'); - cy.getByTestId('edit-tag-button').click({ force: true }); + cy.getByTestId('tags-table').should('contain.text', first); + cy.getByTestId('edit-tag-button').eq(-1).click({ force: true }); cy.wait(300); cy.getByTestId('tags-table') .find('.el-input--large') @@ -79,7 +78,7 @@ describe('Workflow tags', () => { wf.actions.addTags(TEST_TAGS); wf.getters.nthTagPill(1).click(); wf.getters.tagsDropdown().find('.el-tag__close').first().click(); - cy.get('body').type('{enter}'); + cy.get('body').click(0, 0); wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1); }); @@ -87,8 +86,8 @@ describe('Workflow tags', () => { wf.getters.createTagButton().click(); wf.actions.addTags(TEST_TAGS); wf.getters.nthTagPill(1).click(); - wf.getters.tagsDropdown().find('li.selected').first().click(); - cy.get('body').type('{enter}'); + wf.getters.tagsInDropdown().filter('.selected').first().click(); + cy.get('body').click(0, 0); wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1); }); }); diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts index f06ba3d6559d9..0dc12647399b1 100644 --- a/cypress/e2e/18-user-management.cy.ts +++ b/cypress/e2e/18-user-management.cy.ts @@ -1,7 +1,7 @@ -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, INSTANCE_ADMIN } from '../constants'; +import { MainSidebar, SettingsSidebar, SettingsUsersPage, WorkflowPage } from '../pages'; import { PersonalSettingsPage } from '../pages/settings-personal'; +import { getVisibleSelect } from '../utils'; /** * User A - Instance owner @@ -15,28 +15,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', @@ -48,67 +26,148 @@ const updatedPersonalData = { const usersSettingsPage = new SettingsUsersPage(); const workflowPage = new WorkflowPage(); const personalSettingsPage = new PersonalSettingsPage(); +const settingsSidebar = new SettingsSidebar(); +const mainSidebar = new MainSidebar(); -describe('User Management', () => { +describe('User Management', { disableAutoLogin: true }, () => { before(() => { - cy.resetAll(); - 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 }); + 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); + usersSettingsPage.getters.userListItems().should('have.length', 4); // 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'); + usersSettingsPage.getters.userActionsToggle(INSTANCE_ADMIN.email).should('exist'); + }); + + it('should be able to change user role to Admin and back', () => { + cy.enableFeature('advancedPermissions'); + + usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); + + // Change role from Member to Admin + usersSettingsPage.getters + .userRoleSelect(INSTANCE_MEMBERS[0].email) + .find('input') + .should('contain.value', 'Member'); + usersSettingsPage.getters.userRoleSelect(INSTANCE_MEMBERS[0].email).click(); + getVisibleSelect().find('li').contains('Admin').click(); + usersSettingsPage.getters + .userRoleSelect(INSTANCE_MEMBERS[0].email) + .find('input') + .should('contain.value', 'Admin'); + + usersSettingsPage.actions.loginAndVisit( + INSTANCE_MEMBERS[0].email, + INSTANCE_MEMBERS[0].password, + true, + ); + + // Change role from Admin to Member, then back to Admin + usersSettingsPage.getters + .userRoleSelect(INSTANCE_ADMIN.email) + .find('input') + .should('contain.value', 'Admin'); + + usersSettingsPage.getters.userRoleSelect(INSTANCE_ADMIN.email).click(); + getVisibleSelect().find('li').contains('Member').click(); + usersSettingsPage.getters + .userRoleSelect(INSTANCE_ADMIN.email) + .find('input') + .should('contain.value', 'Member'); + + usersSettingsPage.actions.loginAndVisit(INSTANCE_ADMIN.email, INSTANCE_ADMIN.password, false); + usersSettingsPage.actions.loginAndVisit( + INSTANCE_MEMBERS[0].email, + INSTANCE_MEMBERS[0].password, + true, + ); + + usersSettingsPage.getters.userRoleSelect(INSTANCE_ADMIN.email).click(); + getVisibleSelect().find('li').contains('Admin').click(); + usersSettingsPage.getters + .userRoleSelect(INSTANCE_ADMIN.email) + .find('input') + .should('contain.value', 'Admin'); + + usersSettingsPage.actions.loginAndVisit(INSTANCE_ADMIN.email, INSTANCE_ADMIN.password, true); + usersSettingsPage.getters.userRoleSelect(INSTANCE_MEMBERS[0].email).click(); + getVisibleSelect().find('li').contains('Member').click(); + usersSettingsPage.getters + .userRoleSelect(INSTANCE_MEMBERS[0].email) + .find('input') + .should('contain.value', 'Member'); + + cy.disableFeature('advancedPermissions'); + }); + + it('should be able to change theme', () => { + personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password); + + personalSettingsPage.actions.changeTheme('Dark'); + cy.get('body').should('have.attr', 'data-theme', 'dark'); + settingsSidebar.actions.back(); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/static/logo/channel/dev-dark.svg'); + }); + + cy.visit(personalSettingsPage.url); + personalSettingsPage.actions.changeTheme('Light'); + cy.get('body').should('have.attr', 'data-theme', 'light'); + settingsSidebar.actions.back(); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/static/logo/channel/dev.svg'); + }); }); it('should delete user and their data', () => { - usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); - usersSettingsPage.actions.opedDeleteDialog(users[0].email); - usersSettingsPage.getters.deleteDataRadioButton().realClick(); + usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); + usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[0].email); + usersSettingsPage.getters.deleteDataRadioButton().click(); usersSettingsPage.getters.deleteDataInput().type('delete all data'); - usersSettingsPage.getters.deleteUserButton().realClick(); + usersSettingsPage.getters.deleteUserButton().click(); workflowPage.getters.successToast().should('contain', 'User deleted'); }); it('should delete user and transfer their data', () => { - usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); - usersSettingsPage.actions.opedDeleteDialog(users[1].email); - usersSettingsPage.getters.transferDataRadioButton().realClick(); - usersSettingsPage.getters.userSelectDropDown().realClick(); - usersSettingsPage.getters.userSelectOptions().first().realClick(); - usersSettingsPage.getters.deleteUserButton().realClick(); + usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); + usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[1].email); + usersSettingsPage.getters.transferDataRadioButton().click(); + usersSettingsPage.getters.userSelectDropDown().click(); + usersSettingsPage.getters.userSelectOptions().first().click(); + usersSettingsPage.getters.deleteUserButton().click(); workflowPage.getters.successToast().should('contain', 'User deleted'); }); 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, @@ -120,14 +179,16 @@ 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); + personalSettingsPage.getters.changePasswordLink().click(); 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.getters.changePasswordLink().click(); personalSettingsPage.actions.updatePassword('iCannotRemember', updatedPersonalData.newPassword); workflowPage.getters .errorToast() @@ -136,21 +197,22 @@ 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.getters.changePasswordLink().click(); 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 @@ -161,7 +223,7 @@ describe('User Management', () => { it(`should change user email`, () => { personalSettingsPage.actions.loginAndVisit( - instanceOwner.email, + INSTANCE_OWNER.email, updatedPersonalData.newPassword, ); personalSettingsPage.actions.updateEmail(updatedPersonalData.newEmail); diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts index 6ad2ce6592fa1..1855bdb43be66 100644 --- a/cypress/e2e/19-execution.cy.ts +++ b/cypress/e2e/19-execution.cy.ts @@ -1,14 +1,14 @@ import { v4 as uuid } from 'uuid'; -import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages'; +import { NDV, WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages'; +import { SCHEDULE_TRIGGER_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from '../constants'; -const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPageClass(); +const executionsTab = new WorkflowExecutionsTab(); const ndv = new NDV(); describe('Execution', () => { beforeEach(() => { - cy.resetAll(); - cy.skipSetup(); + workflowPage.actions.visit(); }); it('should test manual workflow', () => { @@ -106,7 +106,7 @@ describe('Execution', () => { .canvasNodeByName('Set') .within(() => cy.get('.fa-check').should('not.exist')); - cy.wait(1000); + workflowPage.getters.stopExecutionButton().should('exist'); workflowPage.getters.stopExecutionButton().click(); // Check canvas nodes after workflow stopped @@ -114,10 +114,6 @@ describe('Execution', () => { .canvasNodeByName('Manual') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Wait') .within(() => cy.get('.fa-sync-alt').should('not.visible')); @@ -193,10 +189,6 @@ describe('Execution', () => { .canvasNodeByName('Webhook') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Set') .within(() => cy.get('.fa-check')) @@ -264,16 +256,11 @@ describe('Execution', () => { .canvasNodeByName('Set') .within(() => cy.get('.fa-check').should('not.exist')); - // Check canvas nodes after workflow stopped workflowPage.getters .canvasNodeByName('Webhook') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Wait') .within(() => cy.get('.fa-sync-alt').should('not.visible')); @@ -289,4 +276,347 @@ describe('Execution', () => { // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished) workflowPage.getters.successToast().should('be.visible'); }); + + describe('execution preview', () => { + it('when deleting the last execution, it should show empty state', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + workflowPage.actions.executeWorkflow(); + executionsTab.actions.switchToExecutionsTab(); + + executionsTab.actions.deleteExecutionInPreview(); + + executionsTab.getters.successfulExecutionListItems().should('have.length', 0); + workflowPage.getters.successToast().contains('Execution deleted'); + }); + }); + + describe('connections should be colored differently for pinned data', () => { + beforeEach(() => { + cy.createFixtureWorkflow('Schedule_pinned.json', `Schedule pinned ${uuid()}`); + workflowPage.actions.deselectAll(); + workflowPage.getters.zoomToFitButton().click(); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields1') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields5', 'Edit Fields6') + .should('not.have.class', 'success') + .should('not.have.class', 'pinned'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields7', 'Edit Fields9') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields1', 'Edit Fields2') + .should('not.have.class', 'success') + .should('not.have.class', 'pinned'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields2', 'Edit Fields3') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + }); + + it('when executing the workflow', () => { + workflowPage.actions.executeWorkflow(); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields1') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields5', 'Edit Fields6') + .should('have.class', 'success') + .should('not.have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields7', 'Edit Fields9') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields1', 'Edit Fields2') + .should('have.class', 'success') + .should('not.have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields2', 'Edit Fields3') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + }); + + it('when executing a node', () => { + workflowPage.actions.executeNode('Edit Fields3'); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields1') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields5', 'Edit Fields6') + .should('not.have.class', 'success') + .should('not.have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields7', 'Edit Fields9') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields1', 'Edit Fields2') + .should('have.class', 'success') + .should('not.have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields2', 'Edit Fields3') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + }); + + it('when connecting pinned node by output drag and drop', () => { + cy.drag( + workflowPage.getters.getEndpointSelector('output', SCHEDULE_TRIGGER_NODE_NAME), + [-200, -300], + ); + workflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [150, 200], { + clickToFinish: true, + }); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields8') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.actions.executeWorkflow(); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields8') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + cy.drag(workflowPage.getters.getEndpointSelector('output', 'Edit Fields2'), [-200, -300]); + workflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [150, 200], { + clickToFinish: true, + }); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields2', 'Edit Fields11') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + }); + + it('when connecting pinned node after adding an unconnected node', () => { + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + + cy.draganddrop( + workflowPage.getters.getEndpointSelector('output', SCHEDULE_TRIGGER_NODE_NAME), + workflowPage.getters.getEndpointSelector('input', 'Edit Fields8'), + ); + workflowPage.getters.zoomToFitButton().click(); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields8') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('not.have.class', 'has-run'); + + workflowPage.actions.executeWorkflow(); + + workflowPage.getters + .getConnectionBetweenNodes('Schedule Trigger', 'Edit Fields8') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + + workflowPage.actions.deselectAll(); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + workflowPage.getters.zoomToFitButton().click(); + + cy.draganddrop( + workflowPage.getters.getEndpointSelector('output', 'Edit Fields7'), + workflowPage.getters.getEndpointSelector('input', 'Edit Fields11'), + ); + + workflowPage.getters + .getConnectionBetweenNodes('Edit Fields7', 'Edit Fields11') + .should('have.class', 'success') + .should('have.class', 'pinned') + .should('have.class', 'has-run'); + }); + }); + + it('should send proper payload for node rerun', () => { + cy.createFixtureWorkflow( + 'Multiple_trigger_node_rerun.json', + `Multiple trigger node rerun ${uuid()}`, + ); + + workflowPage.getters.zoomToFitButton().click(); + workflowPage.getters.executeWorkflowButton().click(); + + workflowPage.getters.clearExecutionDataButton().should('be.visible'); + + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + + workflowPage.getters + .canvasNodeByName('do something with them') + .findChildByTestId('execute-node-button') + .click({ force: true }); + + cy.wait('@workflowRun').then((interception) => { + expect(interception.request.body).to.have.property('runData').that.is.an('object'); + const expectedKeys = ['When clicking "Test workflow"', 'fetch 5 random users']; + + expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length); + expect(interception.request.body.runData).to.include.all.keys(expectedKeys); + }); + }); + + it('should send proper payload for manual node run', () => { + cy.createFixtureWorkflow( + 'Check_manual_node_run_for_pinned_and_rundata.json', + `Check manual node run for pinned and rundata ${uuid()}`, + ); + + workflowPage.getters.zoomToFitButton().click(); + + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + + workflowPage.getters + .canvasNodeByName('If') + .findChildByTestId('execute-node-button') + .click({ force: true }); + + cy.wait('@workflowRun').then((interception) => { + expect(interception.request.body).not.to.have.property('runData').that.is.an('object'); + expect(interception.request.body).to.have.property('pinData').that.is.an('object'); + const expectedPinnedDataKeys = ['Webhook']; + + expect(Object.keys(interception.request.body.pinData)).to.have.lengthOf( + expectedPinnedDataKeys.length, + ); + expect(interception.request.body.pinData).to.include.all.keys(expectedPinnedDataKeys); + }); + + workflowPage.getters.clearExecutionDataButton().should('be.visible'); + + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + + workflowPage.getters + .canvasNodeByName('NoOp2') + .findChildByTestId('execute-node-button') + .click({ force: true }); + + cy.wait('@workflowRun').then((interception) => { + expect(interception.request.body).to.have.property('runData').that.is.an('object'); + expect(interception.request.body).to.have.property('pinData').that.is.an('object'); + const expectedPinnedDataKeys = ['Webhook']; + const expectedRunDataKeys = ['If', 'Webhook']; + + expect(Object.keys(interception.request.body.pinData)).to.have.lengthOf( + expectedPinnedDataKeys.length, + ); + expect(interception.request.body.pinData).to.include.all.keys(expectedPinnedDataKeys); + + expect(Object.keys(interception.request.body.runData)).to.have.lengthOf( + expectedRunDataKeys.length, + ); + expect(interception.request.body.runData).to.include.all.keys(expectedRunDataKeys); + }); + }); + + it('should successfully execute partial executions with nodes attached to the second output', () => { + cy.createFixtureWorkflow( + 'Test_Workflow_pairedItem_incomplete_manual_bug.json', + 'My test workflow', + ); + + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + + workflowPage.getters.zoomToFitButton().click(); + workflowPage.getters.executeWorkflowButton().click(); + workflowPage.getters + .canvasNodeByName('Test Expression') + .findChildByTestId('execute-node-button') + .click({ force: true }); + + // Check toast (works because Cypress waits enough for the element to show after the http request node has finished) + // Wait for the execution to return. + cy.wait('@workflowRun'); + // Wait again for the websocket message to arrive and the UI to update. + cy.wait(100); + workflowPage.getters.errorToast({ timeout: 1 }).should('not.exist'); + }); + + it('should execute workflow partially up to the node that has issues', () => { + cy.createFixtureWorkflow( + 'Test_workflow_partial_execution_with_missing_credentials.json', + 'My test workflow', + ); + + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + + workflowPage.getters.zoomToFitButton().click(); + workflowPage.getters.executeWorkflowButton().click(); + + // Wait for the execution to return. + cy.wait('@workflowRun'); + + // Check that the previous nodes executed successfully + workflowPage.getters + .canvasNodeByName('DebugHelper') + .within(() => cy.get('.fa-check')) + .should('exist'); + workflowPage.getters + .canvasNodeByName('Filter') + .within(() => cy.get('.fa-check')) + .should('exist'); + + workflowPage.getters.errorToast().should('contain', `Problem in node ‘Telegram‘`); + }); }); diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index 17d51ebb0ded4..008758aef2f75 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -1,29 +1,18 @@ import { - NEW_NOTION_ACCOUNT_NAME, - NOTION_NODE_NAME, - PIPEDRIVE_NODE_NAME, - HTTP_REQUEST_NODE_NAME, - NEW_QUERY_AUTH_ACCOUNT_NAME, -} from './../constants'; -import { - DEFAULT_USER_EMAIL, - DEFAULT_USER_PASSWORD, GMAIL_NODE_NAME, + HTTP_REQUEST_NODE_NAME, NEW_GOOGLE_ACCOUNT_NAME, + NEW_NOTION_ACCOUNT_NAME, + NEW_QUERY_AUTH_ACCOUNT_NAME, NEW_TRELLO_ACCOUNT_NAME, + NOTION_NODE_NAME, + PIPEDRIVE_NODE_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(); +import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages'; +import { getVisibleSelect } from '../utils'; + const credentialsPage = new CredentialsPage(); const credentialsModal = new CredentialsModal(); const workflowPage = new WorkflowPage(); @@ -32,11 +21,6 @@ const nodeDetailsView = new NDV(); const NEW_CREDENTIAL_NAME = 'Something else'; describe('Credentials', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { cy.visit(credentialsPage.url); }); @@ -49,7 +33,7 @@ describe('Credentials', () => { credentialsModal.getters.newCredentialTypeOption('Notion API').click(); credentialsModal.getters.newCredentialTypeButton().click(); - credentialsModal.getters.connectionParameter('API Key').type('1234567890'); + credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); credentialsModal.actions.setName('My awesome Notion account'); credentialsModal.actions.save(); @@ -58,7 +42,7 @@ describe('Credentials', () => { credentialsPage.getters.credentialCards().should('have.length', 1); }); - it('should create a new credential using Add Credential button', () => { + it.skip('should create a new credential using Add Credential button', () => { credentialsPage.getters.createCredentialButton().click(); credentialsModal.getters.newCredentialModal().should('be.visible'); @@ -76,7 +60,7 @@ describe('Credentials', () => { credentialsPage.getters.credentialCards().should('have.length', 2); }); - it('should search credentials', () => { + it.skip('should search credentials', () => { // Search by name credentialsPage.actions.search('Notion'); credentialsPage.getters.credentialCards().should('have.length', 1); @@ -105,13 +89,16 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); credentialsModal.actions.fillCredentialsForm(); cy.get('.el-message-box').find('button').contains('Close').click(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_GOOGLE_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_GOOGLE_ACCOUNT_NAME); }); it('should show multiple credential types in the same dropdown', () => { @@ -122,7 +109,7 @@ describe('Credentials', () => { cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); // Add oAuth credentials - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); @@ -130,13 +117,14 @@ describe('Credentials', () => { cy.get('.el-message-box').find('button').contains('Close').click(); workflowPage.getters.nodeCredentialsSelect().click(); // Add Service account credentials - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().last().click(); credentialsModal.actions.fillCredentialsForm(); // Both (+ the 'Create new' option) should be in the dropdown - workflowPage.getters.nodeCredentialsSelect().find('li').should('have.length.greaterThan', 3); + workflowPage.getters.nodeCredentialsSelect().click(); + getVisibleSelect().find('li').should('have.length.greaterThan', 2); }); it('should correctly render required and optional credentials', () => { @@ -147,18 +135,18 @@ describe('Credentials', () => { // Select incoming authentication nodeDetailsView.getters.parameterInput('incomingAuthentication').should('exist'); nodeDetailsView.getters.parameterInput('incomingAuthentication').click(); - nodeDetailsView.getters.parameterInput('incomingAuthentication').find('li').first().click(); + getVisibleSelect().find('li').first().click(); // There should be two credential fields workflowPage.getters.nodeCredentialsSelect().should('have.length', 2); workflowPage.getters.nodeCredentialsSelect().first().click(); - workflowPage.getters.nodeCredentialsSelect().first().find('li').last().click(); + getVisibleSelect().find('li').last().click(); // This one should show auth type selector credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); cy.get('body').type('{esc}'); workflowPage.getters.nodeCredentialsSelect().last().click(); - workflowPage.getters.nodeCredentialsSelect().last().find('li').last().click(); + getVisibleSelect().find('li').last().click(); // This one should not show auth type selector credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist'); }); @@ -170,10 +158,13 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist'); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_TRELLO_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_TRELLO_ACCOUNT_NAME); }); it('should delete credentials from NDV', () => { @@ -183,16 +174,22 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_NOTION_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_NOTION_ACCOUNT_NAME); workflowPage.getters.nodeCredentialsEditButton().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.deleteButton().click(); cy.get('.el-message-box').find('button').contains('Yes').click(); workflowPage.getters.successToast().contains('Credential deleted'); - workflowPage.getters.nodeCredentialsSelect().should('not.contain', NEW_TRELLO_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('not.have.value', NEW_TRELLO_ACCOUNT_NAME); }); it('should rename credentials from NDV', () => { @@ -202,17 +199,18 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_TRELLO_ACCOUNT_NAME); - workflowPage.getters.nodeCredentialsEditButton().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.name().click(); credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME); credentialsModal.getters.saveButton().click(); credentialsModal.getters.closeButton().click(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_CREDENTIAL_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_CREDENTIAL_NAME); }); it('should setup generic authentication for HTTP node', () => { @@ -222,20 +220,45 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); nodeDetailsView.getters.parameterInput('authentication').click(); - nodeDetailsView.getters.parameterInput('authentication').find('li').should('have.length', 3); - nodeDetailsView.getters.parameterInput('authentication').find('li').last().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click(); nodeDetailsView.getters.parameterInput('genericAuthType').should('exist'); nodeDetailsView.getters.parameterInput('genericAuthType').click(); - nodeDetailsView.getters - .parameterInput('genericAuthType') - .find('li') - .should('have.length.greaterThan', 0); - nodeDetailsView.getters.parameterInput('genericAuthType').find('li').last().click(); + getVisibleSelect().find('li').should('have.length.greaterThan', 0); + getVisibleSelect().find('li').last().click(); workflowPage.getters.nodeCredentialsSelect().should('exist'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_QUERY_AUTH_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_QUERY_AUTH_ACCOUNT_NAME); + }); + + it('should not show OAuth redirect URL section when OAuth2 credentials are overridden', () => { + cy.intercept('/types/credentials.json', { middleware: true }, (req) => { + req.headers['cache-control'] = 'no-cache, no-store'; + + req.on('response', (res) => { + const credentials = res.body || []; + + const index = credentials.findIndex((c) => c.name === 'slackOAuth2Api'); + + credentials[index] = { + ...credentials[index], + __overwrittenProperties: ['clientId', 'clientSecret'], + }; + }); + }); + + workflowPage.actions.visit(true); + workflowPage.actions.addNodeToCanvas('Slack'); + workflowPage.actions.openNode('Slack'); + workflowPage.getters.nodeCredentialsSelect().click(); + getVisibleSelect().find('li').last().click(); + credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); + nodeDetailsView.getters.copyInput().should('not.exist'); }); }); diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 1e40b7dcc4f8e..37036a7971d6e 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -1,29 +1,27 @@ import { WorkflowPage } from '../pages'; import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab'; +import type { RouteHandler } from 'cypress/types/net-stubbing'; const workflowPage = new WorkflowPage(); const executionsTab = new WorkflowExecutionsTab(); +const executionsRefreshInterval = 4000; // Test suite for executions tab describe('Current Workflow Executions', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { workflowPage.actions.visit(); cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`); - createMockExecutions(); }); it('should render executions tab correctly', () => { + createMockExecutions(); cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); - cy.intercept('GET', '/rest/executions-current?filter=*').as('getCurrentExecutions'); executionsTab.actions.switchToExecutionsTab(); - cy.wait(['@getExecutions', '@getCurrentExecutions']); + cy.wait(['@getExecutions']); + + executionsTab.getters.executionsList().scrollTo(0, 500).wait(0); executionsTab.getters.executionListItems().should('have.length', 11); executionsTab.getters.successfulExecutionListItems().should('have.length', 9); @@ -34,10 +32,48 @@ describe('Current Workflow Executions', () => { .invoke('attr', 'class') .should('match', /_active_/); }); + + it('should not redirect back to execution tab when request is not done before leaving the page', () => { + cy.intercept('GET', '/rest/executions?filter=*'); + cy.intercept('GET', '/rest/executions/active?filter=*'); + + executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToEditorTab(); + cy.wait(executionsRefreshInterval); + cy.url().should('not.include', '/executions'); + executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToEditorTab(); + executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToEditorTab(); + executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToEditorTab(); + cy.wait(executionsRefreshInterval); + cy.url().should('not.include', '/executions'); + executionsTab.actions.switchToExecutionsTab(); + cy.wait(1000); + executionsTab.actions.switchToEditorTab(); + cy.wait(executionsRefreshInterval); + cy.url().should('not.include', '/executions'); + }); + + it('should not redirect back to execution tab when slow request is not done before leaving the page', () => { + const throttleResponse: RouteHandler = (req) => { + return new Promise((resolve) => { + setTimeout(() => resolve(req.continue()), 2000); + }); + }; + + cy.intercept('GET', '/rest/executions?filter=*', throttleResponse); + cy.intercept('GET', '/rest/executions/active?filter=*', throttleResponse); + + executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToEditorTab(); + cy.wait(executionsRefreshInterval); + cy.url().should('not.include', '/executions'); + }); }); const createMockExecutions = () => { - workflowPage.actions.turnOnManualExecutionSaving(); executionsTab.actions.createManualExecutions(5); // Make some failed executions by enabling Code node with syntax error executionsTab.actions.toggleNodeEnabled('Error'); diff --git a/cypress/e2e/21-community-nodes.cy.ts b/cypress/e2e/21-community-nodes.cy.ts index f46a829ac033f..39f572ba5c348 100644 --- a/cypress/e2e/21-community-nodes.cy.ts +++ b/cypress/e2e/21-community-nodes.cy.ts @@ -4,6 +4,7 @@ import { CredentialsModal, WorkflowPage } from '../pages'; import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json'; import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json'; import CustomCredential from '../fixtures/Custom_credential.json'; +import { getVisibleSelect } from '../utils'; const credentialsModal = new CredentialsModal(); const nodeCreatorFeature = new NodeCreator(); @@ -13,10 +14,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.resetAll(); - cy.skipSetup(); - }) beforeEach(() => { cy.intercept('/types/nodes.json', { middleware: true }, (req) => { req.headers['cache-control'] = 'no-cache, no-store'; @@ -24,9 +21,13 @@ describe('Community Nodes', () => { req.on('response', (res) => { const nodes = res.body || []; - nodes.push(CustomNodeFixture, CustomNodeWithN8nCredentialFixture, CustomNodeWithCustomCredentialFixture); + nodes.push( + CustomNodeFixture, + CustomNodeWithN8nCredentialFixture, + CustomNodeWithCustomCredentialFixture, + ); }); - }) + }); cy.intercept('/types/credentials.json', { middleware: true }, (req) => { req.headers['cache-control'] = 'no-cache, no-store'; @@ -35,8 +36,9 @@ describe('Community Nodes', () => { const credentials = res.body || []; credentials.push(CustomCredential); - }) - }) + }); + }); + workflowPage.actions.visit(); }); @@ -48,7 +50,7 @@ describe('Community Nodes', () => { nodeCreatorFeature.getters .getCreatorItem(customNode) - .findChildByTestId('node-creator-item-tooltip') + .find('.el-tooltip__trigger') .should('exist'); nodeCreatorFeature.actions.selectNode(customNode); @@ -68,16 +70,9 @@ describe('Community Nodes', () => { secondParameter().find('label').contains('Resource').should('exist'); secondParameter().find('input.el-input__inner').should('have.value', 'option2'); secondParameter().find('.el-select').click(); - secondParameter().find('.el-select-dropdown__list').should('exist'); // Check if all options are rendered and select the fourth one - secondParameter().find('.el-select-dropdown__list').children().should('have.length', 4); - secondParameter() - .find('.el-select-dropdown__list') - .children() - .eq(3) - .contains('option4') - .should('exist') - .click(); + getVisibleSelect().find('li').should('have.length', 4); + getVisibleSelect().find('li').eq(3).contains('option4').should('exist').click(); secondParameter().find('input.el-input__inner').should('have.value', 'option4'); }); diff --git a/cypress/e2e/22-user-activation-modal.cy.ts b/cypress/e2e/22-user-activation-modal.cy.ts deleted file mode 100644 index 6b8f23be2fcf5..0000000000000 --- a/cypress/e2e/22-user-activation-modal.cy.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { WorkflowPage, NDV, UserActivationSurveyModal } from '../pages'; -import SettingsWithActivationModalEnabled from '../fixtures/Settings_user_activation_modal_enabled.json'; -import { v4 as uuid } from 'uuid'; - -const workflowPage = new WorkflowPage(); -const ndv = new NDV(); -const userActivationSurveyModal = new UserActivationSurveyModal(); - -const BASE_WEBHOOK_URL = 'http://localhost:5678/webhook'; - -describe('User activation survey', () => { - it('Should show activation survey', () => { - cy.resetAll(); - - cy.skipSetup(); - - cy.intercept('GET', '/rest/settings', (req) => { - req.reply(SettingsWithActivationModalEnabled); - }); - - const path = uuid(); - const method = 'GET'; - - workflowPage.actions.addInitialNodeToCanvas('Webhook'); - workflowPage.actions.openNode('Webhook'); - - //input http method - cy.getByTestId('parameter-input-httpMethod').click(); - cy.getByTestId('parameter-input-httpMethod') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(method) - .click(); - - //input path method - cy.getByTestId('parameter-input-path') - .find('.parameter-input') - .find('input') - .clear() - .type(path); - - ndv.actions.close(); - - workflowPage.actions.saveWorkflowOnButtonClick(); - - workflowPage.actions.activateWorkflow(); - - cy.intercept('GET', '/rest/workflows').as('getWorkflows'); - cy.intercept('GET', '/rest/credentials').as('getCredentials'); - cy.intercept('GET', '/rest/active').as('getActive'); - - cy.request(method, `${BASE_WEBHOOK_URL}/${path}`).then((response) => { - expect(response.status).to.eq(200); - cy.visit('/'); - cy.reload(); - - cy.wait(['@getWorkflows', '@getCredentials', '@getActive']); - userActivationSurveyModal.getters.modalContainer().should('be.visible'); - userActivationSurveyModal.getters.feedbackInput().should('be.visible'); - userActivationSurveyModal.getters.feedbackInput().type('testing'); - userActivationSurveyModal.getters.feedbackInput().should('have.value', 'testing'); - userActivationSurveyModal.getters.sendFeedbackButton().click(); - }); - }); -}); diff --git a/cypress/e2e/23-variables.cy.ts b/cypress/e2e/23-variables.cy.ts index 8dc16bb8e967d..ce6a49fb99402 100644 --- a/cypress/e2e/23-variables.cy.ts +++ b/cypress/e2e/23-variables.cy.ts @@ -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.resetAll(); - cy.setup({ email, firstName, lastName, password }); - }); - it('should show the unlicensed action box when the feature is disabled', () => { - cy.signin({ email, password }); + cy.disableFeature('variables', false); cy.visit(variablesPage.url); variablesPage.getters.unavailableResourcesList().should('be.visible'); @@ -25,12 +13,14 @@ 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); + cy.wait(['@loadVariables', '@loadSettings']); }); it('should show the licensed action box when the feature is enabled', () => { diff --git a/cypress/e2e/24-ndv-paired-item.cy.ts b/cypress/e2e/24-ndv-paired-item.cy.ts index 05f5dd8581a71..1b2b4f1efeaad 100644 --- a/cypress/e2e/24-ndv-paired-item.cy.ts +++ b/cypress/e2e/24-ndv-paired-item.cy.ts @@ -5,11 +5,6 @@ const workflowPage = new WorkflowPage(); const ndv = new NDV(); describe('NDV', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - - }); beforeEach(() => { workflowPage.actions.visit(); workflowPage.actions.renameWorkflow(uuid()); @@ -24,7 +19,7 @@ describe('NDV', () => { workflowPage.actions.executeWorkflow(); - workflowPage.actions.openNode('Item Lists'); + workflowPage.actions.openNode('Sort'); ndv.getters.inputPanel().contains('6 items').should('exist'); ndv.getters.outputPanel().contains('6 items').should('exist'); @@ -33,59 +28,34 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); // input to output - ndv.getters.inputTableRow(1) - .should('exist') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).should('exist'); - ndv.getters.inputTableRow(1) - .realHover(); - ndv.getters.outputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(2) - .realHover(); - ndv.getters.outputTableRow(2) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - - ndv.getters.inputTableRow(3) - .realHover(); - ndv.getters.outputTableRow(6) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).realHover(); + ndv.getters.outputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.inputTableRow(2).realHover(); + ndv.getters.outputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.inputTableRow(3).realHover(); + ndv.getters.outputTableRow(6).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); // output to input - ndv.getters.outputTableRow(1) - .realHover(); - ndv.getters.inputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(1).realHover(); + ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(4) - .realHover(); - ndv.getters.inputTableRow(1) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(4).realHover(); + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(2) - .realHover(); - ndv.getters.inputTableRow(2) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - - ndv.getters.outputTableRow(6) - .realHover(); - ndv.getters.inputTableRow(3) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(2).realHover(); + ndv.getters.inputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .realHover(); - ndv.getters.inputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(6).realHover(); + ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.outputTableRow(1).realHover(); + ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); }); it('maps paired input and output items based on selected input node', () => { @@ -97,9 +67,11 @@ describe('NDV', () => { workflowPage.actions.openNode('Set2'); ndv.getters.inputPanel().contains('6 items').should('exist'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() + .find('input') .should('exist') - .should('include.text', '2 of 2 (6 items)'); + .should('have.value', '2 of 2 (6 items)'); ndv.actions.switchInputMode('Table'); ndv.actions.switchOutputMode('Table'); @@ -111,23 +83,22 @@ describe('NDV', () => { ndv.actions.selectInputNode('Set1'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) - .should('have.text', '1000') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).should('have.text', '1000'); + + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).realHover(); cy.wait(50); ndv.getters.outputHoveringItem().should('have.text', '1000'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); - ndv.actions.selectInputNode('Item Lists'); + ndv.actions.selectInputNode('Sort'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) - .should('have.text', '1111') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).should('have.text', '1111'); + + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.getters.inputTableRow(1).realHover(); cy.wait(50); ndv.getters.outputHoveringItem().should('have.text', '1111'); @@ -142,11 +113,13 @@ describe('NDV', () => { workflowPage.actions.executeWorkflow(); workflowPage.actions.openNode('Set3'); - ndv.getters.inputRunSelector() + ndv.getters + .inputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); @@ -155,45 +128,39 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); - ndv.getters.inputRunSelector().find('input') - .should('include.value', '1 of 2 (6 items)'); - ndv.getters.outputRunSelector().find('input') - .should('include.value', '1 of 2 (6 items)'); + ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); + ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); - ndv.getters.inputTableRow(1) - .should('have.text', '1111') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.getters.inputTableRow(1).should('have.text', '1111'); + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + ndv.getters.outputTableRow(1).should('have.text', '1111'); + ndv.getters.outputTableRow(1).realHover(); - ndv.getters.outputTableRow(3) - .should('have.text', '4444') - .realHover(); - ndv.getters.inputTableRow(3) - .should('have.text', '4444') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(3).should('have.text', '4444'); + ndv.getters.outputTableRow(3).realHover(); + + ndv.getters.inputTableRow(3).should('have.text', '4444'); + ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.actions.changeOutputRunSelector('2 of 2 (6 items)'); cy.wait(50); - ndv.getters.inputTableRow(1) - .should('have.text', '1000') - .realHover(); - ndv.getters.outputTableRow(1) + ndv.getters.inputTableRow(1).should('have.text', '1000'); + ndv.getters.inputTableRow(1).realHover(); + + ndv.getters.outputTableRow(1).should('have.text', '1000'); + ndv.getters + .outputTableRow(1) .should('have.text', '1000') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(3) - .should('have.text', '2000') - .realHover(); - ndv.getters.inputTableRow(3) - .should('have.text', '2000') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(3).should('have.text', '2000'); + ndv.getters.outputTableRow(3).realHover(); + + ndv.getters.inputTableRow(3).should('have.text', '2000'); + + ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); }); it('resolves expression with default item when input node is not parent, while still pairing items', () => { @@ -205,46 +172,51 @@ describe('NDV', () => { workflowPage.actions.openNode('Set2'); ndv.getters.inputPanel().contains('6 items').should('exist'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() + .find('input') .should('exist') - .should('include.text', '2 of 2 (6 items)'); + .should('have.value', '2 of 2 (6 items)'); ndv.actions.switchInputMode('Table'); ndv.actions.switchOutputMode('Table'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) - .should('have.text', '1111') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).should('have.text', '1111'); + + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.getters.inputTableRow(1).realHover(); + cy.wait(100); ndv.getters.outputHoveringItem().should('not.exist'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1111'); ndv.actions.selectInputNode('Code1'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1) - .should('have.text', '1000') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .should('have.text', '1000'); + ndv.getters.inputTableRow(1).should('have.text', '1000'); + + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + ndv.getters.outputTableRow(1).should('have.text', '1000'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); ndv.actions.selectInputNode('Code'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1) - .should('have.text', '6666') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + cy.wait(100); + ndv.getters.inputTableRow(1).should('have.text', '6666'); + + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + ndv.getters.outputHoveringItem().should('not.exist'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); ndv.actions.selectInputNode('When clicking'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1).should('have.text', "This is an item, but it's empty.").realHover(); + ndv.getters + .inputTableRow(1) + .should('have.text', "This is an item, but it's empty.") + .realHover(); + ndv.getters.outputHoveringItem().should('have.length', 6); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); }); @@ -261,45 +233,104 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); ndv.actions.switchOutputBranch('False Branch (2 items)'); - ndv.getters.outputTableRow(1) - .should('have.text', '8888') - .realHover(); - ndv.getters.inputTableRow(5) - .should('have.text', '8888') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(1).should('have.text', '8888'); + ndv.getters.outputTableRow(1).realHover(); - ndv.getters.outputTableRow(2) - .should('have.text', '9999') - .realHover(); - ndv.getters.inputTableRow(6) - .should('have.text', '9999') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(5).should('have.text', '8888'); + + ndv.getters.inputTableRow(5).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.outputTableRow(2).should('have.text', '9999'); + ndv.getters.outputTableRow(2).realHover(); + + ndv.getters.inputTableRow(6).should('have.text', '9999'); + + ndv.getters.inputTableRow(6).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.actions.close(); + workflowPage.actions.openNode('Set5'); - ndv.getters.outputTableRow(1) - .should('have.text', '8888') - .realHover(); + + ndv.actions.switchInputBranch('True Branch'); + ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); + ndv.getters.outputTableRow(1).should('have.text', '8888'); + ndv.getters.outputTableRow(1).realHover(); + cy.wait(100); ndv.getters.inputHoveringItem().should('not.exist'); - ndv.getters.inputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.getters.inputTableRow(1).should('have.text', '1111'); + ndv.getters.inputTableRow(1).realHover(); + cy.wait(100); ndv.getters.outputHoveringItem().should('not.exist'); - ndv.actions.switchIntputBranch('False Branch'); - ndv.getters.inputTableRow(1) - .should('have.text', '8888') - .realHover(); - ndv.getters.outputHoveringItem().should('have.text', '8888'); + ndv.actions.switchInputBranch('False Branch'); + ndv.getters.inputTableRow(1).should('have.text', '8888'); + ndv.getters.inputTableRow(1).realHover(); - ndv.actions.changeOutputRunSelector('1 of 2 (4 items)') - ndv.getters.outputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.actions.changeOutputRunSelector('2 of 2 (4 items)'); + ndv.getters.outputTableRow(1).should('have.text', '1111'); + ndv.getters.outputTableRow(1).realHover(); + + ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); + ndv.getters.inputTableRow(1).should('have.text', '8888'); + ndv.getters.inputTableRow(1).realHover(); + ndv.getters.outputHoveringItem().should('have.text', '8888'); // todo there's a bug here need to fix ADO-534 // ndv.getters.outputHoveringItem().should('not.exist'); }); + + it('can resolve expression with paired item in multi-input node', () => { + cy.fixture('expression_with_paired_item_in_multi_input_node.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + + workflowPage.actions.zoomToFit(); + + /* prettier-ignore */ + const PINNED_DATA = [ + { + "id": "abc", + "historyId": "def", + "messages": [ + { + "id": "abc" + } + ] + }, + { + "id": "abc", + "historyId": "def", + "messages": [ + { + "id": "abc" + }, + { + "id": "abc" + }, + { + "id": "abc" + } + ] + }, + { + "id": "abc", + "historyId": "def", + "messages": [ + { + "id": "abc" + } + ] + } + ]; + /* prettier-ignore */ + workflowPage.actions.openNode('Get thread details1'); + ndv.actions.pastePinnedData(PINNED_DATA); + ndv.actions.close(); + + workflowPage.actions.executeWorkflow(); + workflowPage.actions.openNode('Switch1'); + ndv.actions.execute(); + + ndv.getters.parameterExpressionPreview('output').should('include.text', '1'); + }); }); diff --git a/cypress/e2e/25-stickies.cy.ts b/cypress/e2e/25-stickies.cy.ts index 0746fddc0326a..4cbad810f915e 100644 --- a/cypress/e2e/25-stickies.cy.ts +++ b/cypress/e2e/25-stickies.cy.ts @@ -1,8 +1,17 @@ +import { META_KEY } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { getPopper } from '../utils'; +import { Interception } from 'cypress/types/net-stubbing'; const workflowPage = new WorkflowPageClass(); -function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) { +function checkStickiesStyle( + top: number, + left: number, + height: number, + width: number, + zIndex?: number, +) { workflowPage.getters.stickies().should(($el) => { expect($el).to.have.css('top', `${top}px`); expect($el).to.have.css('left', `${left}px`); @@ -16,26 +25,33 @@ function checkStickiesStyle( top: number, left: number, height: number, width: n describe('Canvas Actions', () => { beforeEach(() => { - cy.resetAll(); - cy.skipSetup(); workflowPage.actions.visit(); - - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); }); - it('adds sticky to canvas with default text and position', () => { workflowPage.getters.addStickyButton().should('not.be.visible'); - addDefaultSticky() - workflowPage.getters.stickies().eq(0) + addDefaultSticky(); + workflowPage.actions.deselectAll(); + workflowPage.actions.addStickyFromContextMenu(); + workflowPage.actions.hitAddStickyShortcut(); + + workflowPage.getters.stickies().should('have.length', 3); + + // Should not add a sticky for ctrl+shift+s + cy.get('body') + .type(META_KEY, { delay: 500, release: false }) + .type('{shift}', { release: false }) + .type('s'); + + workflowPage.getters.stickies().should('have.length', 3); + workflowPage.getters + .stickies() + .eq(0) .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n') - .find('a').contains('Guide').should('have.attr', 'href'); + .find('a') + .contains('Guide') + .should('have.attr', 'href'); }); it('drags sticky around to top left corner', () => { @@ -59,18 +75,45 @@ describe('Canvas Actions', () => { it('deletes sticky', () => { workflowPage.actions.addSticky(); - workflowPage.getters.stickies().should('have.length', 1) + workflowPage.getters.stickies().should('have.length', 1); workflowPage.actions.deleteSticky(); - workflowPage.getters.stickies().should('have.length', 0) + workflowPage.getters.stickies().should('have.length', 0); + }); + + it('change sticky color', () => { + workflowPage.actions.addSticky(); + + workflowPage.getters.stickies().should('have.length', 1); + + workflowPage.actions.toggleColorPalette(); + + getPopper().should('be.visible'); + + workflowPage.actions.pickColor(2); + + workflowPage.actions.toggleColorPalette(); + + getPopper().should('not.be.visible'); + + workflowPage.actions.saveWorkflowOnButtonClick(); + + cy.wait('@createWorkflow').then((interception: Interception) => { + const { request } = interception; + const color = request.body?.nodes[0]?.parameters?.color; + expect(color).to.equal(2); + }); + + workflowPage.getters.stickies().should('have.length', 1); }); it('edits sticky and updates content as markdown', () => { workflowPage.actions.addSticky(); - workflowPage.getters.stickies() - .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n') + workflowPage.getters + .stickies() + .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n'); workflowPage.getters.stickies().dblclick(); workflowPage.actions.editSticky('# hello world \n ## text text'); @@ -83,8 +126,11 @@ describe('Canvas Actions', () => { moveSticky({ top: 200, left: 200 }); - dragRightEdge({ left: 200, top: 200, height: 160, width: 240 }, 100); - dragRightEdge({ left: 200, top: 200, height: 160, width: 240 }, -50); + cy.drag('[data-test-id="sticky"] [data-dir="right"]', [100, 100]); + checkStickiesStyle(100, 20, 160, 346); + + cy.drag('[data-test-id="sticky"] [data-dir="right"]', [-50, -50]); + checkStickiesStyle(100, 20, 160, 302); }); it('expands/shrinks sticky from the left edge', () => { @@ -92,66 +138,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', () => { @@ -159,34 +205,57 @@ 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) + workflowPage.getters + .canvasNodes() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', 'auto'); }); workflowPage.actions.addSticky(); - workflowPage.getters.stickies().eq(0) + workflowPage.getters + .stickies() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', '-121'); }); - workflowPage.getters.stickies().eq(1) + workflowPage.getters + .stickies() + .eq(1) .should(($el) => { expect($el).to.have.css('z-index', '-38'); }); cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-200, -200], { index: 1 }); - workflowPage.getters.stickies().eq(0) + workflowPage.getters + .stickies() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', '-121'); }); - workflowPage.getters.stickies().eq(1) + workflowPage.getters + .stickies() + .eq(1) .should(($el) => { expect($el).to.have.css('z-index', '-158'); }); + }); + + it('Empty sticky should not error when activating workflow', () => { + workflowPage.actions.addSticky(); + + workflowPage.getters.stickies().should('have.length', 1); + workflowPage.getters.stickies().dblclick(); + + workflowPage.actions.clearSticky(); + + workflowPage.actions.addNodeToCanvas('Schedule Trigger'); + + workflowPage.actions.activateWorkflow(); }); }); @@ -195,38 +264,25 @@ type Position = { left: number; }; -type BoundingBox = { - height: number; - width: number; - top: number; - left: number; -} - -function dragRightEdge(curr: BoundingBox, move: number) { - workflowPage.getters.stickies().first().then(($el) => { - const { left, top, height, width } = curr; - cy.drag(`[data-test-id="sticky"] [data-dir="right"]`, [left + width + move, 0], { abs: true }); - stickyShouldBePositionedCorrectly({ top, left }); - stickyShouldHaveCorrectSize([height, width * 1.5 + move]); - }); -} - function shouldHaveOneSticky() { workflowPage.getters.stickies().should('have.length', 1); } function shouldBeInDefaultLocation() { - workflowPage.getters.stickies().eq(0).should(($el) => { - expect($el).to.have.css('height', '160px'); - expect($el).to.have.css('width', '240px'); - }) + workflowPage.getters + .stickies() + .eq(0) + .should(($el) => { + expect($el).to.have.css('height', '160px'); + expect($el).to.have.css('width', '240px'); + }); } function shouldHaveDefaultSize() { workflowPage.getters.stickies().should(($el) => { expect($el).to.have.css('height', '160px'); expect($el).to.have.css('width', '240px'); - }) + }); } function addDefaultSticky() { @@ -237,23 +293,21 @@ function addDefaultSticky() { } function stickyShouldBePositionedCorrectly(position: Position) { - const yOffset = -60; + const yOffset = -100; const xOffset = -180; - workflowPage.getters.stickies() - .should(($el) => { - expect($el).to.have.css('top', `${yOffset + position.top}px`); - expect($el).to.have.css('left', `${xOffset + position.left}px`); - }); + workflowPage.getters.stickies().should(($el) => { + expect($el).to.have.css('top', `${yOffset + position.top}px`); + expect($el).to.have.css('left', `${xOffset + position.left}px`); + }); } function stickyShouldHaveCorrectSize(size: [number, number]) { const yOffset = 0; const xOffset = 0; - workflowPage.getters.stickies() - .should(($el) => { - expect($el).to.have.css('height', `${yOffset + size[0]}px`); - expect($el).to.have.css('width', `${xOffset + size[1]}px`); - }); + workflowPage.getters.stickies().should(($el) => { + expect($el).to.have.css('height', `${yOffset + size[0]}px`); + expect($el).to.have.css('width', `${xOffset + size[1]}px`); + }); } function moveSticky(target: Position) { diff --git a/cypress/e2e/26-resource-locator.cy.ts b/cypress/e2e/26-resource-locator.cy.ts new file mode 100644 index 0000000000000..9cea4e25a3c38 --- /dev/null +++ b/cypress/e2e/26-resource-locator.cy.ts @@ -0,0 +1,88 @@ +import { WorkflowPage, NDV, CredentialsModal } from '../pages'; +import { getVisiblePopper, getVisibleSelect } from '../utils'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); +const credentialsModal = new CredentialsModal(); + +const NO_CREDENTIALS_MESSAGE = 'Please add your credential'; +const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential'; +const MODE_SELECTOR_LIST = 'From list'; + +describe('Resource Locator', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it('should render both RLC components in google sheets', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); + ndv.getters.resourceLocator('documentId').should('be.visible'); + ndv.getters.resourceLocator('sheetName').should('be.visible'); + ndv.getters + .resourceLocatorModeSelector('documentId') + .find('input') + .should('have.value', MODE_SELECTOR_LIST); + ndv.getters + .resourceLocatorModeSelector('sheetName') + .find('input') + .should('have.value', MODE_SELECTOR_LIST); + }); + + it('should show appropriate error when credentials are not set', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); + ndv.getters.resourceLocator('documentId').should('be.visible'); + ndv.getters.resourceLocatorInput('documentId').click(); + ndv.getters.resourceLocatorErrorMessage().should('contain', NO_CREDENTIALS_MESSAGE); + }); + + it('should show appropriate error when credentials are not valid', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); + workflowPage.getters.nodeCredentialsSelect().click(); + // Add oAuth credentials + getVisibleSelect().find('li').last().click(); + credentialsModal.getters.credentialsEditModal().should('be.visible'); + credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); + credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); + credentialsModal.actions.fillCredentialsForm(); + cy.get('.el-message-box').find('button').contains('Close').click(); + ndv.getters.resourceLocatorInput('documentId').click(); + ndv.getters.resourceLocatorErrorMessage().should('contain', INVALID_CREDENTIALS_MESSAGE); + }); + + it('should reset resource locator when dependent field is changed', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Google Sheets', true, true, 'Update row in sheet'); + ndv.actions.setRLCValue('documentId', '123'); + ndv.actions.setRLCValue('sheetName', '123'); + ndv.actions.setRLCValue('documentId', '321'); + ndv.getters.resourceLocatorInput('sheetName').should('have.value', ''); + }); + + // unlike RMC and remote options, RLC does not support loadOptionDependsOn + it('should retrieve list options when other params throw errors', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Resource Locator' }); + + ndv.getters.resourceLocatorInput('rlc').click(); + + cy.getByTestId('rlc-item').should('exist'); + getVisiblePopper() + .should('have.length', 1) + .findChildByTestId('rlc-item') + .should('have.length', 5); + + ndv.actions.setInvalidExpression({ fieldName: 'fieldId' }); + + ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview + + ndv.getters.resourceLocatorInput('rlc').click(); + + cy.getByTestId('rlc-item').should('exist'); + getVisiblePopper() + .should('have.length', 1) + .findChildByTestId('rlc-item') + .should('have.length', 5); + }); +}); diff --git a/cypress/e2e/27-cloud.cy.ts b/cypress/e2e/27-cloud.cy.ts new file mode 100644 index 0000000000000..965bc5bccfedf --- /dev/null +++ b/cypress/e2e/27-cloud.cy.ts @@ -0,0 +1,119 @@ +import { + BannerStack, + MainSidebar, + WorkflowPage, + visitPublicApiPage, + getPublicApiUpgradeCTA, +} from '../pages'; +import planData from '../fixtures/Plan_data_opt_in_trial.json'; +import { INSTANCE_OWNER } from '../constants'; + +const mainSidebar = new MainSidebar(); +const bannerStack = new BannerStack(); +const workflowPage = new WorkflowPage(); + +describe('Cloud', { disableAutoLogin: true }, () => { + before(() => { + const now = new Date(); + const fiveDaysFromNow = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000); + planData.expirationDate = fiveDaysFromNow.toJSON(); + }); + + describe('BannerStack', () => { + it('should render trial banner for opt-in cloud user', () => { + cy.intercept('GET', '/rest/admin/cloud-plan', { + body: planData, + }).as('getPlanData'); + + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' }, n8nMetadata: { userId: 1 } }, + }); + }); + }).as('loadSettings'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + cy.visit(workflowPage.url); + + cy.wait('@getPlanData'); + + bannerStack.getters.banner().should('be.visible'); + + mainSidebar.actions.signout(); + + bannerStack.getters.banner().should('not.be.visible'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + cy.visit(workflowPage.url); + + bannerStack.getters.banner().should('be.visible'); + + mainSidebar.actions.signout(); + }); + + it('should not render opt-in-trial banner for non cloud deployment', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'default' } }, + }); + }); + }).as('loadSettings'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + cy.visit(workflowPage.url); + + bannerStack.getters.banner().should('not.be.visible'); + + mainSidebar.actions.signout(); + }); + }); + + describe('Admin Home', () => { + it('Should show admin button', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' }, n8nMetadata: { userId: 1 } }, + }); + }); + }).as('loadSettings'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + cy.visit(workflowPage.url); + + mainSidebar.getters.adminPanel().should('be.visible'); + }); + }); + + describe('Public API', () => { + it('Should show upgrade CTA for Public API if user is trialing', () => { + cy.intercept('GET', '/rest/admin/cloud-plan', { + body: planData, + }).as('getPlanData'); + + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { + ...res.body.data, + deployment: { type: 'cloud' }, + n8nMetadata: { userId: 1 }, + }, + }); + }); + }).as('loadSettings'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + visitPublicApiPage(); + + getPublicApiUpgradeCTA().should('be.visible'); + }); + }); +}); diff --git a/cypress/e2e/27-two-factor-authentication.cy.ts b/cypress/e2e/27-two-factor-authentication.cy.ts new file mode 100644 index 0000000000000..91f6ca57a24cc --- /dev/null +++ b/cypress/e2e/27-two-factor-authentication.cy.ts @@ -0,0 +1,81 @@ +import { MainSidebar } from './../pages/sidebar/main-sidebar'; +import { INSTANCE_OWNER, INSTANCE_ADMIN, BACKEND_BASE_URL } from '../constants'; +import { SigninPage } from '../pages'; +import { PersonalSettingsPage } from '../pages/settings-personal'; +import { MfaLoginPage } from '../pages/mfa-login'; +import generateOTPToken from 'cypress-otp'; + +const MFA_SECRET = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD'; + +const RECOVERY_CODE = 'd04ea17f-e8b2-4afa-a9aa-57a2c735b30e'; + +const user = { + email: INSTANCE_OWNER.email, + password: INSTANCE_OWNER.password, + firstName: 'User', + lastName: 'A', + mfaEnabled: false, + mfaSecret: MFA_SECRET, + mfaRecoveryCodes: [RECOVERY_CODE], +}; + +const admin = { + email: INSTANCE_ADMIN.email, + password: INSTANCE_ADMIN.password, + firstName: 'Admin', + lastName: 'B', + mfaEnabled: false, + mfaSecret: MFA_SECRET, + mfaRecoveryCodes: [RECOVERY_CODE], +}; + +const mfaLoginPage = new MfaLoginPage(); +const signinPage = new SigninPage(); +const personalSettingsPage = new PersonalSettingsPage(); +const mainSidebar = new MainSidebar(); + +describe('Two-factor authentication', () => { + beforeEach(() => { + Cypress.session.clearAllSavedSessions(); + cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, { + owner: user, + members: [], + admin, + }); + cy.on('uncaught:exception', (err, runnable) => { + expect(err.message).to.include('Not logged in'); + return false; + }); + cy.intercept('GET', '/rest/mfa/qr').as('getMfaQrCode'); + }); + + it('Should be able to login with MFA token', () => { + const { email, password } = user; + signinPage.actions.loginWithEmailAndPassword(email, password); + personalSettingsPage.actions.enableMfa(); + mainSidebar.actions.signout(); + const token = generateOTPToken(user.mfaSecret); + mfaLoginPage.actions.loginWithMfaToken(email, password, token); + mainSidebar.actions.signout(); + }); + + it('Should be able to login with recovery code', () => { + const { email, password } = user; + signinPage.actions.loginWithEmailAndPassword(email, password); + personalSettingsPage.actions.enableMfa(); + mainSidebar.actions.signout(); + mfaLoginPage.actions.loginWithRecoveryCode(email, password, user.mfaRecoveryCodes[0]); + mainSidebar.actions.signout(); + }); + + it('Should be able to disable MFA in account', () => { + const { email, password } = user; + signinPage.actions.loginWithEmailAndPassword(email, password); + personalSettingsPage.actions.enableMfa(); + mainSidebar.actions.signout(); + const token = generateOTPToken(user.mfaSecret); + mfaLoginPage.actions.loginWithMfaToken(email, password, token); + personalSettingsPage.actions.disableMfa(); + mainSidebar.actions.signout(); + }); +}); diff --git a/cypress/e2e/28-debug.cy.ts b/cypress/e2e/28-debug.cy.ts new file mode 100644 index 0000000000000..955d33ce28c28 --- /dev/null +++ b/cypress/e2e/28-debug.cy.ts @@ -0,0 +1,139 @@ +import { + HTTP_REQUEST_NODE_NAME, + IF_NODE_NAME, + INSTANCE_OWNER, + MANUAL_TRIGGER_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, +} from '../constants'; +import { WorkflowPage, NDV, WorkflowExecutionsTab } from '../pages'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); +const executionsTab = new WorkflowExecutionsTab(); + +describe('Debug', () => { + beforeEach(() => { + cy.enableFeature('debugInEditor'); + }); + + it('should be able to debug executions', () => { + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + cy.intercept('GET', '/rest/executions/*').as('getExecution'); + cy.intercept('POST', '/rest/workflows/run').as('postWorkflowRun'); + + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + + workflowPage.actions.visit(); + + workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME); + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.typeIntoParameterInput('url', 'https://foo.bar'); + ndv.actions.close(); + + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); + + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + workflowPage.actions.executeWorkflow(); + + cy.wait(['@postWorkflowRun']); + + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + + executionsTab.getters.executionDebugButton().should('have.text', 'Debug in editor').click(); + cy.url().should('include', '/debug'); + cy.get('.el-notification').contains('Execution data imported').should('be.visible'); + cy.get('.matching-pinned-nodes-confirmation').should('not.exist'); + + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.clearParameterInput('url'); + ndv.actions.typeIntoParameterInput('url', 'https://postman-echo.com/get?foo1=bar1&foo2=bar2'); + ndv.actions.close(); + + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + cy.url().should('not.include', '/debug'); + + workflowPage.actions.executeWorkflow(); + + cy.wait(['@postWorkflowRun']); + + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.pinData(); + ndv.actions.close(); + + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + + executionsTab.getters.executionListItems().should('have.length', 2).first().click(); + cy.wait(['@getExecution']); + + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + + let confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 2); + confirmDialog.get('.btn--cancel').click(); + + cy.wait(['@getExecutions']); + + executionsTab.getters.executionListItems().should('have.length', 2).first().click(); + cy.wait(['@getExecution']); + + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 2); + confirmDialog.get('.btn--confirm').click(); + cy.url().should('include', '/debug'); + + workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon'); + workflowPage.getters + .canvasNodes() + .not(':first') + .should('not.have.descendants', '.node-pin-data-icon'); + + cy.reload(true); + cy.wait(['@getExecution']); + + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 1); + confirmDialog.get('.btn--confirm').click(); + + workflowPage.getters.canvasNodePlusEndpointByName(EDIT_FIELDS_SET_NODE_NAME).click(); + workflowPage.actions.addNodeToCanvas(IF_NODE_NAME, false); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + cy.url().should('not.include', '/debug'); + + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 1); + confirmDialog.get('.btn--confirm').click(); + cy.url().should('include', '/debug'); + + workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty'); + + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.pinDataButton().click(); + ndv.actions.close(); + + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + cy.url().should('not.include', '/debug'); + + workflowPage.actions.executeWorkflow(); + workflowPage.actions.zoomToFit(); + workflowPage.actions.deleteNode(IF_NODE_NAME); + + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions']); + executionsTab.getters.executionListItems().should('have.length', 3).first().click(); + cy.wait(['@getExecution']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + cy.get('.el-notification').contains("Some execution data wasn't imported").should('be.visible'); + cy.url().should('include', '/debug'); + }); +}); diff --git a/cypress/e2e/28-resource-mapper.cy.ts b/cypress/e2e/28-resource-mapper.cy.ts new file mode 100644 index 0000000000000..3f5909de62abd --- /dev/null +++ b/cypress/e2e/28-resource-mapper.cy.ts @@ -0,0 +1,95 @@ +import { WorkflowPage, NDV } from '../pages'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); + +describe('Resource Mapper', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it('should not retrieve list options when required params throw errors', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); + + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 3); + + ndv.actions.setInvalidExpression({ fieldName: 'fieldId' }); + + ndv.actions.refreshResourceMapperColumns(); + ndv.getters.resourceMapperFieldsContainer().should('not.exist'); + }); + + it('should retrieve list options when optional params throw errors', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); + + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 3); + + ndv.actions.setInvalidExpression({ fieldName: 'otherField' }); + + ndv.actions.refreshResourceMapperColumns(); + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 3); + }); + + it('should correctly delete single field', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); + ndv.getters.parameterInput('id').type('001'); + ndv.getters.parameterInput('name').type('John'); + ndv.getters.parameterInput('age').type('30'); + ndv.getters.nodeExecuteButton().click(); + ndv.getters.outputTableHeaderByText('id').should('exist'); + ndv.getters.outputTableHeaderByText('name').should('exist'); + ndv.getters.outputTableHeaderByText('age').should('exist'); + // Remove the 'name' field + ndv.getters.resourceMapperRemoveFieldButton('name').should('exist').click({ force: true }); + ndv.getters.nodeExecuteButton().click(); + ndv.getters.parameterInput('id').should('exist'); + ndv.getters.outputTableHeaderByText('id').should('exist'); + // After removing the field, text field and the output table column for the 'name' should not be there anymore + ndv.getters.parameterInput('age').should('exist'); + ndv.getters.outputTableHeaderByText('age').should('exist'); + ndv.getters.parameterInput('name').should('not.exist'); + ndv.getters.outputTableHeaderByText('name').should('not.exist'); + }); + + it('should correctly delete all fields', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); + ndv.getters.parameterInput('id').type('001'); + ndv.getters.parameterInput('name').type('John'); + ndv.getters.parameterInput('age').type('30'); + ndv.getters.nodeExecuteButton().click(); + ndv.getters.outputTableHeaderByText('id').should('exist'); + ndv.getters.outputTableHeaderByText('name').should('exist'); + ndv.getters.outputTableHeaderByText('age').should('exist'); + ndv.getters.resourceMapperColumnsOptionsButton().click(); + // Click on the 'Remove All Fields' option + ndv.getters.resourceMapperRemoveAllFieldsOption().should('be.visible').click(); + ndv.getters.nodeExecuteButton().click(); + ndv.getters.parameterInput('id').should('exist'); + ndv.getters.outputTableHeaderByText('id').should('exist'); + // After removing the all fields, only required one should be in UI and output table + ndv.getters.parameterInput('name').should('not.exist'); + ndv.getters.outputTableHeaderByText('name').should('not.exist'); + ndv.getters.parameterInput('age').should('not.exist'); + ndv.getters.outputTableHeaderByText('age').should('not.exist'); + }); +}); diff --git a/cypress/e2e/29-sql-editor.cy.ts b/cypress/e2e/29-sql-editor.cy.ts new file mode 100644 index 0000000000000..86299d5f67cdc --- /dev/null +++ b/cypress/e2e/29-sql-editor.cy.ts @@ -0,0 +1,71 @@ +import { WorkflowPage, NDV } from '../pages'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); + +describe('SQL editors', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it('should preserve changes when opening-closing Postgres node', () => { + workflowPage.actions.addInitialNodeToCanvas('Postgres', { + action: 'Execute a SQL query', + keepNdvOpen: true, + }); + ndv.getters + .sqlEditorContainer() + .click() + .find('.cm-content') + .type('SELECT * FROM `testTable`') + .type('{esc}'); + ndv.actions.close(); + workflowPage.actions.openNode('Postgres'); + ndv.getters.sqlEditorContainer().find('.cm-content').type('{end} LIMIT 10').type('{esc}'); + ndv.actions.close(); + workflowPage.actions.openNode('Postgres'); + ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10'); + }); + + it('should update expression output dropdown as the query is edited', () => { + workflowPage.actions.addInitialNodeToCanvas('MySQL', { + action: 'Execute a SQL query', + }); + ndv.actions.close(); + + workflowPage.actions.openNode('When clicking "Test workflow"'); + ndv.actions.setPinnedData([{ table: 'test_table' }]); + ndv.actions.close(); + + workflowPage.actions.openNode('MySQL'); + ndv.getters + .sqlEditorContainer() + .find('.cm-content') + .type('SELECT * FROM {{ $json.table }}', { parseSpecialCharSequences: false }); + workflowPage.getters + .inlineExpressionEditorOutput() + .should('have.text', 'SELECT * FROM test_table'); + }); + + it('should not push NDV header out with a lot of code in Postgres editor', () => { + workflowPage.actions.addInitialNodeToCanvas('Postgres', { + action: 'Execute a SQL query', + keepNdvOpen: true, + }); + cy.fixture('Dummy_javascript.txt').then((code) => { + ndv.getters.sqlEditorContainer().find('.cm-content').paste(code); + }); + ndv.getters.nodeExecuteButton().should('be.visible'); + }); + + it('should not push NDV header out with a lot of code in MySQL editor', () => { + workflowPage.actions.addInitialNodeToCanvas('MySQL', { + action: 'Execute a SQL query', + keepNdvOpen: true, + }); + cy.fixture('Dummy_javascript.txt').then((code) => { + ndv.getters.sqlEditorContainer().find('.cm-content').paste(code); + }); + ndv.getters.nodeExecuteButton().should('be.visible'); + }); +}); diff --git a/cypress/e2e/29-templates.cy.ts b/cypress/e2e/29-templates.cy.ts new file mode 100644 index 0000000000000..34762b12fcfb2 --- /dev/null +++ b/cypress/e2e/29-templates.cy.ts @@ -0,0 +1,46 @@ +import { TemplatesPage } from '../pages/templates'; +import { WorkflowsPage } from '../pages/workflows'; +import { MainSidebar } from '../pages/sidebar/main-sidebar'; + +const templatesPage = new TemplatesPage(); +const workflowsPage = new WorkflowsPage(); +const mainSidebar = new MainSidebar(); + +describe('Workflow templates', () => { + beforeEach(() => { + cy.intercept('GET', '**/rest/settings', (req) => { + // Disable cache + delete req.headers['if-none-match'] + req.reply((res) => { + if (res.body.data) { + // Disable custom templates host if it has been overridden by another intercept + res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' }; + } + }); + }).as('settingsRequest'); + }); + + it('Opens website when clicking templates sidebar link', () => { + cy.visit(workflowsPage.url); + mainSidebar.getters.menuItem('Templates').should('be.visible'); + // Templates should be a link to the website + mainSidebar.getters.templates().parent('a').should('have.attr', 'href').and('include', 'https://n8n.io/workflows'); + // Link should contain instance address and n8n version + mainSidebar.getters.templates().parent('a').then(($a) => { + const href = $a.attr('href'); + const params = new URLSearchParams(href); + // Link should have all mandatory parameters expected on the website + expect(decodeURIComponent(`${params.get('utm_instance')}`)).to.include(window.location.origin); + expect(params.get('utm_n8n_version')).to.match(/[0-9]+\.[0-9]+\.[0-9]+/); + expect(params.get('utm_awc')).to.match(/[0-9]+/); + }); + mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank'); + }); + + it('Redirects to website when visiting templates page directly', () => { + cy.visit(templatesPage.url); + cy.origin('https://n8n.io', () => { + cy.url().should('include', 'https://n8n.io/workflows'); + }) + }); +}); diff --git a/cypress/e2e/3-default-owner.cy.ts b/cypress/e2e/3-default-owner.cy.ts deleted file mode 100644 index 1871dd9c6c950..0000000000000 --- a/cypress/e2e/3-default-owner.cy.ts +++ /dev/null @@ -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.resetAll(); - 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 }); - - messageBox.getters.content().should('contain.text', '1 existing workflow and 1 credential'); - - messageBox.actions.confirm(); - 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); - }); -}); diff --git a/cypress/e2e/30-editor-after-route-changes.cy.ts b/cypress/e2e/30-editor-after-route-changes.cy.ts new file mode 100644 index 0000000000000..727078e7352c9 --- /dev/null +++ b/cypress/e2e/30-editor-after-route-changes.cy.ts @@ -0,0 +1,216 @@ +import { + CODE_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, + IF_NODE_NAME, + INSTANCE_OWNER, + SCHEDULE_TRIGGER_NODE_NAME, +} from '../constants'; +import { + WorkflowExecutionsTab, + WorkflowPage as WorkflowPageClass, + WorkflowHistoryPage, +} from '../pages'; + +const workflowPage = new WorkflowPageClass(); +const executionsTab = new WorkflowExecutionsTab(); +const workflowHistoryPage = new WorkflowHistoryPage(); + +const createNewWorkflowAndActivate = () => { + workflowPage.actions.visit(); + workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); + workflowPage.actions.saveWorkflowOnButtonClick(); + workflowPage.actions.activateWorkflow(); + cy.get('.el-notification .el-notification--error').should('not.exist'); +}; + +const editWorkflowAndDeactivate = () => { + workflowPage.getters.canvasNodePlusEndpointByName(SCHEDULE_TRIGGER_NODE_NAME).click(); + workflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); + cy.get('.jtk-connector').should('have.length', 1); + workflowPage.actions.saveWorkflowOnButtonClick(); + workflowPage.getters.activatorSwitch().click(); + workflowPage.actions.zoomToFit(); + cy.get('.el-notification .el-notification--error').should('not.exist'); +}; + +const editWorkflowMoreAndActivate = () => { + cy.drag(workflowPage.getters.getEndpointSelector('plus', EDIT_FIELDS_SET_NODE_NAME), [200, 200], { + realMouse: true, + }); + workflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + + workflowPage.actions.addNodeToCanvas(CODE_NODE_NAME, false); + workflowPage.getters.nodeViewBackground().click(600, 200, { force: true }); + cy.get('.jtk-connector').should('have.length', 2); + workflowPage.actions.zoomToFit(); + workflowPage.actions.saveWorkflowOnButtonClick(); + + workflowPage.actions.addNodeToCanvas(IF_NODE_NAME); + workflowPage.getters.nodeViewBackground().click(600, 200, { force: true }); + cy.get('.jtk-connector').should('have.length', 2); + + const position = { + top: 0, + left: 0, + }; + workflowPage.getters + .canvasNodeByName(IF_NODE_NAME) + .click() + .then(($element) => { + position.top = $element.position().top; + position.left = $element.position().left; + }); + + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 200], { clickToFinish: true }); + workflowPage.getters + .canvasNodes() + .last() + .then(($element) => { + const finalPosition = { + top: $element.position().top, + left: $element.position().left, + }; + + expect(finalPosition.top).to.be.greaterThan(position.top); + expect(finalPosition.left).to.be.greaterThan(position.left); + }); + + cy.draganddrop( + workflowPage.getters.getEndpointSelector('output', CODE_NODE_NAME), + workflowPage.getters.getEndpointSelector('input', IF_NODE_NAME), + ); + cy.get('.jtk-connector').should('have.length', 3); + + workflowPage.actions.saveWorkflowOnButtonClick(); + workflowPage.getters.activatorSwitch().click(); + cy.get('.el-notification .el-notification--error').should('not.exist'); +}; + +const switchBetweenEditorAndHistory = () => { + workflowPage.getters.workflowHistoryButton().click(); + cy.wait(['@getHistory']); + cy.wait(['@getVersion']); + + cy.intercept('GET', '/rest/workflows/*').as('workflowGet'); + workflowHistoryPage.getters.workflowHistoryCloseButton().click(); + cy.wait(['@workflowGet']); + cy.wait(1000); + + workflowPage.getters.canvasNodes().first().should('be.visible'); + workflowPage.getters.canvasNodes().last().should('be.visible'); +}; + +const switchBetweenEditorAndWorkflowlist = () => { + cy.getByTestId('menu-item').first().click(); + cy.wait(['@getUsers', '@getWorkflows', '@getActiveWorkflows', '@getCredentials']); + + cy.getByTestId('resources-list-item').first().click(); + + workflowPage.getters.canvasNodes().first().should('be.visible'); + workflowPage.getters.canvasNodes().last().should('be.visible'); +}; + +const zoomInAndCheckNodes = () => { + cy.getByTestId('zoom-in-button').click(); + cy.getByTestId('zoom-in-button').click(); + cy.getByTestId('zoom-in-button').click(); + cy.getByTestId('zoom-in-button').click(); + + workflowPage.getters.canvasNodes().first().should('not.be.visible'); + workflowPage.getters.canvasNodes().last().should('not.be.visible'); +}; + +describe('Editor actions should work', () => { + beforeEach(() => { + cy.enableFeature('debugInEditor'); + cy.enableFeature('workflowHistory'); + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + createNewWorkflowAndActivate(); + }); + + it('after saving a new workflow', () => { + editWorkflowAndDeactivate(); + editWorkflowMoreAndActivate(); + }); + + it('after switching between Editor and Executions', () => { + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions']); + cy.wait(500); + executionsTab.actions.switchToEditorTab(); + editWorkflowAndDeactivate(); + editWorkflowMoreAndActivate(); + }); + + it('after switching between Editor and Debug', () => { + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + cy.intercept('GET', '/rest/executions/*').as('getExecution'); + cy.intercept('POST', '/rest/workflows/run').as('postWorkflowRun'); + + editWorkflowAndDeactivate(); + workflowPage.actions.executeWorkflow(); + cy.wait(['@postWorkflowRun']); + + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions']); + + executionsTab.getters.executionListItems().should('have.length', 1).first().click(); + cy.wait(['@getExecution']); + + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + editWorkflowMoreAndActivate(); + }); + + it('after switching between Editor and Workflow history', () => { + cy.intercept('GET', '/rest/workflow-history/workflow/*/version/*').as('getVersion'); + cy.intercept('GET', '/rest/workflow-history/workflow/*').as('getHistory'); + + editWorkflowAndDeactivate(); + workflowPage.getters.workflowHistoryButton().click(); + cy.wait(['@getHistory']); + cy.wait(['@getVersion']); + + cy.intercept('GET', '/rest/workflows/*').as('workflowGet'); + workflowHistoryPage.getters.workflowHistoryCloseButton().click(); + cy.wait(['@workflowGet']); + cy.wait(1000); + + editWorkflowMoreAndActivate(); + }); +}); + +describe('Editor zoom should work after route changes', () => { + beforeEach(() => { + cy.enableFeature('debugInEditor'); + cy.enableFeature('workflowHistory'); + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + workflowPage.actions.visit(); + cy.createFixtureWorkflow('Lots_of_nodes.json', `Lots of nodes`); + workflowPage.actions.saveWorkflowOnButtonClick(); + }); + + it('after switching between Editor and Workflow history and Workflow list', () => { + cy.intercept('GET', '/rest/workflow-history/workflow/*/version/*').as('getVersion'); + cy.intercept('GET', '/rest/workflow-history/workflow/*').as('getHistory'); + cy.intercept('GET', '/rest/users').as('getUsers'); + cy.intercept('GET', '/rest/workflows').as('getWorkflows'); + cy.intercept('GET', '/rest/active-workflows').as('getActiveWorkflows'); + cy.intercept('GET', '/rest/credentials').as('getCredentials'); + + switchBetweenEditorAndHistory(); + zoomInAndCheckNodes(); + switchBetweenEditorAndHistory(); + switchBetweenEditorAndHistory(); + zoomInAndCheckNodes(); + switchBetweenEditorAndWorkflowlist(); + zoomInAndCheckNodes(); + switchBetweenEditorAndWorkflowlist(); + switchBetweenEditorAndWorkflowlist(); + zoomInAndCheckNodes(); + switchBetweenEditorAndHistory(); + switchBetweenEditorAndWorkflowlist(); + }); +}); diff --git a/cypress/e2e/30-if-node.cy.ts b/cypress/e2e/30-if-node.cy.ts new file mode 100644 index 0000000000000..95ed1e9a0d34d --- /dev/null +++ b/cypress/e2e/30-if-node.cy.ts @@ -0,0 +1,58 @@ +import { IF_NODE_NAME } from '../constants'; +import { WorkflowPage, NDV } from '../pages'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); + +const FILTER_PARAM_NAME = 'conditions'; + +describe('If Node (filter component)', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + it('should be able to create and delete multiple conditions', () => { + workflowPage.actions.addInitialNodeToCanvas(IF_NODE_NAME, { keepNdvOpen: true }); + + // Default state + ndv.getters.filterComponent(FILTER_PARAM_NAME).should('exist'); + ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 1); + ndv.getters + .filterConditionOperator(FILTER_PARAM_NAME) + .find('input') + .should('have.value', 'is equal to'); + + // Add + ndv.actions.addFilterCondition(FILTER_PARAM_NAME); + ndv.getters.filterConditionLeft(FILTER_PARAM_NAME, 0).find('input').type('first left'); + ndv.getters.filterConditionLeft(FILTER_PARAM_NAME, 1).find('input').type('second left'); + ndv.actions.addFilterCondition(FILTER_PARAM_NAME); + ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 3); + + // Delete + ndv.actions.removeFilterCondition(FILTER_PARAM_NAME, 0); + ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 2); + ndv.getters + .filterConditionLeft(FILTER_PARAM_NAME, 0) + .find('input') + .should('have.value', 'second left'); + ndv.actions.removeFilterCondition(FILTER_PARAM_NAME, 1); + ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 1); + }); + + it('should correctly evaluate conditions', () => { + cy.fixture('Test_workflow_filter.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + + workflowPage.actions.zoomToFit(); + workflowPage.actions.executeWorkflow(); + + workflowPage.actions.openNode('Then'); + ndv.getters.outputPanel().contains('3 items').should('exist'); + ndv.actions.close(); + + workflowPage.actions.openNode('Else'); + ndv.getters.outputPanel().contains('1 item').should('exist'); + }); +}); diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts new file mode 100644 index 0000000000000..9536b3cf60b5a --- /dev/null +++ b/cypress/e2e/30-langchain.cy.ts @@ -0,0 +1,334 @@ +import { + AGENT_NODE_NAME, + MANUAL_CHAT_TRIGGER_NODE_NAME, + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + MANUAL_TRIGGER_NODE_NAME, + AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, + AI_TOOL_CALCULATOR_NODE_NAME, + AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_WIKIPEDIA_NODE_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, +} from './../constants'; +import { createMockNodeExecutionData, runMockWorkflowExcution } from '../utils'; +import { + addLanguageModelNodeToParent, + addMemoryNodeToParent, + addNodeToCanvas, + addOutputParserNodeToParent, + addToolNodeToParent, + clickExecuteWorkflowButton, + clickManualChatButton, + disableNode, + getExecuteWorkflowButton, + navigateToNewWorkflowPage, + openNode, +} from '../composables/workflow'; +import { + clickCreateNewCredential, + clickExecuteNode, + clickGetBackToCanvas, + toggleParameterCheckboxInputByName, +} from '../composables/ndv'; +import { setCredentialValues } from '../composables/modals/credential-modal'; +import { + closeManualChatModal, + getManualChatDialog, + getManualChatMessages, + getManualChatModal, + getManualChatModalLogs, + getManualChatModalLogsEntries, + getManualChatModalLogsTree, + sendManualChatMessage, +} from '../composables/modals/chat-modal'; + +describe('Langchain Integration', () => { + beforeEach(() => { + navigateToNewWorkflowPage(); + }); + + it('should not open chat modal', () => { + addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); + + clickGetBackToCanvas(); + + addNodeToCanvas(AGENT_NODE_NAME, true, true); + clickGetBackToCanvas(); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + clickGetBackToCanvas(); + + clickExecuteWorkflowButton(); + getManualChatModal().should('not.exist'); + }); + + it('should disable test workflow button', () => { + addNodeToCanvas('Schedule Trigger', true); + addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); + + clickGetBackToCanvas(); + + addNodeToCanvas(AGENT_NODE_NAME, true, true); + clickGetBackToCanvas(); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + clickGetBackToCanvas(); + + disableNode('Schedule Trigger'); + + getExecuteWorkflowButton().should('be.disabled'); + }); + + it('should add nodes to all Agent node input types', () => { + addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + addNodeToCanvas(AGENT_NODE_NAME, true, true); + toggleParameterCheckboxInputByName('hasOutputParser'); + clickGetBackToCanvas(); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + clickGetBackToCanvas(); + + addMemoryNodeToParent(AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, AGENT_NODE_NAME); + clickGetBackToCanvas(); + + addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); + clickGetBackToCanvas(); + + addOutputParserNodeToParent(AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, AGENT_NODE_NAME); + clickGetBackToCanvas(); + }); + + it('should add multiple tool nodes to Agent node tool input type', () => { + addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + addNodeToCanvas(AGENT_NODE_NAME, true); + + [ + AI_TOOL_CALCULATOR_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_WIKIPEDIA_NODE_NAME, + ].forEach((tool) => { + addToolNodeToParent(tool, AGENT_NODE_NAME); + clickGetBackToCanvas(); + }); + }); + + it('should be able to open and execute Basic LLM Chain node', () => { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + addNodeToCanvas(BASIC_LLM_CHAIN_NODE_NAME, true); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + true, + ); + + clickCreateNewCredential(); + setCredentialValues({ + apiKey: 'sk_test_123', + }); + clickGetBackToCanvas(); + + openNode(BASIC_LLM_CHAIN_NODE_NAME); + const inputMessage = 'Hello!'; + const outputMessage = 'Hi there! How can I assist you today?'; + + clickExecuteNode(); + runMockWorkflowExcution({ + trigger: () => sendManualChatMessage(inputMessage), + runData: [ + createMockNodeExecutionData(BASIC_LLM_CHAIN_NODE_NAME, { + jsonData: { + main: { output: outputMessage }, + }, + metadata: { + subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], + }, + }), + ], + lastNodeExecuted: BASIC_LLM_CHAIN_NODE_NAME, + }); + + getManualChatDialog().should('contain', outputMessage); + }); + + it('should be able to open and execute Agent node', () => { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + addNodeToCanvas(AGENT_NODE_NAME, true); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + + clickCreateNewCredential(); + setCredentialValues({ + apiKey: 'sk_test_123', + }); + clickGetBackToCanvas(); + + openNode(AGENT_NODE_NAME); + + const inputMessage = 'Hello!'; + const outputMessage = 'Hi there! How can I assist you today?'; + + clickExecuteNode(); + runMockWorkflowExcution({ + trigger: () => sendManualChatMessage(inputMessage), + runData: [ + createMockNodeExecutionData(AGENT_NODE_NAME, { + jsonData: { + main: { output: outputMessage }, + }, + metadata: { + subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], + }, + }), + ], + lastNodeExecuted: AGENT_NODE_NAME, + }); + + getManualChatDialog().should('contain', outputMessage); + }); + + it('should add and use Manual Chat Trigger node together with Agent node', () => { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + addNodeToCanvas(AGENT_NODE_NAME, true); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + + clickCreateNewCredential(); + setCredentialValues({ + apiKey: 'sk_test_123', + }); + clickGetBackToCanvas(); + + clickManualChatButton(); + + getManualChatModalLogs().should('not.exist'); + + const inputMessage = 'Hello!'; + const outputMessage = 'Hi there! How can I assist you today?'; + + runMockWorkflowExcution({ + trigger: () => { + sendManualChatMessage(inputMessage); + }, + runData: [ + createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, { + jsonData: { + main: { input: inputMessage }, + }, + }), + createMockNodeExecutionData(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { + jsonData: { + ai_languageModel: { + response: { + generations: [ + { + text: `{ + "action": "Final Answer", + "action_input": "${outputMessage}" +}`, + message: { + lc: 1, + type: 'constructor', + id: ['langchain', 'schema', 'AIMessage'], + kwargs: { + content: `{ + "action": "Final Answer", + "action_input": "${outputMessage}" +}`, + additional_kwargs: {}, + }, + }, + generationInfo: { finish_reason: 'stop' }, + }, + ], + llmOutput: { + tokenUsage: { + completionTokens: 26, + promptTokens: 519, + totalTokens: 545, + }, + }, + }, + }, + }, + inputOverride: { + ai_languageModel: [ + [ + { + json: { + messages: [ + { + lc: 1, + type: 'constructor', + id: ['langchain', 'schema', 'SystemMessage'], + kwargs: { + content: + 'Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist. However, above all else, all responses must adhere to the format of RESPONSE FORMAT INSTRUCTIONS.', + additional_kwargs: {}, + }, + }, + { + lc: 1, + type: 'constructor', + id: ['langchain', 'schema', 'HumanMessage'], + kwargs: { + content: + 'TOOLS\n------\nAssistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:\n\n\n\nRESPONSE FORMAT INSTRUCTIONS\n----------------------------\n\nOutput a JSON markdown code snippet containing a valid JSON object in one of two formats:\n\n**Option 1:**\nUse this if you want the human to use a tool.\nMarkdown code snippet formatted in the following schema:\n\n```json\n{\n "action": string, // The action to take. Must be one of []\n "action_input": string // The input to the action. May be a stringified object.\n}\n```\n\n**Option #2:**\nUse this if you want to respond directly and conversationally to the human. Markdown code snippet formatted in the following schema:\n\n```json\n{\n "action": "Final Answer",\n "action_input": string // You should put what you want to return to use here and make sure to use valid json newline characters.\n}\n```\n\nFor both options, remember to always include the surrounding markdown code snippet delimiters (begin with "```json" and end with "```")!\n\n\nUSER\'S INPUT\n--------------------\nHere is the user\'s input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):\n\nHello!', + additional_kwargs: {}, + }, + }, + ], + options: { stop: ['Observation:'], promptIndex: 0 }, + }, + }, + ], + ], + }, + }), + createMockNodeExecutionData(AGENT_NODE_NAME, { + jsonData: { + main: { output: 'Hi there! How can I assist you today?' }, + }, + metadata: { + subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], + }, + }), + ], + lastNodeExecuted: AGENT_NODE_NAME, + }); + + const messages = getManualChatMessages(); + messages.should('have.length', 2); + messages.should('contain', inputMessage); + messages.should('contain', outputMessage); + + getManualChatModalLogsTree().should('be.visible'); + getManualChatModalLogsEntries().should('have.length', 1); + + closeManualChatModal(); + }); +}); diff --git a/cypress/e2e/30-workflow-filters.cy.ts b/cypress/e2e/30-workflow-filters.cy.ts new file mode 100644 index 0000000000000..634f95ba06a6f --- /dev/null +++ b/cypress/e2e/30-workflow-filters.cy.ts @@ -0,0 +1,118 @@ +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; + +import { MainSidebar } from '../pages'; +import { INSTANCE_OWNER } from '../constants'; + +const WorkflowsPage = new WorkflowsPageClass(); +const WorkflowPages = new WorkflowPageClass(); +const mainSidebar = new MainSidebar(); + +describe.skip('Workflow filters', () => { + before(() => { + cy.enableFeature('sharing', true); + }); + + beforeEach(() => { + cy.visit(WorkflowsPage.url); + }); + + it('Should filter by tags', () => { + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.newWorkflowButtonCard().click(); + cy.createFixtureWorkflow('Test_workflow_1.json', `Workflow 1`); + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.createWorkflowButton().click(); + cy.createFixtureWorkflow('Test_workflow_2.json', `Workflow 2`); + cy.visit(WorkflowsPage.url); + + WorkflowsPage.getters.workflowFilterButton().click(); + WorkflowsPage.getters.workflowTagsDropdown().click(); + WorkflowsPage.getters.workflowTagItem('other-tag-1').click(); + cy.get('body').click(0, 0); + + WorkflowsPage.getters.workflowCards().should('have.length', 1); + WorkflowsPage.getters.workflowCard('Workflow 2').should('contain.text', 'Workflow 2'); + mainSidebar.actions.goToSettings(); + cy.go('back'); + + WorkflowsPage.getters.workflowCards().should('have.length', 1); + WorkflowsPage.getters.workflowCard('Workflow 2').should('contain.text', 'Workflow 2'); + WorkflowsPage.getters.workflowResetFilters().click(); + + WorkflowsPage.getters.workflowCards().each(($el) => { + const workflowName = $el.find('[data-test-id="workflow-card-name"]').text(); + + WorkflowsPage.getters.workflowCardActions(workflowName).click(); + WorkflowsPage.getters.workflowDeleteButton().click(); + + cy.get('button').contains('delete').click(); + }); + }); + + it('Should filter by status', () => { + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.newWorkflowButtonCard().click(); + cy.createFixtureWorkflow('Test_workflow_1.json', `Workflow 1`); + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.createWorkflowButton().click(); + cy.createFixtureWorkflow('Test_workflow_3.json', `Workflow 3`); + WorkflowPages.getters.activatorSwitch().click(); + cy.visit(WorkflowsPage.url); + + WorkflowsPage.getters.workflowFilterButton().click(); + WorkflowsPage.getters.workflowStatusDropdown().click(); + WorkflowsPage.getters.workflowStatusItem('Active').click(); + cy.get('body').click(0, 0); + + WorkflowsPage.getters.workflowCards().should('have.length', 1); + WorkflowsPage.getters.workflowCard('Workflow 3').should('contain.text', 'Workflow 3'); + mainSidebar.actions.goToSettings(); + cy.go('back'); + + WorkflowsPage.getters.workflowCards().should('have.length', 1); + WorkflowsPage.getters.workflowCard('Workflow 3').should('contain.text', 'Workflow 3'); + WorkflowsPage.getters.workflowResetFilters().click(); + + WorkflowsPage.getters.workflowCards().each(($el) => { + const workflowName = $el.find('[data-test-id="workflow-card-name"]').text(); + + WorkflowsPage.getters.workflowCardActions(workflowName).click(); + WorkflowsPage.getters.workflowDeleteButton().click(); + + cy.get('button').contains('delete').click(); + }); + }); + + it('Should filter by owned by', () => { + cy.visit(WorkflowsPage.url); + + WorkflowsPage.getters.newWorkflowButtonCard().click(); + cy.createFixtureWorkflow('Test_workflow_1.json', `Workflow 1`); + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.createWorkflowButton().click(); + cy.createFixtureWorkflow('Test_workflow_3.json', `Workflow 3`); + WorkflowPages.getters.activatorSwitch().click(); + cy.visit(WorkflowsPage.url); + + WorkflowsPage.getters.workflowFilterButton().click(); + WorkflowsPage.getters.workflowOwnershipDropdown().realClick(); + WorkflowsPage.getters.workflowOwner(INSTANCE_OWNER.email).click(); + cy.get('body').click(0, 0); + + WorkflowsPage.getters.workflowCards().should('have.length', 2); + mainSidebar.actions.goToSettings(); + cy.go('back'); + + WorkflowsPage.getters.workflowResetFilters().click(); + + WorkflowsPage.getters.workflowCards().each(($el) => { + const workflowName = $el.find('[data-test-id="workflow-card-name"]').text(); + + WorkflowsPage.getters.workflowCardActions(workflowName).click(); + WorkflowsPage.getters.workflowDeleteButton().click(); + + cy.get('button').contains('delete').click(); + }); + }); +}); diff --git a/cypress/e2e/31-demo.cy.ts b/cypress/e2e/31-demo.cy.ts new file mode 100644 index 0000000000000..d9397ace4eece --- /dev/null +++ b/cypress/e2e/31-demo.cy.ts @@ -0,0 +1,23 @@ +import workflow from '../fixtures/Manual_wait_set.json'; +import { importWorkflow, vistDemoPage } from '../pages/demo'; +import { WorkflowPage } from '../pages/workflow'; + +const workflowPage = new WorkflowPage(); + +describe('Demo', () => { + it('can import template', () => { + vistDemoPage(); + importWorkflow(workflow); + workflowPage.getters.canvasNodes().should('have.length', 3); + }); + + it('can override theme to dark', () => { + vistDemoPage('dark'); + cy.get('body').should('have.attr', 'data-theme', 'dark'); + }); + + it('can override theme to light', () => { + vistDemoPage('light'); + cy.get('body').should('have.attr', 'data-theme', 'light'); + }); +}); diff --git a/cypress/e2e/32-node-io-filter.cy.ts b/cypress/e2e/32-node-io-filter.cy.ts new file mode 100644 index 0000000000000..3f1ffdf005243 --- /dev/null +++ b/cypress/e2e/32-node-io-filter.cy.ts @@ -0,0 +1,116 @@ +import { WorkflowPage as WorkflowPageClass, NDV } from '../pages'; + +const workflowPage = new WorkflowPageClass(); +const ndv = new NDV(); + +describe('Node IO Filter', () => { + beforeEach(() => { + workflowPage.actions.visit(); + cy.createFixtureWorkflow('Node_IO_filter.json', `Node IO filter`); + workflowPage.actions.saveWorkflowOnButtonClick(); + workflowPage.actions.executeWorkflow(); + }); + + it('should filter pinned data', () => { + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.actions.close(); + workflowPage.getters.canvasNodes().first().dblclick(); + cy.wait(500); + ndv.getters.outputDataContainer().should('be.visible'); + cy.document().trigger('keyup', { key: '/' }); + + const searchInput = ndv.getters.searchInput(); + + searchInput.filter(':focus').should('exist'); + ndv.getters.pagination().find('li').should('have.length', 3); + ndv.getters.outputDataContainer().find('mark').should('not.exist'); + + searchInput.type('ar'); + ndv.getters.pagination().find('li').should('have.length', 2); + ndv.getters.outputDataContainer().find('mark').its('length').should('be.gt', 0); + + searchInput.type('i'); + ndv.getters.pagination().should('not.exist'); + ndv.getters.outputDataContainer().find('mark').its('length').should('be.gt', 0); + }); + + it('should filter input/output data separately', () => { + workflowPage.getters.canvasNodes().eq(1).dblclick(); + cy.wait(500); + ndv.getters.outputDataContainer().should('be.visible'); + ndv.getters.inputDataContainer().should('be.visible'); + ndv.actions.switchInputMode('Table'); + cy.document().trigger('keyup', { key: '/' }); + + ndv.getters.outputPanel().findChildByTestId('ndv-search').filter(':focus').should('not.exist'); + + let focusedInput = ndv.getters + .inputPanel() + .findChildByTestId('ndv-search') + .filter(':focus') + .should('exist'); + + const getInputPagination = () => + ndv.getters.inputPanel().findChildByTestId('ndv-data-pagination'); + const getInputCounter = () => ndv.getters.inputPanel().findChildByTestId('ndv-items-count'); + const getOuputPagination = () => + ndv.getters.outputPanel().findChildByTestId('ndv-data-pagination'); + const getOutputCounter = () => ndv.getters.outputPanel().findChildByTestId('ndv-items-count'); + + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().contains('21 items').should('exist'); + focusedInput.type('ar'); + + getInputPagination().find('li').should('have.length', 2); + getInputCounter().should('contain', '14 of 21 items'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().should('contain', '21 items'); + focusedInput.type('i'); + + getInputPagination().should('not.exist'); + getInputCounter().should('contain', '8 of 21 items'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().should('contain', '21 items'); + + focusedInput.clear(); + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().contains('21 items').should('exist'); + + ndv.getters.outputDataContainer().trigger('mouseover'); + cy.document().trigger('keyup', { key: '/' }); + ndv.getters.inputPanel().findChildByTestId('ndv-search').filter(':focus').should('not.exist'); + + focusedInput = ndv.getters + .outputPanel() + .findChildByTestId('ndv-search') + .filter(':focus') + .should('exist'); + + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().contains('21 items').should('exist'); + focusedInput.type('ar'); + + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().find('li').should('have.length', 2); + getOutputCounter().should('contain', '14 of 21 items'); + focusedInput.type('i'); + + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().should('not.exist'); + getOutputCounter().should('contain', '8 of 21 items'); + + focusedInput.clear(); + getInputPagination().find('li').should('have.length', 3); + getInputCounter().contains('21 items').should('exist'); + getOuputPagination().find('li').should('have.length', 3); + getOutputCounter().contains('21 items').should('exist'); + }); +}); diff --git a/cypress/e2e/32-worker-view.cy.ts b/cypress/e2e/32-worker-view.cy.ts new file mode 100644 index 0000000000000..ba3edbe4c9a17 --- /dev/null +++ b/cypress/e2e/32-worker-view.cy.ts @@ -0,0 +1,43 @@ +import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants'; +import { WorkerViewPage } from '../pages'; + +const workerViewPage = new WorkerViewPage(); + +describe('Worker View (unlicensed)', () => { + beforeEach(() => { + cy.disableFeature('workerView'); + cy.disableQueueMode(); + }); + + it('should not show up in the menu sidebar', () => { + cy.signin(INSTANCE_MEMBERS[0]); + cy.visit(workerViewPage.url); + workerViewPage.getters.menuItem().should('not.exist'); + }); + + it('should show action box', () => { + cy.signin(INSTANCE_MEMBERS[0]); + cy.visit(workerViewPage.url); + workerViewPage.getters.workerViewUnlicensed().should('exist'); + }); +}); + +describe('Worker View (licensed)', () => { + beforeEach(() => { + cy.enableFeature('workerView'); + cy.enableQueueMode(); + }); + + it('should show up in the menu sidebar', () => { + cy.signin(INSTANCE_OWNER); + cy.enableQueueMode(); + cy.visit(workerViewPage.url); + workerViewPage.getters.menuItem().should('exist'); + }); + + it('should show worker list view', () => { + cy.signin(INSTANCE_MEMBERS[0]); + cy.visit(workerViewPage.url); + workerViewPage.getters.workerViewLicensed().should('exist'); + }); +}); diff --git a/cypress/e2e/33-settings-personal.cy.ts b/cypress/e2e/33-settings-personal.cy.ts new file mode 100644 index 0000000000000..9257bee22d25f --- /dev/null +++ b/cypress/e2e/33-settings-personal.cy.ts @@ -0,0 +1,52 @@ +import { WorkflowPage } from "../pages"; + +const workflowPage = new WorkflowPage(); + +const INVALID_NAMES = [ + 'https://n8n.io', + 'http://n8n.io', + 'www.n8n.io', + 'n8n.io', + 'n8n.бг', + 'n8n.io/home', + 'n8n.io/home?send=true', + 'Jack', + '', +]; + +const VALID_NAMES = [ + ['a', 'a'], + ['alice', 'alice'], + ['Robert', 'Downey Jr.'], + ['Mia', 'Mia-Downey'], + ['Mark', "O'neil"], + ['Thomas', 'Müler'], + ['ßáçøñ', 'ßáçøñ'], + ['أحمد', 'فلسطين'], + ['Милорад', 'Филиповић'], +]; + +describe('Personal Settings', () => { + it ('should allow to change first and last name', () => { + cy.visit('/settings/personal'); + VALID_NAMES.forEach((name) => { + cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name[0]); + cy.getByTestId('personal-data-form').find('input[name="lastName"]').clear().type(name[1]); + cy.getByTestId('save-settings-button').click(); + workflowPage.getters.successToast().should('contain', 'Personal details updated'); + workflowPage.getters.successToast().find('.el-notification__closeBtn').click(); + }); + }); + it('not allow malicious values for personal data', () => { + cy.visit('/settings/personal'); + INVALID_NAMES.forEach((name) => { + cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name); + cy.getByTestId('personal-data-form').find('input[name="lastName"]').clear().type(name); + cy.getByTestId('save-settings-button').click(); + workflowPage.getters + .errorToast() + .should('contain', 'Malicious firstName | Malicious lastName'); + workflowPage.getters.errorToast().find('.el-notification__closeBtn').click(); + }); + }); +}); diff --git a/cypress/e2e/34-template-credentials-setup.cy.ts b/cypress/e2e/34-template-credentials-setup.cy.ts new file mode 100644 index 0000000000000..7de435c4fafaa --- /dev/null +++ b/cypress/e2e/34-template-credentials-setup.cy.ts @@ -0,0 +1,208 @@ +import { + clickUseWorkflowButtonByTitle, + visitTemplateCollectionPage, + testData, +} from '../pages/template-collection'; +import * as templateCredentialsSetupPage from '../pages/template-credential-setup'; +import { WorkflowPage } from '../pages/workflow'; +import * as formStep from '../composables/setup-template-form-step'; +import { getSetupWorkflowCredentialsButton } from '../composables/setup-workflow-credentials-button'; +import * as setupCredsModal from '../composables/modals/workflow-credential-setup-modal'; + +const workflowPage = new WorkflowPage(); + +const testTemplate = templateCredentialsSetupPage.testData.simpleTemplate; + +// NodeView uses beforeunload listener that will show a browser +// native popup, which will block cypress from continuing / exiting. +// This prevent the registration of the listener. +Cypress.on('window:before:load', (win) => { + const origAddEventListener = win.addEventListener; + win.addEventListener = (eventName: string, listener: any, opts: any) => { + if (eventName === 'beforeunload') { + return; + } + + return origAddEventListener.call(win, eventName, listener, opts); + }; +}); + +describe('Template credentials setup', () => { + beforeEach(() => { + cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${testTemplate.id}`, { + fixture: testTemplate.fixture, + }); + cy.intercept('GET', '**/rest/settings', (req) => { + // Disable cache + delete req.headers['if-none-match'] + req.reply((res) => { + if (res.body.data) { + // Disable custom templates host if it has been overridden by another intercept + res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' }; + } + }); + }).as('settingsRequest'); + }); + + it('can be opened from template collection page', () => { + visitTemplateCollectionPage(testData.ecommerceStarterPack); + templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag(); + clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram'); + + templateCredentialsSetupPage.getters + .title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`) + .should('be.visible'); + }); + + it('has all the elements on page', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + + templateCredentialsSetupPage.getters + .title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`) + .should('be.visible'); + + templateCredentialsSetupPage.getters + .infoCallout() + .should( + 'contain.text', + 'You need 1x Shopify, 1x X (Formerly Twitter) and 1x Telegram account to setup this template', + ); + + const expectedAppNames = ['1. Shopify', '2. X (Formerly Twitter)', '3. Telegram']; + const expectedAppDescriptions = [ + 'The credential you select will be used in the product created node of the workflow template.', + 'The credential you select will be used in the Twitter node of the workflow template.', + 'The credential you select will be used in the Telegram node of the workflow template.', + ]; + + formStep.getFormStep().each(($el, index) => { + formStep.getStepHeading($el).should('have.text', expectedAppNames[index]); + formStep.getStepDescription($el).should('have.text', expectedAppDescriptions[index]); + }); + }); + + it('can skip template creation', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + + templateCredentialsSetupPage.getters.skipLink().click(); + workflowPage.getters.canvasNodes().should('have.length', 3); + }); + + it('can create credentials and workflow from the template', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + + // Continue button should be disabled if no credentials are created + templateCredentialsSetupPage.getters.continueButton().should('be.disabled'); + + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify'); + + // Continue button should be enabled if at least one has been created + templateCredentialsSetupPage.getters.continueButton().should('be.enabled'); + + templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)'); + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram'); + + templateCredentialsSetupPage.finishCredentialSetup(); + + workflowPage.getters.canvasNodes().should('have.length', 3); + + // Focus the canvas so the copy to clipboard works + workflowPage.getters.canvasNodes().eq(0).realClick(); + workflowPage.actions.selectAll(); + workflowPage.actions.hitCopy(); + + cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite'); + // Check workflow JSON by copying it to clipboard + cy.readClipboard().then((workflowJSON) => { + const workflow = JSON.parse(workflowJSON); + + expect(workflow.meta).to.haveOwnProperty('templateId', testTemplate.id.toString()); + workflow.nodes.forEach((node: any) => { + expect(Object.keys(node.credentials ?? {})).to.have.lengthOf(1); + }); + }); + }); + + it('should work with a template that has no credentials (ADO-1603)', () => { + const templateWithoutCreds = templateCredentialsSetupPage.testData.templateWithoutCredentials; + cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${templateWithoutCreds.id}`, { + fixture: templateWithoutCreds.fixture, + }); + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(templateWithoutCreds.id); + + const expectedAppNames = ['1. Email (IMAP)', '2. Nextcloud']; + const expectedAppDescriptions = [ + 'The credential you select will be used in the IMAP Email node of the workflow template.', + 'The credential you select will be used in the Nextcloud node of the workflow template.', + ]; + + formStep.getFormStep().each(($el, index) => { + formStep.getStepHeading($el).should('have.text', expectedAppNames[index]); + formStep.getStepDescription($el).should('have.text', expectedAppDescriptions[index]); + }); + + templateCredentialsSetupPage.getters.continueButton().should('be.disabled'); + + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Email (IMAP)'); + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Nextcloud'); + + templateCredentialsSetupPage.finishCredentialSetup(); + + workflowPage.getters.canvasNodes().should('have.length', 3); + }); + + describe('Credential setup from workflow editor', () => { + beforeEach(() => { + cy.resetDatabase(); + cy.signinAsOwner(); + }); + + it('should allow credential setup from workflow editor if user skips it during template setup', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + templateCredentialsSetupPage.getters.skipLink().click(); + + getSetupWorkflowCredentialsButton().should('be.visible'); + }); + + it('should allow credential setup from workflow editor if user fills in credentials partially during template setup', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify'); + + templateCredentialsSetupPage.finishCredentialSetup(); + + getSetupWorkflowCredentialsButton().should('be.visible'); + }); + + it('should fill credentials from workflow editor', () => { + templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); + templateCredentialsSetupPage.getters.skipLink().click(); + + getSetupWorkflowCredentialsButton().click(); + setupCredsModal.getWorkflowCredentialsModal().should('be.visible'); + + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify'); + templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)'); + templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram'); + + setupCredsModal.closeModalFromContinueButton(); + setupCredsModal.getWorkflowCredentialsModal().should('not.exist'); + + // Focus the canvas so the copy to clipboard works + workflowPage.getters.canvasNodes().eq(0).realClick(); + workflowPage.actions.selectAll(); + workflowPage.actions.hitCopy(); + + cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite'); + // Check workflow JSON by copying it to clipboard + cy.readClipboard().then((workflowJSON) => { + const workflow = JSON.parse(workflowJSON); + + workflow.nodes.forEach((node: any) => { + expect(Object.keys(node.credentials ?? {})).to.have.lengthOf(1); + }); + }); + + getSetupWorkflowCredentialsButton().should('not.exist'); + }); + }); +}); diff --git a/cypress/e2e/35-admin-user-smoke-test.cy.ts b/cypress/e2e/35-admin-user-smoke-test.cy.ts new file mode 100644 index 0000000000000..05e70aa339ab4 --- /dev/null +++ b/cypress/e2e/35-admin-user-smoke-test.cy.ts @@ -0,0 +1,23 @@ +import { INSTANCE_ADMIN, INSTANCE_OWNER } from '../constants'; +import { SettingsPage } from '../pages/settings'; + +const settingsPage = new SettingsPage(); + +describe('Admin user', { disableAutoLogin: true }, () => { + it('should see same Settings sub menu items as instance owner', () => { + cy.signin(INSTANCE_OWNER); + cy.visit(settingsPage.url); + + let ownerMenuItems = 0; + + settingsPage.getters.menuItems().then(($el) => { + ownerMenuItems = $el.length; + }); + + cy.signout(); + cy.signin(INSTANCE_ADMIN); + cy.visit(settingsPage.url); + + settingsPage.getters.menuItems().should('have.length', ownerMenuItems); + }); +}); diff --git a/cypress/e2e/36-suggested-templates.cy.ts b/cypress/e2e/36-suggested-templates.cy.ts new file mode 100644 index 0000000000000..b788796e45b01 --- /dev/null +++ b/cypress/e2e/36-suggested-templates.cy.ts @@ -0,0 +1,143 @@ +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; + +type SuggestedTemplatesStub = { + sections: SuggestedTemplatesSectionStub[]; +} + +type SuggestedTemplatesSectionStub = { + name: string; + title: string; + description: string; + workflows: Array; +}; + +const WorkflowsListPage = new WorkflowsPageClass(); +const WorkflowPage = new WorkflowPageClass(); + +let fixtureSections: SuggestedTemplatesStub = { sections: [] };; + +describe('Suggested templates - Should render', () => { + + before(() => { + cy.fixture('Suggested_Templates.json').then((data) => { + fixtureSections = data; + }); + }); + + beforeEach(() => { + localStorage.removeItem('SHOW_N8N_SUGGESTED_TEMPLATES'); + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }).as('loadSettings'); + cy.intercept('GET', '/rest/cloud/proxy/templates', { + fixture: 'Suggested_Templates.json', + }); + cy.visit(WorkflowsListPage.url); + cy.wait('@loadSettings'); + }); + + it('should render suggested templates page in empty workflow list', () => { + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('exist'); + WorkflowsListPage.getters.suggestedTemplatesCards().should('have.length', fixtureSections.sections[0].workflows.length); + WorkflowsListPage.getters.suggestedTemplatesSectionDescription().should('contain', fixtureSections.sections[0].description); + }); + + it('should render suggested templates when there are workflows in the list', () => { + WorkflowsListPage.getters.suggestedTemplatesNewWorkflowButton().click(); + cy.createFixtureWorkflow('Test_workflow_1.json', 'Test workflow'); + cy.visit(WorkflowsListPage.url); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('exist'); + cy.contains(`Explore ${fixtureSections.sections[0].name.toLocaleLowerCase()} workflow templates`).should('exist'); + WorkflowsListPage.getters.suggestedTemplatesCards().should('have.length', fixtureSections.sections[0].workflows.length); + }); + + it('should enable users to signup for suggested templates templates', () => { + // Test the whole flow + WorkflowsListPage.getters.suggestedTemplatesCards().first().click(); + WorkflowsListPage.getters.suggestedTemplatesPreviewModal().should('exist'); + WorkflowsListPage.getters.suggestedTemplatesUseTemplateButton().click(); + cy.url().should('include', '/workflow/new'); + WorkflowPage.getters.infoToast().should('contain', 'Template coming soon!'); + WorkflowPage.getters.infoToast().contains('Notify me when it\'s available').click(); + WorkflowPage.getters.successToast().should('contain', 'We will contact you via email once this template is released.'); + cy.visit(WorkflowsListPage.url); + // Once users have signed up for a template, suggestions should not be shown again + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + +}); + +describe('Suggested templates - Should not render', () => { + beforeEach(() => { + localStorage.removeItem('SHOW_N8N_SUGGESTED_TEMPLATES'); + cy.visit(WorkflowsListPage.url); + }); + + it('should not render suggested templates templates if not in cloud deployment', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'notCloud' } }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint throws error', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', { statusCode: 500 }).as('loadTemplates'); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint returns empty list', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', (req) => { + req.on('response', (res) => { + res.send({ + data: { collections: [] }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint returns invalid response', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', (req) => { + req.on('response', (res) => { + res.send({ + data: { somethingElse: [] }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); +}); diff --git a/cypress/e2e/36-versions.cy.ts b/cypress/e2e/36-versions.cy.ts new file mode 100644 index 0000000000000..2d93223ebb81f --- /dev/null +++ b/cypress/e2e/36-versions.cy.ts @@ -0,0 +1,66 @@ +import { INSTANCE_OWNER } from '../constants'; +import { WorkflowsPage } from '../pages/workflows'; +import { + closeVersionUpdatesPanel, + getVersionCard, + getVersionUpdatesPanelOpenButton, + openVersionUpdatesPanel, +} from '../composables/versions'; + +const workflowsPage = new WorkflowsPage(); + +describe('Versions', () => { + it('should open updates panel', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.continue((res) => { + if (res.body.hasOwnProperty('data')) { + res.body.data = { + ...res.body.data, + releaseChannel: 'stable', + versionCli: '1.0.0', + versionNotifications: { + enabled: true, + endpoint: 'https://api.n8n.io/api/versions/', + infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html', + }, + }; + } + }); + }).as('settings'); + + cy.intercept('GET', 'https://api.n8n.io/api/versions/1.0.0', [ + { + name: '1.3.1', + createdAt: '2023-08-18T11:53:12.857Z', + hasSecurityIssue: null, + hasSecurityFix: null, + securityIssueFixVersion: null, + hasBreakingChange: null, + documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131', + nodes: [], + description: 'Includes bug fixes', + }, + { + name: '1.0.5', + createdAt: '2023-07-24T10:54:56.097Z', + hasSecurityIssue: false, + hasSecurityFix: null, + securityIssueFixVersion: null, + hasBreakingChange: true, + documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104', + nodes: [], + description: 'Includes core functionality and bug fixes', + }, + ]); + + cy.signin(INSTANCE_OWNER); + + cy.visit(workflowsPage.url); + cy.wait('@settings'); + + getVersionUpdatesPanelOpenButton().should('contain', '2 updates'); + openVersionUpdatesPanel(); + getVersionCard().should('have.length', 2); + closeVersionUpdatesPanel(); + }); +}); diff --git a/cypress/e2e/37-become-creator-cta.cy.ts b/cypress/e2e/37-become-creator-cta.cy.ts new file mode 100644 index 0000000000000..931208e5f35d5 --- /dev/null +++ b/cypress/e2e/37-become-creator-cta.cy.ts @@ -0,0 +1,32 @@ +import { + getBecomeTemplateCreatorCta, + getCloseBecomeTemplateCreatorCtaButton, + interceptCtaRequestWithResponse, +} from '../composables/becomeTemplateCreatorCta'; +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; + +const WorkflowsPage = new WorkflowsPageClass(); + +describe('Become creator CTA', () => { + it('should not show the CTA if user is not eligible', () => { + interceptCtaRequestWithResponse(false).as('cta'); + cy.visit(WorkflowsPage.url); + + cy.wait('@cta'); + + getBecomeTemplateCreatorCta().should('not.exist'); + }); + + it('should show the CTA if the user is eligible', () => { + interceptCtaRequestWithResponse(true).as('cta'); + cy.visit(WorkflowsPage.url); + + cy.wait('@cta'); + + getBecomeTemplateCreatorCta().should('be.visible'); + + getCloseBecomeTemplateCreatorCtaButton().click(); + + getBecomeTemplateCreatorCta().should('not.exist'); + }); +}); diff --git a/cypress/e2e/38-custom-template-repository.cy.ts b/cypress/e2e/38-custom-template-repository.cy.ts new file mode 100644 index 0000000000000..067bf19b15cc5 --- /dev/null +++ b/cypress/e2e/38-custom-template-repository.cy.ts @@ -0,0 +1,147 @@ +import { TemplatesPage } from '../pages/templates'; +import { WorkflowPage } from '../pages/workflow'; +import { TemplateWorkflowPage } from '../pages/template-workflow'; +import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json'; +import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json'; + +const templatesPage = new TemplatesPage(); +const workflowPage = new WorkflowPage(); +const templateWorkflowPage = new TemplateWorkflowPage(); + + +describe.skip('In-app templates repository', () => { + beforeEach(() => { + cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=&search=', { fixture: 'templates_search/all_templates_search_response.json' }).as('searchRequest'); + cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=Sales*', { fixture: 'templates_search/sales_templates_search_response.json' }).as('categorySearchRequest'); + cy.intercept('GET', '**/api/templates/workflows/*', { fixture: 'templates_search/test_template_preview.json' }).as('singleTemplateRequest'); + cy.intercept('GET', '**/api/workflows/templates/*', { fixture: 'templates_search/test_template_import.json' }).as('singleTemplateRequest'); + cy.intercept('GET', '**/rest/settings', (req) => { + // Disable cache + delete req.headers['if-none-match'] + req.reply((res) => { + if (res.body.data) { + // Enable in-app templates by setting a custom host + res.body.data.templates = { enabled: true, host: 'https://api-staging.n8n.io/api/' }; + } + }); + }).as('settingsRequest'); + }); + + it('can open onboarding flow', () => { + templatesPage.actions.openOnboardingFlow(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io'); + cy.url().then(($url) => { + expect($url).to.match(/.*\/workflow\/.*?onboardingId=1$/); + }) + + workflowPage.actions.shouldHaveWorkflowName(`Demo: ${name}`); + + workflowPage.getters.canvasNodes().should('have.length', 4); + workflowPage.getters.stickies().should('have.length', 1); + workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon'); + }); + + it('can import template', () => { + templatesPage.actions.importTemplate(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io'); + + cy.url().then(($url) => { + expect($url).to.include('/workflow/new?templateId=1'); + }); + + workflowPage.getters.canvasNodes().should('have.length', 4); + workflowPage.getters.stickies().should('have.length', 1); + workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name); + }); + + it('should save template id with the workflow', () => { + cy.visit(templatesPage.url); + cy.get('.el-skeleton.n8n-loading').should('not.exist'); + templatesPage.getters.firstTemplateCard().should('exist'); + templatesPage.getters.templatesLoadingContainer().should('not.exist'); + templatesPage.getters.firstTemplateCard().click(); + cy.url().should('include', '/templates/'); + + cy.url().then(($url) => { + const templateId = $url.split('/').pop(); + + templatesPage.getters.useTemplateButton().click(); + cy.url().should('include', '/workflow/new'); + workflowPage.actions.saveWorkflowOnButtonClick(); + + workflowPage.actions.selectAll(); + workflowPage.actions.hitCopy(); + + cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite'); + // Check workflow JSON by copying it to clipboard + cy.readClipboard().then((workflowJSON) => { + expect(workflowJSON).to.contain(`"templateId": "${templateId}"`); + }); + }); + }); + + it('can open template with images and hides workflow screenshots', () => { + templateWorkflowPage.actions.openTemplate(WorkflowTemplate, 'https://api-staging.n8n.io'); + + templateWorkflowPage.getters.description().find('img').should('have.length', 1); + }); + + + it('renders search elements correctly', () => { + cy.visit(templatesPage.url); + templatesPage.getters.searchInput().should('exist'); + templatesPage.getters.allCategoriesFilter().should('exist'); + templatesPage.getters.categoryFilters().should('have.length.greaterThan', 1); + templatesPage.getters.templateCards().should('have.length.greaterThan', 0); + }); + + it('can filter templates by category', () => { + cy.visit(templatesPage.url); + templatesPage.getters.templatesLoadingContainer().should('not.exist'); + templatesPage.getters.categoryFilter('sales').should('exist'); + let initialTemplateCount = 0; + let initialCollectionCount = 0; + + templatesPage.getters.templateCountLabel().then(($el) => { + initialTemplateCount = parseInt($el.text().replace(/\D/g, ''), 10); + templatesPage.getters.collectionCountLabel().then(($el) => { + initialCollectionCount = parseInt($el.text().replace(/\D/g, ''), 10); + + templatesPage.getters.categoryFilter('sales').click(); + templatesPage.getters.templatesLoadingContainer().should('not.exist'); + + // Should have less templates and collections after selecting a category + templatesPage.getters.templateCountLabel().should(($el) => { + expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialTemplateCount); + }); + templatesPage.getters.collectionCountLabel().should(($el) => { + expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialCollectionCount); + }); + }); + }); + }); + + it('should preserve search query in URL', () => { + cy.visit(templatesPage.url); + templatesPage.getters.templatesLoadingContainer().should('not.exist'); + templatesPage.getters.categoryFilter('sales').should('exist'); + templatesPage.getters.categoryFilter('sales').click(); + templatesPage.getters.searchInput().type('auto'); + + cy.url().should('include', '?categories='); + cy.url().should('include', '&search='); + + cy.reload(); + + // Should preserve search query in URL + cy.url().should('include', '?categories='); + cy.url().should('include', '&search='); + + // Sales category should still be selected + templatesPage.getters.categoryFilter('sales').find('label').should('have.class', 'is-checked'); + // Search input should still have the search query + templatesPage.getters.searchInput().should('have.value', 'auto'); + // Sales checkbox should be pushed to the top + templatesPage.getters.categoryFilters().eq(1).then(($el) => { + expect($el.text()).to.equal('Sales'); + }); + }); +}); diff --git a/cypress/e2e/39-import-workflow.cy.ts b/cypress/e2e/39-import-workflow.cy.ts new file mode 100644 index 0000000000000..831228fba35d9 --- /dev/null +++ b/cypress/e2e/39-import-workflow.cy.ts @@ -0,0 +1,74 @@ +import { WorkflowPage } from '../pages'; +import { MessageBox as MessageBoxClass } from '../pages/modals/message-box'; + +const workflowPage = new WorkflowPage(); +const messageBox = new MessageBoxClass(); + +before(() => { + cy.fixture('Onboarding_workflow.json').then((data) => { + cy.intercept('GET', '/rest/workflows/from-url*', { + body: { data }, + }).as('downloadWorkflowFromURL'); + }); +}); + +describe('Import workflow', () => { + describe('From URL', () => { + it('should import workflow', () => { + workflowPage.actions.visit(true); + workflowPage.getters.workflowMenu().click(); + workflowPage.getters.workflowMenuItemImportFromURLItem().click(); + + messageBox.getters.modal().should('be.visible'); + + messageBox.getters.content().type('https://fakepage.com/workflow.json'); + + messageBox.getters.confirm().click(); + + workflowPage.actions.zoomToFit(); + + workflowPage.getters.canvasNodes().should('have.length', 4); + + workflowPage.getters.errorToast().should('not.exist'); + + workflowPage.getters.successToast().should('not.exist'); + }); + + it('clicking outside modal should not show error toast', () => { + workflowPage.actions.visit(true); + + workflowPage.getters.workflowMenu().click(); + workflowPage.getters.workflowMenuItemImportFromURLItem().click(); + + cy.get('body').click(0, 0); + + workflowPage.getters.errorToast().should('not.exist'); + }); + + it('canceling modal should not show error toast', () => { + workflowPage.actions.visit(true); + + workflowPage.getters.workflowMenu().click(); + workflowPage.getters.workflowMenuItemImportFromURLItem().click(); + messageBox.getters.cancel().click(); + + workflowPage.getters.errorToast().should('not.exist'); + }); + }); + + describe('From File', () => { + it('should import workflow', () => { + workflowPage.actions.visit(true); + + workflowPage.getters.workflowMenu().click(); + workflowPage.getters.workflowMenuItemImportFromFile().click(); + workflowPage.getters + .workflowImportInput() + .selectFile('cypress/fixtures/Test_workflow-actions_paste-data.json', { force: true }); + cy.waitForLoad(false); + workflowPage.actions.zoomToFit(); + workflowPage.getters.canvasNodes().should('have.length', 5); + workflowPage.getters.nodeConnections().should('have.length', 5); + }); + }); +}); diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts index 0057afd93c839..6955c95463550 100644 --- a/cypress/e2e/4-node-creator.cy.ts +++ b/cypress/e2e/4-node-creator.cy.ts @@ -1,18 +1,14 @@ import { NodeCreator } from '../pages/features/node-creator'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; import { NDV } from '../pages/ndv'; +import { getVisibleSelect } from '../utils'; +import { IF_NODE_NAME } from '../constants'; const nodeCreatorFeature = new NodeCreator(); const WorkflowPage = new WorkflowPageClass(); const NDVModal = new NDV(); - describe('Node Creator', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); }); @@ -20,7 +16,10 @@ describe('Node Creator', () => { it('should open node creator on trigger tab if no trigger is on canvas', () => { nodeCreatorFeature.getters.canvasAddButton().click(); - nodeCreatorFeature.getters.nodeCreator().contains('Select a trigger').should('be.visible'); + nodeCreatorFeature.getters + .nodeCreator() + .contains('What triggers this workflow?') + .should('be.visible'); }); it('should navigate subcategory', () => { @@ -36,7 +35,7 @@ describe('Node Creator', () => { nodeCreatorFeature.actions.openNodeCreator(); nodeCreatorFeature.getters.searchBar().find('input').type('manual'); - nodeCreatorFeature.getters.creatorItem().should('have.length', 1); + nodeCreatorFeature.getters.creatorItem().should('have.length', 2); nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123'); nodeCreatorFeature.getters.creatorItem().should('have.length', 0); nodeCreatorFeature.getters @@ -58,8 +57,8 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCreatorItem('On app event').click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image'); - nodeCreatorFeature.getters.getCreatorItem('Results in other categories (1)').should('exist'); - nodeCreatorFeature.getters.creatorItem().should('have.length', 2); + nodeCreatorFeature.getters.getCategoryItem('Results in other categories').should('exist'); + nodeCreatorFeature.getters.creatorItem().should('have.length', 1); nodeCreatorFeature.getters.getCreatorItem('Edit Image').should('exist'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image123123'); nodeCreatorFeature.getters.creatorItem().should('have.length', 0); @@ -78,7 +77,10 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.nodeCreator().contains('What happens next?').should('be.visible'); nodeCreatorFeature.getters.getCreatorItem('Add another trigger').click(); - nodeCreatorFeature.getters.nodeCreator().contains('Select a trigger').should('be.visible'); + nodeCreatorFeature.getters + .nodeCreator() + .contains('What triggers this workflow?') + .should('be.visible'); nodeCreatorFeature.getters.activeSubcategory().find('button').should('exist'); nodeCreatorFeature.getters.activeSubcategory().find('button').click(); nodeCreatorFeature.getters.nodeCreator().contains('What happens next?').should('be.visible'); @@ -91,7 +93,7 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCreatorItem(editImageNode).click(); nodeCreatorFeature.getters.activeSubcategory().should('have.text', editImageNode); nodeCreatorFeature.getters.getCreatorItem('Crop Image').click(); - NDVModal.getters.parameterInput('operation').should('contain.text', 'Crop'); + NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Crop'); }); it('should search through actions and confirm added action', () => { @@ -100,15 +102,15 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.searchBar().find('input').type('{rightarrow}'); nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'FTP'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('file'); - // Navigate to rename action which should be the 4th item - nodeCreatorFeature.getters.searchBar().find('input').type('{downarrow} {downarrow} {downarrow} {rightarrow}'); - NDVModal.getters.parameterInput('operation').should('contain.text', 'Rename'); - }) + // The 1st trigger is selected, up 1x to the collapsable header, up 2x to the last action (rename) + nodeCreatorFeature.getters.searchBar().find('input').type('{uparrow}{uparrow}{rightarrow}'); + NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Rename'); + }); it('should not show actions for single action nodes', () => { const singleActionNodes = [ 'DHL', - 'iCalendar', + 'Edit Fields', 'LingvaNex', 'Mailcheck', 'MSG91', @@ -116,19 +118,157 @@ describe('Node Creator', () => { 'Spontit', 'Vonage', 'Send Email', - 'Toggl Trigger' - ] - const doubleActionNode = 'OpenWeatherMap' + 'Toggl Trigger', + ]; + const doubleActionNode = 'OpenWeatherMap'; nodeCreatorFeature.actions.openNodeCreator(); singleActionNodes.forEach((node) => { nodeCreatorFeature.getters.searchBar().find('input').clear().type(node); - nodeCreatorFeature.getters.getCreatorItem(node).find('button[class*="panelIcon"]').should('not.exist'); - }) + nodeCreatorFeature.getters + .getCreatorItem(node) + .find('button[class*="panelIcon"]') + .should('not.exist'); + }); nodeCreatorFeature.getters.searchBar().find('input').clear().type(doubleActionNode); nodeCreatorFeature.getters.getCreatorItem(doubleActionNode).click(); - nodeCreatorFeature.getters.creatorItem().should('have.length', 2); - }) + nodeCreatorFeature.getters.creatorItem().should('have.length', 4); + }); + + it('should have "Actions" section collapsed when opening actions view from Trigger root view', () => { + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('ActiveCampaign'); + nodeCreatorFeature.getters.getCreatorItem('ActiveCampaign').click(); + nodeCreatorFeature.getters.getCategoryItem('Actions').should('exist'); + nodeCreatorFeature.getters.getCategoryItem('Triggers').should('exist'); + + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + }); + + it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => { + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters.getCreatorItem('Manually').click(); + + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); + nodeCreatorFeature.getters.getCreatorItem('n8n').click(); + + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters.getCategoryItem('Triggers').click(); + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + }); + + it('should show callout and two suggested nodes if node has no trigger actions', () => { + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); + + cy.getByTestId('actions-panel-no-triggers-callout').should('be.visible'); + nodeCreatorFeature.getters.getCreatorItem('On a Schedule').should('be.visible'); + nodeCreatorFeature.getters.getCreatorItem('On a Webhook call').should('be.visible'); + }); + + it('should show intro callout if user has not made a production execution', () => { + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); + + cy.getByTestId('actions-panel-activation-callout').should('be.visible'); + nodeCreatorFeature.getters.activeSubcategory().find('button').click(); + nodeCreatorFeature.getters.searchBar().find('input').clear(); + + nodeCreatorFeature.getters.getCreatorItem('On a schedule').click(); + + // Setup 1s interval execution + cy.getByTestId('parameter-input-field').click(); + getVisibleSelect().find('.option-headline').contains('Seconds').click(); + cy.getByTestId('parameter-input-secondsInterval').clear().type('1'); + + NDVModal.actions.close(); + + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); + nodeCreatorFeature.getters.getCreatorItem('Get All People').click(); + NDVModal.actions.close(); + + WorkflowPage.actions.saveWorkflowOnButtonClick(); + WorkflowPage.actions.activateWorkflow(); + WorkflowPage.getters.activatorSwitch().should('have.class', 'is-checked'); + + // Wait for schedule 1s execution to mark user as having made a production execution + cy.wait(1500); + cy.reload(); + + // Action callout should not be visible after user has made a production execution + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); + + cy.getByTestId('actions-panel-activation-callout').should('not.exist'); + }); + + it('should show Trigger and Actions sections during search', () => { + nodeCreatorFeature.actions.openNodeCreator(); + + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('Non existent action name'); + + nodeCreatorFeature.getters.getCategoryItem('Triggers').should('be.visible'); + nodeCreatorFeature.getters.getCategoryItem('Actions').should('be.visible'); + cy.getByTestId('actions-panel-no-triggers-callout').should('be.visible'); + nodeCreatorFeature.getters.getCreatorItem('On a Schedule').should('be.visible'); + nodeCreatorFeature.getters.getCreatorItem('On a Webhook call').should('be.visible'); + }); describe('should correctly append manual trigger for regular actions', () => { // For these sources, manual node should be added @@ -136,7 +276,8 @@ describe('Node Creator', () => { { name: 'canvas add button', handler: () => nodeCreatorFeature.getters.canvasAddButton().click(), - }, { + }, + { name: 'plus button', handler: () => nodeCreatorFeature.getters.plusButton().click(), }, @@ -146,34 +287,221 @@ describe('Node Creator', () => { // name: 'tab key', // handler: () => cy.realPress('Tab'), // }, - ] + ]; sourcesWithAppend.forEach((source) => { it(`should append manual trigger when source is ${source.name}`, () => { - source.handler() + source.handler(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); nodeCreatorFeature.getters.getCreatorItem('Create a credential').click(); NDVModal.actions.close(); WorkflowPage.getters.canvasNodes().should('have.length', 2); }); }); + // @TODO FIX ADDING 2 NODES IN ONE GO it('should not append manual trigger when source is canvas related', () => { nodeCreatorFeature.getters.canvasAddButton().click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); nodeCreatorFeature.getters.getCreatorItem('Create a credential').click(); NDVModal.actions.close(); - WorkflowPage.actions.deleteNode('When clicking "Execute Workflow"') - WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click() + WorkflowPage.actions.deleteNode('When clicking "Test workflow"'); + WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); nodeCreatorFeature.getters.getCreatorItem('Create a credential').click(); 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', 'Summarize'); WorkflowPage.getters.canvasNodes().should('have.length', 3); - }) + }); + }); + + it('should correctly append a No Op node when Loop Over Items node is added (from add button)', () => { + nodeCreatorFeature.actions.openNodeCreator(); + + nodeCreatorFeature.getters.searchBar().find('input').type('Loop Over Items'); + nodeCreatorFeature.getters.getCreatorItem('Loop Over Items').click(); + NDVModal.actions.close(); + + WorkflowPage.getters.canvasNodes().should('have.length', 3); + WorkflowPage.getters.nodeConnections().should('have.length', 3); + + WorkflowPage.getters.getConnectionBetweenNodes('Loop Over Items', 'Replace Me').should('exist'); + WorkflowPage.getters.getConnectionBetweenNodes('Replace Me', 'Loop Over Items').should('exist'); + }); + + it('should correctly append a No Op node when Loop Over Items node is added (from connection)', () => { + WorkflowPage.actions.addNodeToCanvas('Manual'); + cy.get('.plus-endpoint').should('be.visible').click(); + + nodeCreatorFeature.getters.searchBar().find('input').type('Loop Over Items'); + nodeCreatorFeature.getters.getCreatorItem('Loop Over Items').click(); + NDVModal.actions.close(); + + WorkflowPage.getters.canvasNodes().should('have.length', 3); + WorkflowPage.getters.nodeConnections().should('have.length', 3); + + WorkflowPage.getters.getConnectionBetweenNodes('Loop Over Items', 'Replace Me').should('exist'); + WorkflowPage.getters.getConnectionBetweenNodes('Replace Me', 'Loop Over Items').should('exist'); + }); + + it('should have most relevenat nodes on top when searching', () => { + nodeCreatorFeature.getters.canvasAddButton().click(); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('email'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Email Trigger (IMAP)'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('Set'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('i'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw'); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('Edit F'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('i'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('IF'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Switch'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('swit'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Switch'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('red'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Redis'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Reddit'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('redd'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Reddit'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('wh'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webhook'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('web'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webflow'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('webh'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webhook'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('func'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('cod'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Coda'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Code'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('code'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('js'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Edit Fields (Set)'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('fi'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Filter'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('filt'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Filter'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('manu'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Manual Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('sse'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'SSE Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('cmpar'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Compare Datasets'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('fb'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Facebook Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('crn'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('cron'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('sch'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('time'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Schedule Trigger'); + nodeCreatorFeature.getters.nodeItemName().eq(2).should('have.text', 'Wait'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('mail'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Mailgun'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('mailc'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Mailcheck'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Mailchimp'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('api'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('s3'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'S3'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('no op'); + nodeCreatorFeature.getters + .nodeItemName() + .first() + .should('have.text', 'No Operation, do nothing'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('do no'); + nodeCreatorFeature.getters + .nodeItemName() + .first() + .should('have.text', 'No Operation, do nothing'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('htt'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('http'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('wa'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('wait'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('spreadsheet'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Convert to File'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Extract from File'); + nodeCreatorFeature.getters.nodeItemName().eq(2).should('have.text', 'Google Sheets'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('sheets'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Google Sheets'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('ggle she'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Google Sheets'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('hub'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HubSpot'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('git'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Git'); + nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'GitHub'); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('gith'); + nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'GitHub'); }); }); diff --git a/cypress/e2e/40-manual-partial-execution.cy.ts b/cypress/e2e/40-manual-partial-execution.cy.ts new file mode 100644 index 0000000000000..5fe31b56ad137 --- /dev/null +++ b/cypress/e2e/40-manual-partial-execution.cy.ts @@ -0,0 +1,28 @@ +import { NDV, WorkflowPage } from '../pages'; + +const canvas = new WorkflowPage(); +const ndv = new NDV(); + +describe('Manual partial execution', () => { + it('should execute parent nodes with no run data only once', () => { + canvas.actions.visit(); + + cy.fixture('manual-partial-execution.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + + canvas.actions.zoomToFit(); + + canvas.actions.openNode('Edit Fields'); + + cy.get('button').contains('Test step').click(); // create run data + cy.get('button').contains('Test step').click(); // use run data + + ndv.actions.close(); + + canvas.actions.openNode('Webhook1'); + + ndv.getters.nodeRunSuccessIndicator().should('exist'); + ndv.getters.outputRunSelector().should('not.exist'); // single run + }); +}); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index 749fb4434e506..6103dbbc039f6 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -1,15 +1,15 @@ -import { WorkflowPage, NDV } from '../pages'; import { v4 as uuid } from 'uuid'; +import { getVisibleSelect } from '../utils'; +import { MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../constants'; +import { NDV, WorkflowPage } from '../pages'; +import { NodeCreator } from '../pages/features/node-creator'; +import { clickCreateNewCredential } from '../composables/ndv'; +import { setCredentialValues } from '../composables/modals/credential-modal'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); describe('NDV', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - - }); beforeEach(() => { workflowPage.actions.visit(); workflowPage.actions.renameWorkflow(uuid()); @@ -45,14 +45,37 @@ describe('NDV', () => { ndv.getters.outputDisplayMode().should('have.length.at.least', 1).and('be.visible'); }); - it('should change input', () => { + it('should change input and go back to canvas', () => { cy.createFixtureWorkflow('NDV-test-select-input.json', `NDV test select input ${uuid()}`); workflowPage.actions.zoomToFit(); workflowPage.getters.canvasNodes().last().dblclick(); ndv.getters.inputSelect().click(); ndv.getters.inputOption().last().click(); - ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist') + ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist'); ndv.getters.inputDataContainer().should('contain', 'start'); + ndv.getters.backToCanvas().click(); + ndv.getters.container().should('not.be.visible'); + cy.shouldNotHaveConsoleErrors(); + }); + + it('should disconect Switch outputs if rules order was changed', () => { + cy.createFixtureWorkflow('NDV-test-switch_reorder.json', `NDV test switch reorder`); + workflowPage.actions.zoomToFit(); + + workflowPage.actions.executeWorkflow(); + workflowPage.actions.openNode('Merge'); + ndv.getters.outputPanel().contains('2 items').should('exist'); + cy.contains('span', 'first').should('exist'); + ndv.getters.backToCanvas().click(); + + workflowPage.actions.openNode('Switch'); + cy.get('.cm-line').realMouseMove(100, 100); + cy.get('.fa-angle-down').click(); + ndv.getters.backToCanvas().click(); + workflowPage.actions.executeWorkflow(); + workflowPage.actions.openNode('Merge'); + ndv.getters.outputPanel().contains('1 item').should('exist'); + cy.contains('span', 'zero').should('exist'); }); it('should show correct validation state for resource locator params', () => { @@ -69,15 +92,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); ndv.getters.parameterInput('table').find('input').eq(1).focus().blur(); - ndv.getters.parameterInput('application').find('input').eq(1).focus().blur(); + ndv.getters.parameterInput('base').find('input').eq(1).focus().blur(); cy.get('.has-issues').should('have.length', 2); 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); }); @@ -97,10 +120,20 @@ describe('NDV', () => { ndv.getters.container().should('be.visible'); workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); workflowPage.getters.isWorkflowSaved(); - }) + }); describe('test output schema view', () => { - const schemaKeys = ['id', 'name', 'email', 'notes', 'country', 'created', 'objectValue', 'prop1', 'prop2']; + const schemaKeys = [ + 'id', + 'name', + 'email', + 'notes', + 'country', + 'created', + 'objectValue', + 'prop1', + 'prop2', + ]; function setupSchemaWorkflow() { cy.createFixtureWorkflow('Test_workflow_schema_test.json', `NDV test schema view ${uuid()}`); workflowPage.actions.zoomToFit(); @@ -109,60 +142,87 @@ describe('NDV', () => { } it('should switch to output schema view and validate it', () => { - setupSchemaWorkflow() + setupSchemaWorkflow(); ndv.getters.outputDisplayMode().children().should('have.length', 3); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); schemaKeys.forEach((key) => { - ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('exist'); + ndv.getters + .outputPanel() + .find('[data-test-id=run-data-schema-item]') + .contains(key) + .should('exist'); }); }); it('should preserve schema view after execution', () => { - setupSchemaWorkflow() - ndv.getters.outputDisplayMode().contains('Schema').click(); + setupSchemaWorkflow(); + ndv.actions.switchOutputMode('Schema'); ndv.actions.execute(); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); - }) + }); it('should collapse and expand nested schema object', () => { - setupSchemaWorkflow() - const expandedObjectProps = ['prop1', 'prop2'];; - const getObjectValueItem = () => ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').filter(':contains("objectValue")'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + setupSchemaWorkflow(); + const expandedObjectProps = ['prop1', 'prop2']; + const getObjectValueItem = () => + ndv.getters + .outputPanel() + .find('[data-test-id=run-data-schema-item]') + .filter(':contains("objectValue")'); + ndv.actions.switchOutputMode('Schema'); expandedObjectProps.forEach((key) => { - ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('be.visible'); + ndv.getters + .outputPanel() + .find('[data-test-id=run-data-schema-item]') + .contains(key) + .should('be.visible'); }); getObjectValueItem().find('label').click(); expandedObjectProps.forEach((key) => { - ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('not.be.visible'); + ndv.getters + .outputPanel() + .find('[data-test-id=run-data-schema-item]') + .contains(key) + .should('not.be.visible'); }); - }) + }); it('should not display pagination for schema', () => { - setupSchemaWorkflow() + setupSchemaWorkflow(); ndv.getters.backToCanvas().click(); workflowPage.getters.canvasNodeByName('Set').click(); - workflowPage.actions.addNodeToCanvas('Customer Datastore (n8n training)', true, true, 'Get All People'); + workflowPage.actions.addNodeToCanvas( + 'Customer Datastore (n8n training)', + true, + true, + 'Get All People', + ); ndv.actions.execute(); ndv.getters.outputPanel().contains('25 items').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); - ndv.getters.outputDisplayMode().contains('JSON').click(); + ndv.actions.switchOutputMode('JSON'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); - }) + }); it('should display large schema', () => { - cy.createFixtureWorkflow('Test_workflow_schema_test_pinned_data.json', `NDV test schema view ${uuid()}`); + cy.createFixtureWorkflow( + 'Test_workflow_schema_test_pinned_data.json', + `NDV test schema view ${uuid()}`, + ); workflowPage.actions.zoomToFit(); workflowPage.actions.openNode('Set'); ndv.getters.outputPanel().contains('20 items').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); - ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]').should('have.length', 20); - }) + ndv.getters + .outputPanel() + .find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]') + .should('have.length', 20); + }); }); it('can link and unlink run selectors between input and output', () => { @@ -171,11 +231,13 @@ describe('NDV', () => { workflowPage.actions.executeWorkflow(); workflowPage.actions.openNode('Set3'); - ndv.getters.inputRunSelector() + ndv.getters + .inputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); @@ -184,23 +246,20 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); - ndv.getters.inputRunSelector() - .find('input') - .should('include.value', '1 of 2 (6 items)'); + ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111'); ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111'); ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.actions.changeInputRunSelector('2 of 2 (6 items)'); - ndv.getters.outputRunSelector() - .find('input') - .should('include.value', '2 of 2 (6 items)'); + ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)'); // unlink ndv.actions.toggleOutputRunLinking(); ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); - ndv.getters.inputRunSelector() + ndv.getters + .inputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); @@ -208,23 +267,449 @@ describe('NDV', () => { // link again ndv.actions.toggleOutputRunLinking(); ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip - ndv.getters.inputRunSelector() - .find('input') - .should('include.value', '1 of 2 (6 items)'); - + ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); + // unlink again ndv.actions.toggleInputRunLinking(); ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.actions.changeInputRunSelector('2 of 2 (6 items)'); - ndv.getters.outputRunSelector() - .find('input') - .should('include.value', '1 of 2 (6 items)'); + ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); // link again ndv.actions.toggleInputRunLinking(); ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip - ndv.getters.outputRunSelector() - .find('input') - .should('include.value', '2 of 2 (6 items)'); + ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)'); + }); + + it('should display parameter hints correctly', () => { + workflowPage.actions.visit(); + + cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`); + workflowPage.actions.openNode('Set1'); + + ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions + + [ + { + input: 'hello', + }, + { + input: '', + output: '[empty]', + }, + { + input: ' test', + }, + { + input: ' ', + }, + { + input: '
', + }, + ].forEach(({ input, output }) => { + if (input) { + ndv.actions.typeIntoParameterInput('value', input); + } + ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview + + ndv.actions.validateExpressionPreview('value', output || input); + ndv.getters.parameterInput('value').clear(); + }); + }); + + it('should not retrieve remote options when required params throw errors', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); + + ndv.getters.parameterInput('remoteOptions').click(); + getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); + + ndv.actions.setInvalidExpression({ fieldName: 'fieldId', delay: 200 }); + + ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview + + ndv.getters.parameterInput('remoteOptions').click(); + + ndv.getters.parameterInputIssues('remoteOptions').realHover({ scrollBehavior: false }); + // Remote options dropdown should not be visible + ndv.getters.parameterInput('remoteOptions').find('.el-select').should('not.exist'); + }); + + it('should retrieve remote options when non-required params throw errors', () => { + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); + + ndv.getters.parameterInput('remoteOptions').click(); + getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); + ndv.getters.parameterInput('remoteOptions').click(); + + ndv.actions.setInvalidExpression({ fieldName: 'otherField', delay: 50 }); + + ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview + + ndv.getters.parameterInput('remoteOptions').click(); + getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); + }); + + it('should flag issues as soon as params are set', () => { + workflowPage.actions.addInitialNodeToCanvas('Webhook'); + workflowPage.getters.canvasNodes().first().dblclick(); + + workflowPage.getters.nodeIssuesByName('Webhook').should('not.exist'); + ndv.getters.nodeExecuteButton().should('not.be.disabled'); + ndv.getters.triggerPanelExecuteButton().should('exist'); + + ndv.getters.parameterInput('path').clear(); + + ndv.getters.nodeExecuteButton().should('be.disabled'); + ndv.getters.triggerPanelExecuteButton().should('not.exist'); + ndv.actions.close(); + workflowPage.getters.nodeIssuesByName('Webhook').should('exist'); + + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.parameterInput('path').type('t'); + + ndv.getters.nodeExecuteButton().should('not.be.disabled'); + ndv.getters.triggerPanelExecuteButton().should('exist'); + + ndv.actions.close(); + workflowPage.getters.nodeIssuesByName('Webhook').should('not.exist'); + }); + + it('should not push NDV header out with a lot of code in Code node editor', () => { + workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true }); + ndv.getters.parameterInput('jsCode').get('.cm-content').type('{selectall}').type('{backspace}'); + cy.fixture('Dummy_javascript.txt').then((code) => { + ndv.getters.parameterInput('jsCode').get('.cm-content').paste(code); + }); + ndv.getters.nodeExecuteButton().should('be.visible'); + }); + + it('should allow editing code in fullscreen in the Code node', () => { + workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true }); + ndv.actions.openCodeEditorFullscreen(); + + ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()'); + ndv.getters.codeEditorFullscreen().should('contain.text', 'foo()'); + cy.wait(200); + ndv.getters.codeEditorDialog().find('.el-dialog__close').click(); + ndv.getters.parameterInput('jsCode').get('.cm-content').should('contain.text', 'foo()'); + }); + + it('should not retrieve remote options when a parameter value changes', () => { + cy.intercept('/rest/dynamic-node-parameters/options?**', cy.spy().as('fetchParameterOptions')); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); + // Type something into the field + ndv.actions.typeIntoParameterInput('otherField', 'test'); + // Should call the endpoint only once (on mount), not for every keystroke + cy.get('@fetchParameterOptions').should('have.been.calledOnce'); + }); + + describe('floating nodes', () => { + function getFloatingNodeByPosition( + position: 'inputMain' | 'outputMain' | 'outputSub' | 'inputSub', + ) { + return cy.get(`[data-node-placement=${position}]`); + } + + it('should traverse floating nodes with mouse', () => { + cy.createFixtureWorkflow('Floating_Nodes.json', `Floating Nodes`); + workflowPage.getters.canvasNodes().first().dblclick(); + getFloatingNodeByPosition('inputMain').should('not.exist'); + getFloatingNodeByPosition('outputMain').should('exist'); + // Traverse 4 connected node forwards + Array.from(Array(4).keys()).forEach((i) => { + getFloatingNodeByPosition('outputMain').click({ force: true }); + ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`); + getFloatingNodeByPosition('inputMain').should('exist'); + getFloatingNodeByPosition('outputMain').should('exist'); + ndv.actions.close(); + workflowPage.getters.selectedNodes().should('have.length', 1); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', `Node ${i + 1}`); + workflowPage.getters.selectedNodes().first().dblclick(); + }); + + getFloatingNodeByPosition('outputMain').click({ force: true }); + ndv.getters.nodeNameContainer().should('contain', 'Chain'); + + // Traverse 4 connected node backwards + Array.from(Array(4).keys()).forEach((i) => { + getFloatingNodeByPosition('inputMain').click({ force: true }); + ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`); + getFloatingNodeByPosition('outputMain').should('exist'); + getFloatingNodeByPosition('inputMain').should('exist'); + }); + getFloatingNodeByPosition('inputMain').click({ force: true }); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME); + getFloatingNodeByPosition('inputMain').should('not.exist'); + getFloatingNodeByPosition('inputSub').should('not.exist'); + getFloatingNodeByPosition('outputSub').should('not.exist'); + ndv.actions.close(); + workflowPage.getters.selectedNodes().should('have.length', 1); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME); + }); + + it('should traverse floating nodes with keyboard', () => { + cy.createFixtureWorkflow('Floating_Nodes.json', `Floating Nodes`); + workflowPage.getters.canvasNodes().first().dblclick(); + getFloatingNodeByPosition('inputMain').should('not.exist'); + getFloatingNodeByPosition('outputMain').should('exist'); + // Traverse 4 connected node forwards + Array.from(Array(4).keys()).forEach((i) => { + cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']); + ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`); + getFloatingNodeByPosition('inputMain').should('exist'); + getFloatingNodeByPosition('outputMain').should('exist'); + ndv.actions.close(); + workflowPage.getters.selectedNodes().should('have.length', 1); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', `Node ${i + 1}`); + workflowPage.getters.selectedNodes().first().dblclick(); + }); + + cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']); + ndv.getters.nodeNameContainer().should('contain', 'Chain'); + + // Traverse 4 connected node backwards + Array.from(Array(4).keys()).forEach((i) => { + cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']); + ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`); + getFloatingNodeByPosition('outputMain').should('exist'); + getFloatingNodeByPosition('inputMain').should('exist'); + }); + cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME); + getFloatingNodeByPosition('inputMain').should('not.exist'); + getFloatingNodeByPosition('inputSub').should('not.exist'); + getFloatingNodeByPosition('outputSub').should('not.exist'); + ndv.actions.close(); + workflowPage.getters.selectedNodes().should('have.length', 1); + workflowPage.getters + .selectedNodes() + .first() + .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME); + }); + + it('should connect floating sub-nodes', () => { + const nodeCreator = new NodeCreator(); + const connectionGroups = [ + { + title: 'Language Models', + id: 'ai_languageModel', + }, + { + title: 'Tools', + id: 'ai_tool', + }, + ]; + + workflowPage.actions.addInitialNodeToCanvas('AI Agent', { keepNdvOpen: true }); + + connectionGroups.forEach((group) => { + cy.getByTestId(`add-subnode-${group.id}`).should('exist'); + cy.getByTestId(`add-subnode-${group.id}`).click(); + + cy.getByTestId('nodes-list-header').contains(group.title).should('exist'); + nodeCreator.getters.getNthCreatorItem(1).click(); + getFloatingNodeByPosition('outputSub').should('exist'); + getFloatingNodeByPosition('outputSub').click({ force: true }); + + if (group.id === 'ai_languageModel') { + cy.getByTestId(`add-subnode-${group.id}`).should('not.exist'); + } else { + cy.getByTestId(`add-subnode-${group.id}`).should('exist'); + // Expand the subgroup + cy.getByTestId('subnode-connection-group-ai_tool').click(); + cy.getByTestId(`add-subnode-${group.id}`).click(); + nodeCreator.getters.getNthCreatorItem(1).click(); + getFloatingNodeByPosition('outputSub').click({ force: true }); + cy.getByTestId('subnode-connection-group-ai_tool') + .findChildByTestId('floating-subnode') + .should('have.length', 2); + } + }); + + // Since language model has no credentials set, it should show an error + // Sinse code tool require alphanumeric tool name it would also show an error(2 errors, 1 for each tool node) + cy.get('[class*=hasIssues]').should('have.length', 3); + }); + }); + + it('should show node name and version in settings', () => { + cy.createFixtureWorkflow('Test_workflow_ndv_version.json', `NDV test version ${uuid()}`); + + workflowPage.actions.openNode('Edit Fields (old)'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Set node version 2 (Latest version: 3.3)'); + ndv.actions.close(); + + workflowPage.actions.openNode('Edit Fields (latest)'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Edit Fields (Set) node version 3.3 (Latest)'); + ndv.actions.close(); + + workflowPage.actions.openNode('Edit Fields (no typeVersion)'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Edit Fields (Set) node version 3.3 (Latest)'); + ndv.actions.close(); + + workflowPage.actions.openNode('Function'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Function node version 1 (Deprecated)'); + ndv.actions.close(); + }); + + it('Should render xml and html tags as strings and can search', () => { + cy.createFixtureWorkflow('Test_workflow_xml_output.json', `test`); + + workflowPage.actions.executeWorkflow(); + + workflowPage.actions.openNode('Edit Fields'); + + ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table'); + + ndv.getters + .outputTableRow(1) + .should('include.text', ' '); + + cy.document().trigger('keyup', { key: '/' }); + ndv.getters.searchInput().filter(':focus').type(' Introduction to XML John Doe 2020 1234567890 Data Science Basics Jane Smith 2019 0987654321 Programming in Python Bob Johnson 2021 5432109876 "}]', + ); + ndv.getters.outputDataContainer().find('mark').should('have.text', ' span') + .should('include.text', ''); + }); + + it('should properly show node execution indicator', () => { + workflowPage.actions.addInitialNodeToCanvas('Code'); + workflowPage.actions.openNode('Code'); + // Should not show run info before execution + ndv.getters.nodeRunSuccessIndicator().should('not.exist'); + ndv.getters.nodeRunErrorIndicator().should('not.exist'); + ndv.getters.nodeExecuteButton().click(); + ndv.getters.nodeRunSuccessIndicator().should('exist'); + }); + + it('should properly show node execution indicator for multiple nodes', () => { + workflowPage.actions.addInitialNodeToCanvas('Code'); + workflowPage.actions.openNode('Code'); + ndv.actions.typeIntoParameterInput('jsCode', 'testets'); + ndv.getters.backToCanvas().click(); + workflowPage.actions.executeWorkflow(); + // Manual tigger node should show success indicator + workflowPage.actions.openNode('When clicking "Test workflow"'); + ndv.getters.nodeRunSuccessIndicator().should('exist'); + // Code node should show error + ndv.getters.backToCanvas().click(); + workflowPage.actions.openNode('Code'); + ndv.getters.nodeRunErrorIndicator().should('exist'); + }); + + it('Should clear mismatched collection parameters', () => { + workflowPage.actions.addInitialNodeToCanvas('LDAP', { + keepNdvOpen: true, + action: 'Create a new entry', + }); + // Add some attributes in Create operation + cy.getByTestId('parameter-item').contains('Add Attributes').click(); + ndv.actions.changeNodeOperation('Update'); + // Attributes should be empty after operation change + cy.getByTestId('parameter-item').contains('Currently no items exist').should('exist'); + }); + + it('Should keep RLC values after operation change', () => { + const TEST_DOC_ID = '1111'; + workflowPage.actions.addInitialNodeToCanvas('Google Sheets', { + keepNdvOpen: true, + action: 'Append row in sheet', + }); + ndv.actions.setRLCValue('documentId', TEST_DOC_ID); + ndv.actions.changeNodeOperation('Update Row'); + ndv.getters.resourceLocatorInput('documentId').find('input').should('have.value', TEST_DOC_ID); + }); + + it('Should not clear resource/operation after credential change', () => { + workflowPage.actions.addInitialNodeToCanvas('Discord', { + keepNdvOpen: true, + action: 'Delete a message', + }); + + clickCreateNewCredential(); + setCredentialValues({ + botToken: 'sk_test_123', + }); + + ndv.getters.parameterInput('resource').find('input').should('have.value', 'Message'); + ndv.getters.parameterInput('operation').find('input').should('have.value', 'Delete'); + }); + + it('Should open appropriate node creator after clicking on connection hint link', () => { + const nodeCreator = new NodeCreator(); + const hintMapper = { + Memory: 'AI Nodes', + 'Output Parser': 'AI Nodes', + 'Token Splitter': 'Document Loaders', + Tool: 'AI Nodes', + Embeddings: 'Vector Stores', + 'Vector Store': 'Retrievers', + }; + cy.createFixtureWorkflow( + 'open_node_creator_for_connection.json', + `open_node_creator_for_connection ${uuid()}`, + ); + + Object.entries(hintMapper).forEach(([node, group]) => { + workflowPage.actions.openNode(node); + cy.get('[data-action=openSelectiveNodeCreator]').contains('Insert one').click(); + nodeCreator.getters.activeSubcategory().should('contain', group); + cy.realPress('Escape'); + }); + }); + + it('Stop listening for trigger event from NDV', () => { + cy.intercept('POST', '/rest/workflows/run').as('workflowRun'); + workflowPage.actions.addInitialNodeToCanvas('Local File Trigger', { + keepNdvOpen: true, + action: 'On Changes To A Specific File', + isTrigger: true, + }); + ndv.getters.triggerPanelExecuteButton().should('exist'); + ndv.getters.triggerPanelExecuteButton().realClick(); + ndv.getters.triggerPanelExecuteButton().should('contain', 'Stop Listening'); + ndv.getters.triggerPanelExecuteButton().realClick(); + cy.wait('@workflowRun').then(() => { + ndv.getters.triggerPanelExecuteButton().should('contain', 'Test step'); + workflowPage.getters.successToast().should('exist'); + }); }); }); diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts index 7fa465d2ee86d..0964cff41e225 100644 --- a/cypress/e2e/6-code-node.cy.ts +++ b/cypress/e2e/6-code-node.cy.ts @@ -5,31 +5,153 @@ const WorkflowPage = new WorkflowPageClass(); const ndv = new NDV(); describe('Code node', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); + describe('Code editor', () => { + beforeEach(() => { + WorkflowPage.actions.visit(); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); + WorkflowPage.actions.addNodeToCanvas('Code', true, true); + }); + + it('should show correct placeholders switching modes', () => { + cy.contains('// Loop over input items and add a new field').should('be.visible'); + + ndv.getters.parameterInput('mode').click(); + ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); + + cy.contains("// Add a new field called 'myNewField'").should('be.visible'); - it('should execute the placeholder in all-items mode successfully', () => { - WorkflowPage.actions.addInitialNodeToCanvas('Manual'); - WorkflowPage.actions.addNodeToCanvas('Code'); - WorkflowPage.actions.openNode('Code'); + ndv.getters.parameterInput('mode').click(); + ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for All Items'); + cy.contains('// Loop over input items and add a new field').should('be.visible'); + }); - ndv.actions.execute(); + it('should execute the placeholder successfully in both modes', () => { + ndv.actions.execute(); - WorkflowPage.getters.successToast().contains('Node executed successfully'); + WorkflowPage.getters.successToast().contains('Node executed successfully'); + ndv.getters.parameterInput('mode').click(); + ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); + + ndv.actions.execute(); + + WorkflowPage.getters.successToast().contains('Node executed successfully'); + }); }); - 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'); - ndv.getters.parameterInput('mode').click(); - ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); + describe('Ask AI', () => { + it('tab should display based on experiment', () => { + WorkflowPage.actions.visit(); + cy.window().then((win) => { + win.featureFlags.override('011_ask_AI', 'control'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); + WorkflowPage.actions.addNodeToCanvas('Code'); + WorkflowPage.actions.openNode('Code'); + + cy.getByTestId('code-node-tab-ai').should('not.exist'); + + ndv.actions.close(); + win.featureFlags.override('011_ask_AI', undefined); + WorkflowPage.actions.openNode('Code'); + cy.getByTestId('code-node-tab-ai').should('not.exist'); + }); + }); + + describe('Enabled', () => { + beforeEach(() => { + WorkflowPage.actions.visit(); + cy.window().then((win) => { + win.featureFlags.override('011_ask_AI', 'gpt3'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); + WorkflowPage.actions.addNodeToCanvas('Code', true, true); + }); + }); + + it('tab should exist if experiment selected and be selectable', () => { + cy.getByTestId('code-node-tab-ai').should('exist'); + cy.get('#tab-ask-ai').click(); + cy.contains('Hey AI, generate JavaScript').should('exist'); + }); + + it('generate code button should have correct state & tooltips', () => { + cy.getByTestId('code-node-tab-ai').should('exist'); + cy.get('#tab-ask-ai').click(); + + cy.getByTestId('ask-ai-cta').should('be.disabled'); + cy.getByTestId('ask-ai-cta').realHover(); + cy.getByTestId('ask-ai-cta-tooltip-no-input-data').should('exist'); + ndv.actions.executePrevious(); + cy.getByTestId('ask-ai-cta').realHover(); + cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist'); + cy.getByTestId('ask-ai-prompt-input') + // Type random 14 character string + .type([...Array(14)].map(() => ((Math.random() * 36) | 0).toString(36)).join('')); + + cy.getByTestId('ask-ai-cta').realHover(); + cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist'); + + cy.getByTestId('ask-ai-prompt-input') + .clear() + // Type random 15 character string + .type([...Array(15)].map(() => ((Math.random() * 36) | 0).toString(36)).join('')); + cy.getByTestId('ask-ai-cta').should('be.enabled'); + + cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600'); + }); + + it('should send correct schema and replace code', () => { + const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''); + cy.get('#tab-ask-ai').click(); + ndv.actions.executePrevious(); + + cy.getByTestId('ask-ai-prompt-input').type(prompt); + + cy.intercept('POST', '/rest/ask-ai', { + statusCode: 200, + body: { + data: { + code: 'console.log("Hello World")', + }, + }, + }).as('ask-ai'); + + cy.getByTestId('ask-ai-cta').click(); + const askAiReq = cy.wait('@ask-ai'); + + askAiReq + .its('request.body') + .should('have.keys', ['question', 'model', 'context', 'n8nVersion']); + + askAiReq.its('context').should('have.keys', ['schema', 'ndvPushRef', 'pushRef']); + + cy.contains('Code generation completed').should('be.visible'); + cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")'); + cy.get('#tab-code').should('have.class', 'is-active'); + }); + + it('should show error based on status code', () => { + const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''); + cy.get('#tab-ask-ai').click(); + ndv.actions.executePrevious(); + + cy.getByTestId('ask-ai-prompt-input').type(prompt); + + const handledCodes = [ + { code: 400, message: 'Code generation failed due to an unknown reason' }, + { code: 413, message: 'Your workflow data is too large for AI to process' }, + { code: 429, message: "We've hit our rate limit with our AI partner" }, + { code: 500, message: 'Code generation failed due to an unknown reason' }, + ]; - ndv.actions.execute(); + handledCodes.forEach(({ code, message }) => { + cy.intercept('POST', '/rest/ask-ai', { + statusCode: code, + status: code, + }).as('ask-ai'); - WorkflowPage.getters.successToast().contains('Node executed successfully'); + cy.getByTestId('ask-ai-cta').click(); + cy.contains(message).should('be.visible'); + }); + }); + }); }); }); diff --git a/cypress/e2e/7-workflow-actions.cy.ts b/cypress/e2e/7-workflow-actions.cy.ts index baa38e9f7a205..794e2ee605821 100644 --- a/cypress/e2e/7-workflow-actions.cy.ts +++ b/cypress/e2e/7-workflow-actions.cy.ts @@ -3,22 +3,24 @@ import { MANUAL_TRIGGER_NODE_NAME, META_KEY, SCHEDULE_TRIGGER_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, + INSTANCE_MEMBERS, + INSTANCE_OWNER, } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { getVisibleSelect } from '../utils'; +import { WorkflowExecutionsTab } from '../pages'; const NEW_WORKFLOW_NAME = 'Something else'; -const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json'; const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow'; const DUPLICATE_WORKFLOW_TAG = 'Duplicate'; const WorkflowPage = new WorkflowPageClass(); +const WorkflowPages = new WorkflowsPageClass(); +const executionsTab = new WorkflowExecutionsTab(); describe('Workflow Actions', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); }); @@ -67,6 +69,63 @@ 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)); + cy.waitForLoad(); + 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 not save workflow twice when save is in progress', () => { + // This happens when users click save button from workflow name input + // In this case blur on the input saves the workflow and then click on the button saves it again + WorkflowPage.actions.visit(); + WorkflowPage.getters + .workflowNameInput() + .invoke('val') + .then((oldName) => { + WorkflowPage.getters.workflowNameInputContainer().click(); + WorkflowPage.getters.workflowNameInput().type('{selectall}'); + WorkflowPage.getters.workflowNameInput().type('Test'); + WorkflowPage.getters.saveButton().click(); + WorkflowPage.getters.workflowNameInput().should('have.value', 'Test'); + cy.visit(WorkflowPages.url); + // There should be no workflow with the old name (duplicate save) + WorkflowPages.getters.workflowCards().contains(String(oldName)).should('not.exist'); + }); + }); + it('should copy nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); @@ -79,98 +138,56 @@ describe('Workflow Actions', () => { WorkflowPage.getters.successToast().should('exist'); }); - it('should paste nodes', () => { + it('should paste nodes (both current and old node versions)', () => { cy.fixture('Test_workflow-actions_paste-data.json').then((data) => { cy.get('body').paste(JSON.stringify(data)); - WorkflowPage.getters.canvasNodes().should('have.have.length', 2); + WorkflowPage.actions.zoomToFit(); + WorkflowPage.getters.canvasNodes().should('have.length', 5); + WorkflowPage.getters.nodeConnections().should('have.length', 5); }); }); - it('should import workflow from url', () => { - WorkflowPage.getters.workflowMenu().should('be.visible'); - WorkflowPage.getters.workflowMenu().click(); - WorkflowPage.getters.workflowMenuItemImportFromURLItem().should('be.visible'); - WorkflowPage.getters.workflowMenuItemImportFromURLItem().click(); - cy.get('.el-message-box').should('be.visible'); - cy.get('.el-message-box').find('input').type(IMPORT_WORKFLOW_URL); - cy.get('body').type('{enter}'); - cy.waitForLoad(false) - WorkflowPage.actions.zoomToFit(); - WorkflowPage.getters.canvasNodes().should('have.length', 2); - WorkflowPage.getters.nodeConnections().should('have.length', 1); - }); - - it('should import workflow from file', () => { - WorkflowPage.getters - .workflowImportInput() - .selectFile('cypress/fixtures/Test_workflow-actions_paste-data.json', { force: true }); - cy.waitForLoad(false) - WorkflowPage.actions.zoomToFit(); - WorkflowPage.getters.canvasNodes().should('have.length', 2); - WorkflowPage.getters.nodeConnections().should('have.length', 1); - }); - it('should update workflow settings', () => { - cy.resetAll(); - cy.skipSetup(); - 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', 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'); + 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().click(); + getVisibleSelect() + .find('li') + .should('have.length', totalWorkflows + 2); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsTimezoneSelect().click(); + getVisibleSelect().find('li').should('exist'); + getVisibleSelect().find('li').eq(1).click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveFiledExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveSuccessExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveManualExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveExecutionProgressSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().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', () => { @@ -208,7 +225,7 @@ describe('Workflow Actions', () => { .find('.el-select__tags input') .type(DUPLICATE_WORKFLOW_TAG); WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{enter}'); - WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{enter}'); + WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{esc}'); WorkflowPage.getters .duplicateWorkflowModal() .find('button') @@ -229,9 +246,46 @@ describe('Workflow Actions', () => { it('should duplicate unsaved workflow', () => { duplicateWorkflow(); }); + it('should duplicate saved workflow', () => { WorkflowPage.actions.saveWorkflowOnButtonClick(); duplicateWorkflow(); }); }); + + it('should keep endpoint click working when switching between execution and editor tab', () => { + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + + WorkflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME); + WorkflowPage.actions.saveWorkflowOnButtonClick(); + + WorkflowPage.getters.canvasNodePlusEndpointByName(EDIT_FIELDS_SET_NODE_NAME).click(); + WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + cy.get('body').type('{esc}'); + + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions']); + cy.wait(500); + executionsTab.actions.switchToEditorTab(); + + WorkflowPage.getters.canvasNodePlusEndpointByName(EDIT_FIELDS_SET_NODE_NAME).click(); + WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); + }); +}); + +describe('Menu entry Push To Git', () => { + it('should not show up in the menu for members', () => { + cy.signin(INSTANCE_MEMBERS[0]); + cy.visit(WorkflowPages.url); + WorkflowPage.actions.visit(); + WorkflowPage.getters.workflowMenuItemGitPush().should('not.exist'); + }); + + it('should show up for owners', () => { + cy.signin(INSTANCE_OWNER); + cy.visit(WorkflowPages.url); + WorkflowPage.actions.visit(); + WorkflowPage.getters.workflowMenuItemGitPush().should('exist'); + }); }); diff --git a/cypress/e2e/8-http-request-node.cy.ts b/cypress/e2e/8-http-request-node.cy.ts index 9c3cce11e85c3..1567815ddf448 100644 --- a/cypress/e2e/8-http-request-node.cy.ts +++ b/cypress/e2e/8-http-request-node.cy.ts @@ -1,12 +1,13 @@ import { WorkflowPage, NDV } from '../pages'; +import { NodeCreator } from '../pages/features/node-creator'; const workflowPage = new WorkflowPage(); +const nodeCreatorFeature = new NodeCreator(); const ndv = new NDV(); describe('HTTP Request node', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); + beforeEach(() => { + workflowPage.actions.visit(); }); it('should make a request with a URL and receive a response', () => { @@ -19,4 +20,40 @@ describe('HTTP Request node', () => { ndv.getters.outputPanel().contains('fact'); }); + + describe('Credential-only HTTP Request Node variants', () => { + it('should render a modified HTTP Request Node', () => { + workflowPage.actions.addInitialNodeToCanvas('Manual'); + + workflowPage.getters.nodeCreatorPlusButton().click(); + workflowPage.getters.nodeCreatorSearchBar().type('VirusTotal'); + + expect(nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'VirusTotal')); + expect( + nodeCreatorFeature.getters + .nodeItemDescription() + .first() + .should('have.text', 'HTTP request'), + ); + + nodeCreatorFeature.actions.selectNode('VirusTotal'); + expect(ndv.getters.nodeNameContainer().should('contain.text', 'VirusTotal HTTP Request')); + expect( + ndv.getters + .parameterInput('url') + .find('input') + .should('contain.value', 'https://www.virustotal.com/api/v3/'), + ); + + // These parameters exist for normal HTTP Request Node, but are hidden for credential-only variants + expect(ndv.getters.parameterInput('authentication').should('not.exist')); + expect(ndv.getters.parameterInput('nodeCredentialType').should('not.exist')); + + expect( + workflowPage.getters + .nodeCredentialsLabel() + .should('contain.text', 'Credential for VirusTotal'), + ); + }); + }); }); diff --git a/cypress/e2e/9-expression-editor-modal.cy.ts b/cypress/e2e/9-expression-editor-modal.cy.ts index 957c0505f5505..66758cabd3e0f 100644 --- a/cypress/e2e/9-expression-editor-modal.cy.ts +++ b/cypress/e2e/9-expression-editor-modal.cy.ts @@ -1,65 +1,117 @@ +import { NDV } from '../pages/ndv'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; const WorkflowPage = new WorkflowPageClass(); +const ndv = new NDV(); describe('Expression editor modal', () => { - before(() => { - cy.resetAll(); - cy.skipSetup(); - }); - beforeEach(() => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual'); - WorkflowPage.actions.addNodeToCanvas('Hacker News'); - WorkflowPage.actions.openNode('Hacker News'); - WorkflowPage.actions.openExpressionEditorModal(); + WorkflowPage.actions.addInitialNodeToCanvas('Schedule'); + cy.on('uncaught:exception', (err) => err.name !== 'ExpressionError'); }); - it('should resolve primitive resolvables', () => { - WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters.expressionModalInput().type('{{ 1 + 2'); - WorkflowPage.getters.expressionModalOutput().contains(/^3$/); - WorkflowPage.getters.expressionModalInput().clear(); + describe('Static data', () => { + beforeEach(() => { + WorkflowPage.actions.addNodeToCanvas('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openExpressionEditorModal(); + }); - WorkflowPage.getters.expressionModalInput().type('{{ "ab" + "cd"'); - WorkflowPage.getters.expressionModalOutput().contains(/^abcd$/); + it('should resolve primitive resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ 1 + 2'); + WorkflowPage.getters.expressionModalOutput().contains(/^3$/); + WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ "ab" + "cd"'); + WorkflowPage.getters.expressionModalOutput().contains(/^abcd$/); - WorkflowPage.getters.expressionModalInput().type('{{ true && false'); - WorkflowPage.getters.expressionModalOutput().contains(/^false$/); - }); + WorkflowPage.getters.expressionModalInput().clear(); - it('should resolve object resolvables', () => { - WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters - .expressionModalInput() - .type('{{ { a : 1 }', { parseSpecialCharSequences: false }); - WorkflowPage.getters.expressionModalOutput().contains(/^\[Object: \{"a": 1\}\]$/); + WorkflowPage.getters.expressionModalInput().type('{{ true && false'); + WorkflowPage.getters.expressionModalOutput().contains(/^false$/); + }); - WorkflowPage.getters.expressionModalInput().clear(); + it('should resolve object resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters + .expressionModalInput() + .type('{{ { a : 1 }', { parseSpecialCharSequences: false }); + WorkflowPage.getters.expressionModalOutput().contains(/^\[Object: \{"a": 1\}\]$/); - WorkflowPage.getters - .expressionModalInput() - .type('{{ { a : 1 }.a', { parseSpecialCharSequences: false }); - WorkflowPage.getters.expressionModalOutput().contains(/^1$/); - }); + WorkflowPage.getters.expressionModalInput().clear(); + + WorkflowPage.getters + .expressionModalInput() + .type('{{ { a : 1 }.a', { parseSpecialCharSequences: false }); + WorkflowPage.getters.expressionModalOutput().contains(/^1$/); + }); - it('should resolve array resolvables', () => { - WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3]'); - WorkflowPage.getters.expressionModalOutput().contains(/^\[Array: \[1,2,3\]\]$/); + it('should resolve array resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3]'); + WorkflowPage.getters.expressionModalOutput().contains(/^\[Array: \[1,2,3\]\]$/); - WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3][0]'); - WorkflowPage.getters.expressionModalOutput().contains(/^1$/); + WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3][0]'); + WorkflowPage.getters.expressionModalOutput().contains(/^1$/); + }); }); - it('should resolve $parameter[]', () => { - WorkflowPage.getters.expressionModalInput().clear(); - WorkflowPage.getters.expressionModalInput().type('{{ $parameter["operation"]'); - WorkflowPage.getters.expressionModalOutput().contains(/^getAll$/); + describe('Dynamic data', () => { + beforeEach(() => { + WorkflowPage.actions.openNode('Schedule Trigger'); + ndv.actions.setPinnedData([{ myStr: 'Monday' }]); + ndv.actions.close(); + WorkflowPage.actions.addNodeToCanvas('No Operation'); + WorkflowPage.actions.addNodeToCanvas('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openExpressionEditorModal(); + }); + + it('should resolve $parameter[]', () => { + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ $parameter["operation"]'); + WorkflowPage.getters.expressionModalOutput().should('have.text', 'getAll'); + }); + + it('should resolve input: $json,$input,$(nodeName)', () => { + // Previous nodes have not run, input is empty + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ $json.myStr'); + WorkflowPage.getters + .expressionModalOutput() + .should('have.text', '[Execute previous nodes for preview]'); + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ $input.item.json.myStr'); + WorkflowPage.getters + .expressionModalOutput() + .should('have.text', '[Execute previous nodes for preview]'); + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type("{{ $('Schedule Trigger').item.json.myStr"); + WorkflowPage.getters + .expressionModalOutput() + .should('have.text', '[Execute previous nodes for preview]'); + + // Run workflow + cy.get('body').type('{esc}'); + ndv.actions.close(); + WorkflowPage.actions.executeNode('No Operation'); + WorkflowPage.actions.openNode('Hacker News'); + WorkflowPage.actions.openExpressionEditorModal(); + + // Previous nodes have run, input can be resolved + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ $json.myStr'); + WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday'); + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type('{{ $input.item.json.myStr'); + WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday'); + WorkflowPage.getters.expressionModalInput().clear(); + WorkflowPage.getters.expressionModalInput().type("{{ $('Schedule Trigger').item.json.myStr"); + WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday'); + }); }); }); diff --git a/cypress/fixtures/Check_manual_node_run_for_pinned_and_rundata.json b/cypress/fixtures/Check_manual_node_run_for_pinned_and_rundata.json new file mode 100644 index 0000000000000..0236f38811bd8 --- /dev/null +++ b/cypress/fixtures/Check_manual_node_run_for_pinned_and_rundata.json @@ -0,0 +1,318 @@ +{ + "name": "Webhook PairedItem error test", + "nodes": [ + { + "parameters": { + "path": "86f05bcc-44a4-44f7-b774-7002fc2eddfc", + "options": {} + }, + "id": "143572ab-f85b-4a6f-8ca7-4a5cea00a9fe", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 860, + 140 + ], + "webhookId": "86f05bcc-44a4-44f7-b774-7002fc2eddfc" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "e9605092-a127-46ad-9fb3-e671f955f856", + "leftValue": "={{ $json.headers.host }}", + "rightValue": "asdf", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "50542b55-4237-4c18-9e3a-5d146372c270", + "name": "If", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 1080, + 140 + ] + }, + { + "parameters": {}, + "id": "c6365289-0383-4d73-bd5f-a52b6a6e1eeb", + "name": "No Operation, do nothing", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1420, + 20 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "1d65053c-31de-43e8-a870-e7e79d34ca67", + "name": "asdf", + "value": "={{ $('Webhook').item.json.headers['accept-encoding'] }}", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "8fd60b5e-19eb-47f4-a3e2-3822c722a68a", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 1860, + 220 + ] + }, + { + "parameters": {}, + "id": "96ac4860-81eb-4d47-9a6e-7c717d910fcd", + "name": "NoOp1", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1420, + 220 + ] + }, + { + "parameters": {}, + "id": "5b550207-3f4f-4519-b272-ff02d9d28ffc", + "name": "NoOp3", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 2040, + 220 + ] + }, + { + "parameters": {}, + "id": "9f450e47-902e-413e-99ce-ea93f6bc375e", + "name": "NoOp2", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1640, + 220 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "1d65053c-31de-43e8-a870-e7e79d34ca67", + "name": "asdf", + "value": "={{ $('Webhook').item.json.headers['accept-encoding'] }}", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "7acd1642-a6ef-4c33-a562-95b19fedbded", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 2220, + 220 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "1d65053c-31de-43e8-a870-e7e79d34ca67", + "name": "asdf", + "value": "={{ $('Webhook').item.json.headers['accept-encoding'] }}", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "adc93a3b-2825-4ddf-9a17-fe61b5861d43", + "name": "Edit Fields2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 1460, + 440 + ] + }, + { + "parameters": {}, + "id": "1cd5d8dc-cd08-4596-9435-d48a6e20996d", + "name": "NoOp", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1700, + 480 + ] + }, + { + "parameters": { + "content": "For Error:\n1. Execute \"If\"\n2. Execute \"NoOp2\"\n" + }, + "id": "658c3a31-b640-4338-8b22-6d0a17ab5b80", + "name": "Sticky Note", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 940, + 480 + ] + } + ], + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "localhost:5678", + "user-agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "accept-language": "en-US,en;q=0.5", + "accept-encoding": "gzip, deflate, br", + "connection": "keep-alive", + "cookie": "n8n-auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ1YWQzODQ1LWFmMTAtNDc2OC04ZDU4LTBmNDE3YTVlNDkxNSIsImVtYWlsIjoiamFuQG44bi5pbyIsInBhc3N3b3JkIjoiMzhjMTFiMDEwMjRlMDhiZTE0ZGExZjhmOWVjNGFmOTQxZmQwOGUyMzJmNWEzYmMyNjBmOTI1ZjIxNTBhNTZlYSIsImlhdCI6MTcwNzA3ODgxMSwiZXhwIjoxNzA3NjgzNjExfQ.bhbh8gCbLYAY87kPqZSGZeMBq7_4d9IgKnsrJ0UV4Iw", + "upgrade-insecure-requests": "1", + "sec-fetch-dest": "document", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "if-none-match": "W/\"22-6OS7cK0FzqnV2NeDHdOSGS1bVUs\"" + }, + "params": {}, + "query": {}, + "body": {} + } + } + ] + }, + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "No Operation, do nothing", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "NoOp1", + "type": "main", + "index": 0 + }, + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "NoOp1": { + "main": [ + [ + { + "node": "NoOp2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "NoOp3", + "type": "main", + "index": 0 + } + ] + ] + }, + "NoOp2": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "NoOp3": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "NoOp", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "631547ec-c580-4b5f-9220-7fd3d801029b", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff" + }, + "id": "fjCAcetjbaYM1vy6", + "tags": [] +} \ No newline at end of file diff --git a/cypress/fixtures/Dummy_javascript.txt b/cypress/fixtures/Dummy_javascript.txt new file mode 100644 index 0000000000000..9ccfe6c5d1417 --- /dev/null +++ b/cypress/fixtures/Dummy_javascript.txt @@ -0,0 +1,42 @@ +var File = function(url, object){ + File.list = Array.isArray(File.list)? File.list : []; + File.progress = File.progress || 0; + this.progress = 0; + this.object = object; + this.url = url; +}; + +File.indexOf = function(term){ + for(var index in File.list){ + var file = File.list[index]; + if (file.equals(term) || file.url === term || file.object === term) { + return index; + } + } + return -1; +}; + +File.find = function(term){ + var index = File.indexOf(term); + return ~index && File.list[index]; +}; + +File.prototype.equals = function(file){ + var isFileType = file instanceof File; + return isFileType && this.url === file.url && this.object === file.object; +}; + +File.prototype.save = function(update){ + update = typeof update === 'undefined'? true : update; + if(Array.isArray(File.list)){ + var index = File.indexOf(this); + if(~index && update) { + File.list[index] = this; + console.warn('File `%s` has been loaded before and updated now for: %O.', this.url, this); + }else File.list.push(this); + console.log(File.list) + }else{ + File.list = [this]; + } + return this; +}; diff --git a/cypress/fixtures/Dummy_sql.txt b/cypress/fixtures/Dummy_sql.txt new file mode 100644 index 0000000000000..c8976be22916f --- /dev/null +++ b/cypress/fixtures/Dummy_sql.txt @@ -0,0 +1,49 @@ +CREATE TABLE emp ( +empno INT PRIMARY KEY, +ename VARCHAR(10), +job VARCHAR(9), +mgr INT NULL, +hiredate DATETIME, +sal NUMERIC(7,2), +comm NUMERIC(7,2) NULL, +dept INT) +begin +insert into emp values + (1,'JOHNSON','ADMIN',6,'12-17-1990',18000,NULL,4) +insert into emp values + (2,'HARDING','MANAGER',9,'02-02-1998',52000,300,3) +insert into emp values + (3,'TAFT','SALES I',2,'01-02-1996',25000,500,3) +insert into emp values + (4,'HOOVER','SALES I',2,'04-02-1990',27000,NULL,3) +insert into emp values + (5,'LINCOLN','TECH',6,'06-23-1994',22500,1400,4) +insert into emp values + (6,'GARFIELD','MANAGER',9,'05-01-1993',54000,NULL,4) +insert into emp values + (7,'POLK','TECH',6,'09-22-1997',25000,NULL,4) +insert into emp values + (8,'GRANT','ENGINEER',10,'03-30-1997',32000,NULL,2) +insert into emp values + (9,'JACKSON','CEO',NULL,'01-01-1990',75000,NULL,4) +insert into emp values + (10,'FILLMORE','MANAGER',9,'08-09-1994',56000,NULL,2) +insert into emp values + (11,'ADAMS','ENGINEER',10,'03-15-1996',34000,NULL,2) +insert into emp values + (12,'WASHINGTON','ADMIN',6,'04-16-1998',18000,NULL,4) +insert into emp values + (13,'MONROE','ENGINEER',10,'12-03-2000',30000,NULL,2) +insert into emp values + (14,'ROOSEVELT','CPA',9,'10-12-1995',35000,NULL,1) +end +CREATE TABLE dept ( +deptno INT NOT NULL, +dname VARCHAR(14), +loc VARCHAR(13)) +begin +insert into dept values (1,'ACCOUNTING','ST LOUIS') +insert into dept values (2,'RESEARCH','NEW YORK') +insert into dept values (3,'SALES','ATLANTA') +insert into dept values (4, 'OPERATIONS','SEATTLE') +end diff --git a/cypress/fixtures/Ecommerce_starter_pack_template_collection.json b/cypress/fixtures/Ecommerce_starter_pack_template_collection.json new file mode 100644 index 0000000000000..1f908c587bae5 --- /dev/null +++ b/cypress/fixtures/Ecommerce_starter_pack_template_collection.json @@ -0,0 +1 @@ +{"collection":{"id":1,"name":"eCommerce Starter Pack","description":"eCommerce operations are complex — but there are many things that you can automate to make your life easier. This collection provides a few ideas to get started.\n\nReduce manual work and the risk of human error by automating processes such as social media promotion of products, updating customer databases, and get notifications for important events.","totalViews":0,"createdAt":"2022-02-17T12:40:50.498Z","nodes":[{"id":20,"name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"displayName":"IF","icon":"fa:map-signs","iconData":{"icon":"map-signs","type":"icon"},"typeVersion":1,"categories":[{"id":9,"name":"Core Nodes"}]},{"id":49,"name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"displayName":"Telegram","icon":"file:telegram.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"typeVersion":1,"categories":[{"id":6,"name":"Communication"}]},{"id":107,"name":"n8n-nodes-base.shopifyTrigger","defaults":{"name":"Shopify Trigger"},"displayName":"Shopify Trigger","icon":"file:shopify.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4="},"typeVersion":1,"categories":[{"id":2,"name":"Sales"}]},{"id":126,"name":"n8n-nodes-base.mautic","defaults":{"name":"Mautic"},"displayName":"Mautic","icon":"file:mautic.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo="},"typeVersion":1,"categories":[{"id":1,"name":"Marketing & Content"},{"id":6,"name":"Communication"}]},{"id":235,"name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"displayName":"WooCommerce Trigger","icon":"file:wooCommerce.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"typeVersion":1,"categories":[{"id":2,"name":"Sales"}]},{"id":325,"name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"displayName":"X (Formerly Twitter)","icon":"file:x.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"typeVersion":2,"categories":[{"id":1,"name":"Marketing & Content"}]}],"categories":[{"id":2,"name":"Sales"}],"workflows":[{"id":1205,"name":"Promote new Shopify products on Twitter and Telegram","views":485,"recentViews":9850,"totalViews":485,"createdAt":"2021-08-24T10:40:50.007Z","description":"This workflow automatically promotes your new Shopify products on Twitter and Telegram. This workflow is also featured in the blog post [*6 e-commerce workflows to power up your Shopify store*](https://n8n.io/blog/no-code-ecommerce-workflow-automations/#promote-your-new-products-on-social-media).\n\n## Prerequisites\n\n- A Shopify account and [credentials](https://docs.n8n.io/integrations/credentials/shopify/)\n- A Twitter account and [credentials](https://docs.n8n.io/integrations/credentials/twitter/)\n- A Telegram account and [credentials](https://docs.n8n.io/integrations/credentials/telegram/) for the channel you want to send messages to.\n\n## Nodes\n\n- [Shopify Trigger node](https://docs.n8n.io/integrations/trigger-nodes/n8n-nodes-base.shopifytrigger/) triggers the workflow when you create a new product in Shopify.\n- [Twitter node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.twitter/) posts a tweet with the text \"Hey there, my design is now on a new product! Visit my {shop name} to get this cool {product title} (and check out more {product type})\".\n- [Telegram node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.telegram/) posts a message with the same text as above in a Telegram channel.","workflow":{"nodes":[{"name":"Twitter","type":"n8n-nodes-base.twitter","position":[720,-220],"parameters":{"text":"=Hey there, my design is now on a new product ✨\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}}) 🛍️","additionalFields":{}},"credentials":{"twitterOAuth1Api":"twitter"},"typeVersion":1},{"name":"Telegram","type":"n8n-nodes-base.telegram","position":[720,-20],"parameters":{"text":"=Hey there, my design is now on a new product!\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}})","chatId":"123456","additionalFields":{}},"credentials":{"telegramApi":"telegram_habot"},"typeVersion":1},{"name":"product created","type":"n8n-nodes-base.shopifyTrigger","position":[540,-110],"webhookId":"2a7e0e50-8f09-4a2b-bf54-a849a6ac4fe0","parameters":{"topic":"products/create"},"credentials":{"shopifyApi":"shopify_nodeqa"},"typeVersion":1}],"connections":{"product created":{"main":[[{"node":"Twitter","type":"main","index":0},{"node":"Telegram","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":3,"nodeTypes":{"n8n-nodes-base.twitter":{"count":1},"n8n-nodes-base.telegram":{"count":1},"n8n-nodes-base.shopifyTrigger":{"count":1}}},"user":{"username":"lorenanda"},"nodes":[{"id":49,"icon":"file:telegram.svg","name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"categories":[{"id":6,"name":"Communication"}],"displayName":"Telegram","typeVersion":1},{"id":107,"icon":"file:shopify.svg","name":"n8n-nodes-base.shopifyTrigger","defaults":{"name":"Shopify Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4="},"categories":[{"id":2,"name":"Sales"}],"displayName":"Shopify Trigger","typeVersion":1},{"id":325,"icon":"file:x.svg","name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"categories":[{"id":1,"name":"Marketing & Content"}],"displayName":"X (Formerly Twitter)","typeVersion":2}],"categories":[{"id":2,"name":"Sales"},{"id":19,"name":"Marketing & Growth"}],"image":[{"id":527,"url":"https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/89a078b208fe4c6181902608b1cd1332.png"}]},{"id":1456,"name":"Add new customers from WooCommerce to Mautic","views":333,"recentViews":9833,"totalViews":333,"createdAt":"2022-02-17T15:00:40.748Z","description":"This workflow uses a WooCommerce trigger that will run when a new customer has been added, It will then add the customer to Mautic.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Mautic nodes.","workflow":{"id":83,"name":"New WooCommerce Customer to Mautic","nodes":[{"name":"Check for Existing","type":"n8n-nodes-base.mautic","position":[280,480],"parameters":{"options":{"search":"={{$json[\"email\"]}}"},"operation":"getAll","authentication":"oAuth2"},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1,"alwaysOutputData":true},{"name":"If New","type":"n8n-nodes-base.if","position":[460,480],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"id\"]}}","operation":"isEmpty"}]}},"typeVersion":1},{"name":"Create Contact","type":"n8n-nodes-base.mautic","position":[680,320],"parameters":{"email":"={{$node[\"Customer Created\"].json[\"email\"]}}","company":"={{$node[\"Customer Created\"].json[\"billing\"][\"company\"]}}","options":{},"lastName":"={{$node[\"Customer Created\"].json[\"last_name\"]}}","firstName":"={{$node[\"Customer Created\"].json[\"first_name\"]}}","authentication":"oAuth2","additionalFields":{}},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1},{"name":"Update Contact","type":"n8n-nodes-base.mautic","position":[680,580],"parameters":{"options":{},"contactId":"={{$json[\"id\"]}}","operation":"update","updateFields":{"lastName":"={{$node[\"Customer Created or Updated\"].json[\"last_name\"]}}","firstName":"={{$node[\"Customer Created or Updated\"].json[\"first_name\"]}}"},"authentication":"oAuth2"},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1},{"name":"Customer Created or Updated","type":"n8n-nodes-base.wooCommerceTrigger","position":[100,480],"webhookId":"5d89e322-a5e0-4cce-9eab-185e8375175b","parameters":{"event":"customer.updated"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"If New":{"main":[[{"node":"Create Contact","type":"main","index":0}],[{"node":"Update Contact","type":"main","index":0}]]},"Check for Existing":{"main":[[{"node":"If New","type":"main","index":0}]]},"Customer Created or Updated":{"main":[[{"node":"Check for Existing","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":6,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.mautic":{"count":3},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":126,"icon":"file:mautic.svg","name":"n8n-nodes-base.mautic","defaults":{"name":"Mautic"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo="},"categories":[{"id":1,"name":"Marketing & Content"},{"id":6,"name":"Communication"}],"displayName":"Mautic","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1459,"name":"Notify on Telegram and Twitter when new order is added in WooCommerce","views":620,"recentViews":9823,"totalViews":620,"createdAt":"2022-02-17T15:02:14.961Z","description":"This workflow uses a WooCommerce trigger that will run a new product has been added, It will then post the product to Telegram and Twitter.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce, Twitter and Telegram nodes.","workflow":{"id":85,"name":"New WooCommerce Product to Twitter and Telegram","nodes":[{"name":"Twitter","type":"n8n-nodes-base.twitter","position":[720,300],"parameters":{"text":"=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.","additionalFields":{}},"credentials":{"twitterOAuth1Api":{"id":"37","name":"joffcom"}},"typeVersion":1},{"name":"Telegram","type":"n8n-nodes-base.telegram","position":[720,500],"parameters":{"text":"=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.","chatId":"123456","additionalFields":{}},"credentials":{"telegramApi":{"id":"56","name":"Telegram account"}},"typeVersion":1},{"name":"WooCommerce Trigger","type":"n8n-nodes-base.wooCommerceTrigger","position":[540,400],"webhookId":"ab7b134b-9b2d-4e0d-b496-1aee30db0808","parameters":{"event":"product.created"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1}],"active":false,"settings":{},"connections":{"WooCommerce Trigger":{"main":[[{"node":"Twitter","type":"main","index":0},{"node":"Telegram","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.twitter":{"count":1},"n8n-nodes-base.telegram":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":49,"icon":"file:telegram.svg","name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"categories":[{"id":6,"name":"Communication"}],"displayName":"Telegram","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1},{"id":325,"icon":"file:x.svg","name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"categories":[{"id":1,"name":"Marketing & Content"}],"displayName":"X (Formerly Twitter)","typeVersion":2}],"categories":[{"id":2,"name":"Sales"},{"id":19,"name":"Marketing & Growth"}],"image":[]},{"id":1457,"name":"Notify on Slack when new order is registered in WooCommerce","views":178,"recentViews":9787,"totalViews":178,"createdAt":"2022-02-17T15:01:13.489Z","description":"This workflow uses a WooCommerce trigger that will run when an order has been placed.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.","workflow":{"id":81,"name":"New WooCommerce order to Slack","nodes":[{"name":"Order Created","type":"n8n-nodes-base.wooCommerceTrigger","position":[340,500],"webhookId":"287b4bf4-67ec-4c97-85d9-c0d3e6f59e6b","parameters":{"event":"order.created"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1},{"name":"Send to Slack","type":"n8n-nodes-base.slack","position":[780,480],"parameters":{"text":":sparkles: There is a new order :sparkles:","channel":"woo-commerce","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#66FF00","fields":{"item":[{"short":true,"title":"Order ID","value":"={{$json[\"id\"]}}"},{"short":true,"title":"Status","value":"={{$json[\"status\"]}}"},{"short":true,"title":"Total","value":"={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}"},{"short":false,"title":"Link","value":"={{$node[\"Order Created\"].json[\"_links\"][\"self\"][0][\"href\"]}}"}]},"footer":"=*Ordered:* {{$json[\"date_created\"]}} | *Transaction ID:* {{$json[\"transaction_id\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"53","name":"Slack Access Token"}},"typeVersion":1},{"name":"Price over 100","type":"n8n-nodes-base.if","position":[540,500],"parameters":{"conditions":{"number":[{"value1":"={{$json[\"total\"]}}","value2":100,"operation":"largerEqual"}]}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"Order Created":{"main":[[{"node":"Price over 100","type":"main","index":0}]]},"Price over 100":{"main":[[{"node":"Send to Slack","type":"main","index":0}],[]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1765,"name":"Get Slack notifications when new product published on WooCommerce","views":79,"recentViews":9577,"totalViews":79,"createdAt":"2022-08-12T12:36:53.409Z","description":"This workflow let's a bot in Slack notify a specific channel when a new product in WooCommerce is published and live on the site. \n\n## Prerequisites\n\n[WooCommerce](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.woocommercetrigger/) account\n[Slack](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack/) and a [Slack bot](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace)\n\n## How it works\n\n1. Listen for WooCommerce product creation\n2. If permalink starts with https://[your-url-here].com/product/\n3. Slack bot notifies channel that a new product has been added. \n\nPlease note, you must update the URL in the IF node to match your url. If your WooCommerce doesn't use the slug /product/, that will need to be updated too. \n","workflow":{"id":1016,"name":"Woocommerce to slack: notify new product created","tags":[{"id":"5","name":"FVF","createdAt":"2022-07-30T07:43:44.795Z","updatedAt":"2022-07-30T07:43:44.795Z"}],"nodes":[{"name":"If URL has /product/","type":"n8n-nodes-base.if","position":[640,300],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"permalink\"]}}","value2":"https://[add-your-url-here]/product/","operation":"startsWith"}]}},"typeVersion":1},{"name":"Send message to slack","type":"n8n-nodes-base.slack","position":[920,260],"parameters":{"text":":new: A new product has been added! :new:","channel":"newproducts","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#66FF00","fields":{"item":[{"short":false,"title":"Name","value":"={{$json[\"name\"]}}"},{"short":true,"title":"Price","value":"={{$json[\"regular_price\"]}}"},{"short":true,"title":"Sale Price","value":"={{$json[\"sale_price\"]}}"},{"short":false,"title":"Link","value":"={{$json[\"permalink\"]}}"}]},"footer":"=Added: {{$json[\"date_created\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"21","name":"FVF bot"}},"typeVersion":1},{"name":"On product creation","type":"n8n-nodes-base.wooCommerceTrigger","position":[460,300],"webhookId":"267c4855-6227-4d33-867e-74600097473e","parameters":{"event":"product.created"},"credentials":{"wooCommerceApi":{"id":"20","name":"WooCommerce account FVF"}},"typeVersion":1}],"active":true,"settings":{},"connections":{"On product creation":{"main":[[{"node":"If URL has /product/","type":"main","index":0}]]},"If URL has /product/":{"main":[[{"node":"Send message to slack","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"n8n-team"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1460,"name":"Notify on Slack when refund is registered in WooCommerce","views":85,"recentViews":9541,"totalViews":85,"createdAt":"2022-02-17T15:02:58.662Z","description":"This workflow uses a WooCommerce trigger that will run when an order has been updated and the status is refunded.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.","workflow":{"id":82,"name":"New WooCommerce refund to Slack","nodes":[{"name":"Order Updated","type":"n8n-nodes-base.wooCommerceTrigger","position":[320,500],"webhookId":"f7736be3-e978-4a17-b936-7ce9f8ccdb72","parameters":{"event":"order.updated"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1},{"name":"If Refund and Over 100","type":"n8n-nodes-base.if","position":[540,500],"parameters":{"conditions":{"number":[{"value1":"={{$json[\"total\"]}}","value2":100,"operation":"largerEqual"}],"string":[{"value1":"={{$json[\"status\"]}}","value2":"refunded"}]}},"typeVersion":1},{"name":"Send to Slack","type":"n8n-nodes-base.slack","position":[780,480],"parameters":{"text":":x: A refund has been issued :x:","channel":"woo-commerce","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#FF0000","fields":{"item":[{"short":true,"title":"Order ID","value":"={{$json[\"id\"]}}"},{"short":true,"title":"Status","value":"={{$json[\"status\"]}}"},{"short":true,"title":"Total","value":"={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}"}]},"footer":"=*Order updated:* {{$json[\"date_modified\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"53","name":"Slack Access Token"}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"Order Updated":{"main":[[{"node":"If Refund and Over 100","type":"main","index":0}]]},"If Refund and Over 100":{"main":[[{"node":"Send to Slack","type":"main","index":0}],[]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"},{"id":8,"name":"Finance & Accounting"}],"image":[]}],"image":[]}} diff --git a/cypress/fixtures/Floating_Nodes.json b/cypress/fixtures/Floating_Nodes.json new file mode 100644 index 0000000000000..2ffc1b3fde777 --- /dev/null +++ b/cypress/fixtures/Floating_Nodes.json @@ -0,0 +1,176 @@ +{ + "name": "Floating Nodes", + "nodes": [ + { + "parameters": {}, + "id": "d0eda550-2526-42a1-aa19-dee411c8acf9", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 700, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "30412165-1229-4b21-9890-05bfbd9952ab", + "name": "Node 1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 920, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "201cc8fc-3124-47a3-bc08-b3917c1ddcd9", + "name": "Node 2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1100, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "a29802bb-a284-495d-9917-6c6e42fef01e", + "name": "Node 3", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1280, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "a95a72b3-8b39-44e2-a05b-d8d677741c80", + "name": "Node 4", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1440, + 560 + ] + }, + { + "parameters": {}, + "id": "4674f10d-6144-4a17-bbbb-350c3974438e", + "name": "Chain", + "type": "@n8n/n8n-nodes-langchain.chainLlm", + "typeVersion": 1, + "position": [ + 1580, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "58e12ea5-bd3e-4abf-abec-fcfb5c0a7955", + "name": "Model", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1, + "position": [ + 1600, + 740 + ] + } + ], + "pinData": {}, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Node 1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Node 1": { + "main": [ + [ + { + "node": "Node 2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Node 3": { + "main": [ + [ + { + "node": "Node 4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Node 2": { + "main": [ + [ + { + "node": "Node 3", + "type": "main", + "index": 0 + } + ] + ] + }, + "Chain": { + "main": [ + [] + ] + }, + "Model": { + "ai_languageModel": [ + [ + { + "node": "Chain", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Node 4": { + "main": [ + [ + { + "node": "Chain", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "2730d156-a98a-4ac8-b481-5c16361fdba2", + "id": "6bzXMGxHuxeEaqsA", + "meta": { + "instanceId": "1838be0fa0389fbaf5e2e4aaedab4ddc79abc4175b433401abb22a281001b853" + }, + "tags": [] +} diff --git a/cypress/fixtures/Lots_of_nodes.json b/cypress/fixtures/Lots_of_nodes.json new file mode 100644 index 0000000000000..7b3ad507c8f18 --- /dev/null +++ b/cypress/fixtures/Lots_of_nodes.json @@ -0,0 +1,1051 @@ +{ + "name": "Lots of nodes", + "nodes": [ + { + "parameters": {}, + "id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 860, + 740 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "dce967a7-8c5e-43cc-ba2b-e0fb0c9cf14c", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1080, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "df7a719e-b25a-43e3-b941-7091a7d9a1a8", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1300, + 740 + ] + }, + { + "parameters": {}, + "id": "32968b79-6a8b-43ed-b884-eb906b597661", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 1520, + 740 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "e9a72745-6dbb-4be1-b286-aaa679b95e36", + "name": "Code1", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1820, + 80 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "f831d21b-c3a9-4bd8-9fc3-6daef12bd43f", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2040, + 80 + ] + }, + { + "parameters": {}, + "id": "6e6b2a4f-9e61-4245-8502-ca01e851fcbe", + "name": "IF1", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 2260, + 80 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "535b9786-ead9-44f9-bff2-ef2e019a4cf9", + "name": "Code3", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2560, + -260 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "6a181d75-f2f2-4ad1-be3c-81ebe077ccc8", + "name": "Edit Fields3", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2780, + -260 + ] + }, + { + "parameters": {}, + "id": "4b45828e-4e2b-4046-b9ae-24b373a81863", + "name": "IF7", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3000, + -260 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "059534cb-820c-4fb7-933c-eeed2ae74f1c", + "name": "Code7", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + -400 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "4f5c0d94-b69d-4ad3-aa8f-f1dd5824ec4a", + "name": "Edit Fields7", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + -400 + ] + }, + { + "parameters": {}, + "id": "cd74f840-7b0f-425d-8ecd-e247a7d8abf5", + "name": "IF8", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + -400 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "3c97fd14-9c23-45e2-a1ac-934d743e9a01", + "name": "Code8", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + -80 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "9e7bd7e9-5142-4751-b132-735d27007d82", + "name": "Edit Fields8", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + -80 + ] + }, + { + "parameters": {}, + "id": "8d3968b6-16d4-4e03-9026-eeaf70b17805", + "name": "IF9", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + -80 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "141edef3-ea0f-4e90-9b6a-09f5d5551195", + "name": "Code4", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2560, + 440 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b5b93cd7-9448-4290-91b7-c3c8429925fd", + "name": "Edit Fields4", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2780, + 440 + ] + }, + { + "parameters": {}, + "id": "79d2c11c-0378-4ff5-b166-ae1bf773f53a", + "name": "IF14", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3000, + 440 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "8483e962-24e7-4495-9c8e-481481ebe897", + "name": "Code13", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 300 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "74dfb8f9-6d14-493e-97d5-729e1f44856b", + "name": "Edit Fields13", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 300 + ] + }, + { + "parameters": {}, + "id": "0c2e8e54-958d-4932-91b5-b23979460c97", + "name": "IF15", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 300 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "bfed29c6-c453-4850-8acf-7aa11b1d0d8e", + "name": "Code14", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 620 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d8415057-c597-40a9-95f6-bafbe3fafac0", + "name": "Edit Fields14", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 620 + ] + }, + { + "parameters": {}, + "id": "51ed9040-bb6c-4f77-9740-74b54ac56a00", + "name": "IF16", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 620 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "5864e701-eb16-4412-ae8b-be1f2a1f16a5", + "name": "Code2", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1820, + 1480 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "4b7de291-f1c7-4ae8-a545-81aaa2ebd1fb", + "name": "Edit Fields2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2040, + 1480 + ] + }, + { + "parameters": {}, + "id": "328aa16f-82ed-465e-b548-9436f21eb519", + "name": "IF2", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 2260, + 1480 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "90aaf0b0-57b6-4a08-b000-abb2956ba640", + "name": "Code5", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2560, + 1140 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "7d327c87-da3b-4f4b-9f9a-51c9c622990d", + "name": "Edit Fields5", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2780, + 1140 + ] + }, + { + "parameters": {}, + "id": "fa2a3b1b-53de-454e-a16d-e2bf62cb05ec", + "name": "IF21", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3000, + 1140 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "8efaa5a3-982e-41b4-af6e-28e35c64093d", + "name": "Code19", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 1000 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "987e27fd-778a-4562-85a9-369b1ec232de", + "name": "Edit Fields19", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 1000 + ] + }, + { + "parameters": {}, + "id": "b3f4e9b3-9995-4019-9b0f-dadd64e036b4", + "name": "IF22", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 1000 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "681c1b30-063d-4c1e-b550-942a9dd3eb9a", + "name": "Code20", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 1320 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "024770b6-7bf4-44f6-9675-d4f7dc73d6ac", + "name": "Edit Fields20", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 1320 + ] + }, + { + "parameters": {}, + "id": "24699015-3ccf-4ffa-b52f-8ba4c4853963", + "name": "IF23", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 1320 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "f4b2d116-2fda-4a3a-9509-0e8c64e7796e", + "name": "Code6", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2560, + 1840 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "535e5b12-6743-4c01-9fc5-e27b10421423", + "name": "Edit Fields6", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 2780, + 1840 + ] + }, + { + "parameters": {}, + "id": "3dcbecdf-686b-445f-9c77-2902d0dc1f56", + "name": "IF28", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3000, + 1840 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "7223c6ef-664b-426a-8d08-eca1b34e6b23", + "name": "Code25", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 1700 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "496414a6-384a-4f94-97ec-d2e5ad646f82", + "name": "Edit Fields25", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 1700 + ] + }, + { + "parameters": {}, + "id": "82f9562d-e4a8-49f3-924d-983effb4b6c6", + "name": "IF29", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 1700 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "c91d4bc5-3c60-4c22-aa31-44e84e0816ec", + "name": "Code26", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3260, + 2020 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "49b61f23-bf3f-474d-8bba-a3b7de6f6441", + "name": "Edit Fields26", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 3480, + 2020 + ] + }, + { + "parameters": {}, + "id": "1cad6ae3-1064-4f30-a9ec-502891868332", + "name": "IF30", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 3700, + 2020 + ] + } + ], + "pinData": {}, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "Code1", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code1": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "IF1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code3": { + "main": [ + [ + { + "node": "Edit Fields3", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields3": { + "main": [ + [ + { + "node": "IF7", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF1": { + "main": [ + [ + { + "node": "Code3", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code4", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF7": { + "main": [ + [ + { + "node": "Code7", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code7": { + "main": [ + [ + { + "node": "Edit Fields7", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields7": { + "main": [ + [ + { + "node": "IF8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code8": { + "main": [ + [ + { + "node": "Edit Fields8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields8": { + "main": [ + [ + { + "node": "IF9", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code4": { + "main": [ + [ + { + "node": "Edit Fields4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields4": { + "main": [ + [ + { + "node": "IF14", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF14": { + "main": [ + [ + { + "node": "Code13", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code14", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code13": { + "main": [ + [ + { + "node": "Edit Fields13", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields13": { + "main": [ + [ + { + "node": "IF15", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code14": { + "main": [ + [ + { + "node": "Edit Fields14", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields14": { + "main": [ + [ + { + "node": "IF16", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code2": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "IF2", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF2": { + "main": [ + [ + { + "node": "Code5", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code5": { + "main": [ + [ + { + "node": "Edit Fields5", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields5": { + "main": [ + [ + { + "node": "IF21", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF21": { + "main": [ + [ + { + "node": "Code19", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code20", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code19": { + "main": [ + [ + { + "node": "Edit Fields19", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields19": { + "main": [ + [ + { + "node": "IF22", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code20": { + "main": [ + [ + { + "node": "Edit Fields20", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields20": { + "main": [ + [ + { + "node": "IF23", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code6": { + "main": [ + [ + { + "node": "Edit Fields6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields6": { + "main": [ + [ + { + "node": "IF28", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF28": { + "main": [ + [ + { + "node": "Code25", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code26", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code25": { + "main": [ + [ + { + "node": "Edit Fields25", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields25": { + "main": [ + [ + { + "node": "IF29", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code26": { + "main": [ + [ + { + "node": "Edit Fields26", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields26": { + "main": [ + [ + { + "node": "IF30", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "d38289e0-49d3-4e1d-8e4b-46e4eb85a2c9", + "id": "iKlx4AGIjCNJSu9M", + "meta": { + "instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7" + }, + "tags": [] +} diff --git a/cypress/fixtures/Multiple_trigger_node_rerun.json b/cypress/fixtures/Multiple_trigger_node_rerun.json new file mode 100644 index 0000000000000..39d231a894c5c --- /dev/null +++ b/cypress/fixtures/Multiple_trigger_node_rerun.json @@ -0,0 +1,133 @@ +{ + "name": "Multiple trigger node rerun", + "nodes": [ + { + "parameters": {}, + "id": "5ae8991f-08a2-4b27-b61c-85e3b8a83693", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 460, + 460 + ] + }, + { + "parameters": { + "url": "https://random-data-api.com/api/v2/users?size=5", + "options": {} + }, + "id": "22511d75-ab54-49e1-b8af-08b8b3372373", + "name": "fetch 5 random users", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 680, + 460 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.first_name_reversed = item.json = {\n firstName: item.json.first_name,\n firstnNameReversed: item.json.first_name_BUG.split(\"\").reverse().join(\"\")\n };\n}\n\nreturn $input.all();" + }, + "id": "4b66b15a-1685-46c1-a5e3-ebf8cdb11d21", + "name": "do something with them", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 900, + 460 + ] + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "* * * * *" + } + ] + } + }, + "id": "d763fc3b-6c4a-4d39-8857-ff84f7b6dc83", + "name": "Schedule Trigger", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.1, + "position": [ + 460, + 660 + ] + } + ], + "pinData": { + "Schedule Trigger": [ + { + "json": { + "timestamp": "2024-01-29T13:45:00.006+01:00", + "Readable date": "January 29th 2024, 1:45:00 pm", + "Readable time": "1:45:00 pm", + "Day of week": "Monday", + "Year": "2024", + "Month": "January", + "Day of month": "29", + "Hour": "13", + "Minute": "45", + "Second": "00", + "Timezone": "CET +01:00" + } + } + ], + "When clicking \"Test workflow\"": [ + { + "json": {} + } + ] + }, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "fetch 5 random users", + "type": "main", + "index": 0 + } + ] + ] + }, + "fetch 5 random users": { + "main": [ + [ + { + "node": "do something with them", + "type": "main", + "index": 0 + } + ] + ] + }, + "Schedule Trigger": { + "main": [ + [ + { + "node": "fetch 5 random users", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "b9a6c3b0-15cd-4359-a92e-12a691a36b7b", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7" + }, + "id": "PymcwIrbqgNh3O0K", + "tags": [] +} \ No newline at end of file diff --git a/cypress/fixtures/NDV-test-switch_reorder.json b/cypress/fixtures/NDV-test-switch_reorder.json new file mode 100644 index 0000000000000..cf970434f3efb --- /dev/null +++ b/cypress/fixtures/NDV-test-switch_reorder.json @@ -0,0 +1,235 @@ +{ + "name": "switch reorder", + "nodes": [ + { + "parameters": {}, + "id": "b3f0815d-b733-413f-ab3f-74e48277bd3a", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -20, + 620 + ] + }, + { + "parameters": {}, + "id": "fbc5b12a-6165-4cab-80a1-9fd6e4fbe39f", + "name": "One", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 620, + 720 + ] + }, + { + "parameters": { + "duplicateItem": true, + "duplicateCount": 1, + "assignments": { + "assignments": [ + { + "id": "ec6c1d1d-a17a-4537-8135-d474df7fded1", + "name": "entry", + "value": "first", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "8c5a72a5-17ef-40e0-8477-764f24770174", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 160, + 740 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "d8ec7c46-d02f-4bf5-931e-5ec2fb8bea22", + "name": "entry", + "value": "zero", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "bc3fb81d-2ddf-4b28-a93d-762a48e8fd6b", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 160, + 500 + ] + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "leftValue": "={{ $json.entry }}", + "rightValue": "first", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "1" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "ffa570ef-fc16-49ec-87be-56159f14a44b", + "leftValue": "={{ $json.entry }}", + "rightValue": "=second", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "2" + } + ] + }, + "options": {} + }, + "id": "296ba553-c6c5-4c84-89fb-9056b24bab30", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 3, + "position": [ + 360, + 740 + ] + }, + { + "parameters": {}, + "id": "da787dd6-8e85-4dd5-8326-198705b4ae4b", + "name": "Merge", + "type": "n8n-nodes-base.merge", + "typeVersion": 2.1, + "position": [ + 880, + 520 + ] + } + ], + "pinData": { + "Edit Fields": [ + { + "json": { + "entry": "first" + } + }, + { + "json": { + "entry": "second" + } + } + ] + }, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + }, + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "One": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 1 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "One", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "ce5db792-5e38-4d54-895b-88d85f2545d0", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd" + }, + "id": "uMpL0bN7t1NYZDJS", + "tags": [] +} diff --git a/cypress/fixtures/Node_IO_filter.json b/cypress/fixtures/Node_IO_filter.json new file mode 100644 index 0000000000000..61be5d58d8016 --- /dev/null +++ b/cypress/fixtures/Node_IO_filter.json @@ -0,0 +1,653 @@ +{ + "name": "Node IO filter", + "nodes": [ + { + "parameters": {}, + "id": "46770685-44d1-4aad-9107-1d790cf26b50", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 840, + 180 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "480e3832-2ce4-4118-9f7b-a8aed6017174", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1080, + 180 + ] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.profile.name }}", + "operation": "contains", + "value2": "an" + } + ] + } + }, + "id": "4773d460-6ed9-49e1-a688-7e480f0fbacf", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 1300, + 180 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d17dffe6-e29c-4c1a-8b4c-9e374dcd70ea", + "name": "True", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1560, + 60 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "893d6e79-feb4-4752-a6f8-e2e5f5163787", + "name": "False", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1560, + 240 + ] + } + ], + "pinData": { + "When clicking \"Test workflow\"": [ + { + "json": { + "id": "654cfa05fa51480dcb543b1a", + "email": "reese_hahn@kidgrease.coach", + "username": "reese94", + "profile": { + "name": "Reese Hahn", + "company": "Kidgrease", + "dob": "1994-06-18", + "address": "3 Richmond Street, Norfolk, Delaware", + "location": { + "lat": 22.507436, + "long": -50.812775 + }, + "about": "Cupidatat voluptate reprehenderit commodo mollit tempor sint id. Id exercitation id eiusmod dolore non non anim voluptate anim eu consectetur." + }, + "apiKey": "a18592bf-1147-4b61-a70f-2ab90b60bb6e", + "roles": [ + "guest" + ], + "createdAt": "2010-10-04T09:57:59.240Z", + "updatedAt": "2010-10-05T09:57:59.240Z" + } + }, + { + "json": { + "id": "654cfa055bea471bc4853158", + "email": "jeanne_boyd@hatology.gratis", + "username": "jeanne91", + "profile": { + "name": "Jeanne Boyd", + "company": "Hatology", + "dob": "1991-02-21", + "address": "81 Kingsway Place, Blairstown, Vermont", + "location": { + "lat": -57.665234, + "long": -41.301893 + }, + "about": "Proident pariatur non consequat cupidatat Lorem nisi est consequat dolor id eiusmod id. Amet culpa ex Lorem nostrud labore laboris culpa mollit dolor culpa ut." + }, + "apiKey": "8a6056a6-0197-4920-858d-cb26f8c8a1e2", + "roles": [ + "owner", + "admin" + ], + "createdAt": "2011-11-06T09:05:41.945Z", + "updatedAt": "2011-11-07T09:05:41.945Z" + } + }, + { + "json": { + "id": "654cfa05b012921c060dc5a5", + "email": "roslyn_underwood@portico.melbourne", + "username": "roslyn88", + "profile": { + "name": "Roslyn Underwood", + "company": "Portico", + "dob": "1988-04-30", + "address": "24 Schenck Street, Drytown, New Jersey", + "location": { + "lat": 11.797141, + "long": 10.751804 + }, + "about": "Duis excepteur minim consequat exercitation. Laboris occaecat cupidatat aliqua consequat occaecat." + }, + "apiKey": "72d629f3-d613-4fd0-bbfe-3f67c8ad7af2", + "roles": [ + "member", + "owner" + ], + "createdAt": "2012-11-17T22:09:10.911Z", + "updatedAt": "2012-11-18T22:09:10.911Z" + } + }, + { + "json": { + "id": "654cfa05df7b35968507efe6", + "email": "combs_hardy@acrodance.domains", + "username": "combs91", + "profile": { + "name": "Combs Hardy", + "company": "Acrodance", + "dob": "1991-04-30", + "address": "58 Pineapple Street, Falconaire, New Mexico", + "location": { + "lat": -62.922443, + "long": -159.493799 + }, + "about": "Magna qui minim velit magna est eiusmod aliquip elit aliquip excepteur. Laborum labore do ut et ut in incididunt do elit nostrud." + }, + "apiKey": "d9807b9e-aee9-486d-9826-4e6c166bfbe4", + "roles": [ + "owner", + "member" + ], + "createdAt": "2014-04-13T13:02:09.319Z", + "updatedAt": "2014-04-14T13:02:09.319Z" + } + }, + { + "json": { + "id": "654cfa05f2d4a0508a7c59c4", + "email": "terrell_peters@vantage.international", + "username": "terrell94", + "profile": { + "name": "Terrell Peters", + "company": "Vantage", + "dob": "1994-01-31", + "address": "10 Lafayette Walk, Vincent, Virginia", + "location": { + "lat": -62.267913, + "long": 29.682121 + }, + "about": "Eiusmod fugiat nulla ea tempor incididunt nulla nulla consectetur officia incididunt proident sint. Sunt duis non excepteur non." + }, + "apiKey": "20b96df1-d882-4dea-a505-84d7ff296a6e", + "roles": [ + "admin", + "guest" + ], + "createdAt": "2010-12-09T08:24:56.517Z", + "updatedAt": "2010-12-10T08:24:56.517Z" + } + }, + { + "json": { + "id": "654cfa0599fbabf3a05c7b14", + "email": "shari_winters@powernet.supply", + "username": "shari93", + "profile": { + "name": "Shari Winters", + "company": "Powernet", + "dob": "1993-03-10", + "address": "89 Aviation Road, Leyner, Indiana", + "location": { + "lat": 40.404704, + "long": -141.216235 + }, + "about": "Occaecat sit laboris elit laboris do anim culpa dolore exercitation enim. Non veniam sint exercitation irure." + }, + "apiKey": "2b869ce9-3431-4edb-944d-9d9336b1eb4a", + "roles": [ + "guest", + "admin" + ], + "createdAt": "2014-10-15T15:56:55.873Z", + "updatedAt": "2014-10-16T15:56:55.873Z" + } + }, + { + "json": { + "id": "654cfa050df18b4798ec95be", + "email": "rena_beasley@bitrex.ma", + "username": "rena90", + "profile": { + "name": "Rena Beasley", + "company": "Bitrex", + "dob": "1990-01-09", + "address": "78 Forbell Street, Homeland, Maine", + "location": { + "lat": 46.047548, + "long": 4.128049 + }, + "about": "Lorem aliqua veniam duis ut cillum ad sunt mollit incididunt elit. Ipsum incididunt et magna incididunt quis duis amet duis occaecat laborum nulla et commodo nisi." + }, + "apiKey": "17e350f8-1020-4344-bbd7-ceb62cd44edb", + "roles": [ + "member", + "owner" + ], + "createdAt": "2010-04-22T13:35:24.838Z", + "updatedAt": "2010-04-23T13:35:24.838Z" + } + }, + { + "json": { + "id": "654cfa0595243d2b7b1ea22a", + "email": "sally_gentry@eventex.maif", + "username": "sally93", + "profile": { + "name": "Sally Gentry", + "company": "Eventex", + "dob": "1993-04-03", + "address": "54 Plaza Street, Greenbackville, North Carolina", + "location": { + "lat": -20.529121, + "long": 73.533118 + }, + "about": "Laborum sit exercitation sint laborum. Fugiat sit ipsum ullamco sint do dolore in sunt incididunt adipisicing magna ullamco aute." + }, + "apiKey": "746b6ab3-c63f-44df-bb99-9de48f8e43c4", + "roles": [ + "owner", + "guest" + ], + "createdAt": "2011-09-18T13:18:49.655Z", + "updatedAt": "2011-09-19T13:18:49.655Z" + } + }, + { + "json": { + "id": "654cfa05cdea66c87bb01439", + "email": "battle_duran@jasper.property", + "username": "battle88", + "profile": { + "name": "Battle Duran", + "company": "Jasper", + "dob": "1988-11-04", + "address": "34 Amherst Street, Corriganville, Nevada", + "location": { + "lat": 74.391489, + "long": -98.421464 + }, + "about": "Nostrud occaecat laborum aliquip sint est minim id aliquip adipisicing dolor. Aute velit amet officia anim sint anim aliquip." + }, + "apiKey": "b22a3ddd-d540-4df0-9ce5-e837bc6a6a10", + "roles": [ + "member" + ], + "createdAt": "2012-08-31T19:14:37.463Z", + "updatedAt": "2012-09-01T19:14:37.463Z" + } + }, + { + "json": { + "id": "654cfa05e9c13e25d41d4135", + "email": "petty_moore@neurocell.shriram", + "username": "petty91", + "profile": { + "name": "Petty Moore", + "company": "Neurocell", + "dob": "1991-03-10", + "address": "78 Interborough Parkway, Grill, Texas", + "location": { + "lat": -79.817761, + "long": -36.728201 + }, + "about": "Dolor occaecat anim est Lorem culpa fugiat id aliqua sint. Sit nisi do exercitation do voluptate exercitation in." + }, + "apiKey": "4b341cfb-a83c-4f2a-9f4d-11cd747b8783", + "roles": [ + "admin" + ], + "createdAt": "2012-01-02T21:28:22.431Z", + "updatedAt": "2012-01-03T21:28:22.431Z" + } + }, + { + "json": { + "id": "654cfa052890c7b4d510d3d4", + "email": "matilda_kelley@senmei.in", + "username": "matilda93", + "profile": { + "name": "Matilda Kelley", + "company": "Senmei", + "dob": "1993-02-04", + "address": "29 Stuart Street, Henrietta, New York", + "location": { + "lat": 40.788206, + "long": -135.821558 + }, + "about": "Dolor veniam ex ullamco deserunt reprehenderit nostrud sunt culpa cupidatat qui labore deserunt. In ad anim laboris amet labore duis consequat nostrud eiusmod." + }, + "apiKey": "dcf40383-a00a-43ef-8bd0-4af7e70413bd", + "roles": [ + "owner", + "guest" + ], + "createdAt": "2014-03-28T22:07:39.636Z", + "updatedAt": "2014-03-29T22:07:39.636Z" + } + }, + { + "json": { + "id": "654cfa05af129db469473bf1", + "email": "savannah_hardin@exoblue.kn", + "username": "savannah89", + "profile": { + "name": "Savannah Hardin", + "company": "Exoblue", + "dob": "1989-07-01", + "address": "44 Navy Walk, Fresno, Kentucky", + "location": { + "lat": 75.679679, + "long": -58.534947 + }, + "about": "Id eiusmod eu elit consequat quis anim veniam officia anim ipsum. Sunt ex sit ipsum id est eu." + }, + "apiKey": "98d6abb7-e4aa-4b3b-8958-ff3c4d672f1d", + "roles": [ + "guest", + "member" + ], + "createdAt": "2011-04-15T00:55:02.325Z", + "updatedAt": "2011-04-16T00:55:02.325Z" + } + }, + { + "json": { + "id": "654cfa055dfa731b01573a67", + "email": "abbott_gallegos@katakana.dad", + "username": "abbott91", + "profile": { + "name": "Abbott Gallegos", + "company": "Katakana", + "dob": "1991-03-04", + "address": "85 Indiana Place, Forestburg, Michigan", + "location": { + "lat": -5.417414, + "long": -4.557904 + }, + "about": "Adipisicing amet ullamco aliquip velit nostrud qui non pariatur Lorem. Culpa ut deserunt esse quis magna." + }, + "apiKey": "3cf92c24-6193-4cc9-85fc-78e4ad9d6e13", + "roles": [ + "guest", + "owner" + ], + "createdAt": "2011-06-01T16:38:39.316Z", + "updatedAt": "2011-06-02T16:38:39.316Z" + } + }, + { + "json": { + "id": "654cfa05386de2e6d75c1694", + "email": "short_brennan@hyplex.tc", + "username": "short92", + "profile": { + "name": "Short Brennan", + "company": "Hyplex", + "dob": "1992-04-19", + "address": "21 Irving Place, Hinsdale, Northern Mariana Islands", + "location": { + "lat": 57.340225, + "long": -7.021582 + }, + "about": "Mollit dolor dolore deserunt anim minim adipisicing eiusmod velit tempor id veniam cupidatat. Magna veniam consequat incididunt ut quis culpa excepteur tempor eiusmod consectetur excepteur." + }, + "apiKey": "07bf533d-4a31-4e78-9d6e-d46160479069", + "roles": [ + "admin", + "member" + ], + "createdAt": "2014-03-10T19:25:02.217Z", + "updatedAt": "2014-03-11T19:25:02.217Z" + } + }, + { + "json": { + "id": "654cfa05fd2a878d43bb45cd", + "email": "bowers_cooke@iplax.ci", + "username": "bowers92", + "profile": { + "name": "Bowers Cooke", + "company": "Iplax", + "dob": "1992-07-05", + "address": "83 Greenpoint Avenue, Marion, Georgia", + "location": { + "lat": 64.261022, + "long": -58.493714 + }, + "about": "Deserunt ipsum fugiat tempor sunt eu ea laboris ad magna ex laborum laboris. Ullamco nostrud qui exercitation aute consectetur irure." + }, + "apiKey": "a3ecc58b-f292-4de1-b6e5-014345a76a7a", + "roles": [ + "member", + "owner" + ], + "createdAt": "2010-06-20T16:34:56.467Z", + "updatedAt": "2010-06-21T16:34:56.467Z" + } + }, + { + "json": { + "id": "654cfa05a6de547367990f9c", + "email": "tara_rutledge@escenta.lc", + "username": "tara90", + "profile": { + "name": "Tara Rutledge", + "company": "Escenta", + "dob": "1990-08-11", + "address": "25 Butler Place, Frierson, Missouri", + "location": { + "lat": -32.176783, + "long": 67.345415 + }, + "about": "Aute sunt laborum anim ex non pariatur nisi minim tempor adipisicing. Excepteur irure non amet eiusmod et excepteur." + }, + "apiKey": "22da9647-a7b7-4815-91bb-d5101fc90e55", + "roles": [ + "member" + ], + "createdAt": "2013-09-06T21:41:53.287Z", + "updatedAt": "2013-09-07T21:41:53.287Z" + } + }, + { + "json": { + "id": "654cfa053778601ad57f22cd", + "email": "elva_chapman@bytrex.gg", + "username": "elva90", + "profile": { + "name": "Elva Chapman", + "company": "Bytrex", + "dob": "1990-05-31", + "address": "4 Royce Place, Advance, New Hampshire", + "location": { + "lat": -28.393464, + "long": -28.622091 + }, + "about": "Est sit deserunt Lorem amet voluptate elit reprehenderit occaecat est eiusmod eu reprehenderit laborum. Pariatur magna occaecat et excepteur est excepteur consectetur ad nulla." + }, + "apiKey": "4d242fa4-ac69-42f1-8f12-ec19d9c6d632", + "roles": [ + "owner", + "admin" + ], + "createdAt": "2011-04-05T04:04:31.524Z", + "updatedAt": "2011-04-06T04:04:31.524Z" + } + }, + { + "json": { + "id": "654cfa054c6abbc57efcb100", + "email": "pitts_meyer@unisure.tui", + "username": "pitts93", + "profile": { + "name": "Pitts Meyer", + "company": "Unisure", + "dob": "1993-06-12", + "address": "47 Columbus Place, Cade, Alaska", + "location": { + "lat": 56.723675, + "long": 158.093389 + }, + "about": "Non ea pariatur excepteur nostrud elit quis qui. Dolore aute velit ipsum officia ea pariatur incididunt non elit tempor duis consequat." + }, + "apiKey": "82a88344-d289-447c-81b5-1ae10cd1994b", + "roles": [ + "guest", + "admin" + ], + "createdAt": "2014-05-15T06:38:59.269Z", + "updatedAt": "2014-05-16T06:38:59.269Z" + } + }, + { + "json": { + "id": "654cfa0527e7ce14e421d9cd", + "email": "delia_figueroa@overplex.um", + "username": "delia89", + "profile": { + "name": "Delia Figueroa", + "company": "Overplex", + "dob": "1989-04-22", + "address": "12 Nova Court, Taft, Ohio", + "location": { + "lat": -32.990583, + "long": -4.598863 + }, + "about": "Cupidatat fugiat veniam eu proident excepteur deserunt ad esse fugiat deserunt. Non velit cillum velit veniam ex minim eiusmod tempor excepteur voluptate adipisicing nostrud." + }, + "apiKey": "b3a7747b-24a0-4039-8a21-56e83441a660", + "roles": [ + "admin", + "guest" + ], + "createdAt": "2014-09-20T03:40:10.190Z", + "updatedAt": "2014-09-21T03:40:10.190Z" + } + }, + { + "json": { + "id": "654cfa05cf60000cbca6dca4", + "email": "kristina_fulton@portaline.engineer", + "username": "kristina88", + "profile": { + "name": "Kristina Fulton", + "company": "Portaline", + "dob": "1988-07-25", + "address": "50 Laurel Avenue, Greenwich, Palau", + "location": { + "lat": 44.118984, + "long": 41.518949 + }, + "about": "Id incididunt officia exercitation ipsum id cillum consectetur. Veniam enim voluptate ut proident ex." + }, + "apiKey": "c106dbf0-bfc0-461d-b1d7-1840fe8e1cbc", + "roles": [ + "admin", + "member" + ], + "createdAt": "2010-04-10T08:06:27.028Z", + "updatedAt": "2010-04-11T08:06:27.028Z" + } + }, + { + "json": { + "id": "654cfa0501fe5691d620f570", + "email": "gould_noel@gonkle.gmx", + "username": "gould91", + "profile": { + "name": "Gould Noel", + "company": "Gonkle", + "dob": "1991-10-08", + "address": "33 Crooke Avenue, Idamay, Oklahoma", + "location": { + "lat": -11.398731, + "long": 34.706948 + }, + "about": "Veniam esse tempor aute quis mollit consequat Lorem. Nostrud ea dolore laboris Lorem elit est do nisi Lorem minim reprehenderit culpa." + }, + "apiKey": "1089783d-32ae-4102-8ac5-1e7f6cebe3c1", + "roles": [ + "guest", + "admin" + ], + "createdAt": "2011-12-30T20:24:19.620Z", + "updatedAt": "2011-12-31T20:24:19.620Z" + } + } + ] + }, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "True", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "False", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "9812dda2-cc1b-4458-97d8-21ccb18c90d1", + "id": "WNq486x7DpV1MPRH", + "meta": { + "instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7" + }, + "tags": [] +} diff --git a/cypress/fixtures/Onboarding_workflow.json b/cypress/fixtures/Onboarding_workflow.json new file mode 100644 index 0000000000000..8e292b59a3a7a --- /dev/null +++ b/cypress/fixtures/Onboarding_workflow.json @@ -0,0 +1,1020 @@ +{ + "name": "DEMO: Create a new record in Google Sheets when something happens in Hubspot", + "nodes": [ + { + "parameters": { + "eventsUi": { + "eventValues": [ + {} + ] + }, + "additionalFields": {} + }, + "id": "78395fdf-2e8b-4064-a102-c1c0335e0d94", + "name": "HubSpot Trigger", + "type": "n8n-nodes-base.hubspotTrigger", + "typeVersion": 1, + "position": [ + 580, + 320 + ], + "webhookId": "25833e56-c646-4af0-8bbe-2eea8bda4c00", + "notesInFlow": true, + "notes": "On Contact Created" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json['identity-profiles'][0].identities[0].value }}", + "operation": "contains", + "value2": "@gmail" + } + ] + } + }, + "id": "3888d918-c140-47a1-8024-d50fddb3f8f0", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 820, + 320 + ], + "notesInFlow": true, + "notes": "Is Gmail Email?" + }, + { + "parameters": {}, + "id": "416a8876-f496-499c-a089-aad243daabc6", + "name": "Is Gmail, Don't Add to Sheet", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1140, + 240 + ] + }, + { + "parameters": { + "content": "## Demo: Creating Google Sheets records when something happens in HubSpot\nThis workflow runs each time a new Contact is added in HubSpot. It filters out Contacts with Gmail email addresses then pushes the remaining new Contacts to [this Google Sheet](https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit?usp=sharing).", + "height": 160.1450000000002, + "width": 480.31999999999596 + }, + "id": "cf69cda9-ba96-468f-990c-6c3ad5242053", + "name": "Sticky Note1", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 520, + 100 + ] + }, + { + "parameters": { + "operation": "append", + "documentId": { + "__rl": true, + "value": "1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A", + "mode": "list", + "cachedResultName": "New HubSpot Contacts", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": "gid=0", + "mode": "list", + "cachedResultName": "New Contacts", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit#gid=0" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "Name": "={{ $json.properties.num_unique_conversion_events.versions[0]['source-type'] }}", + "Email": "={{ $json.properties.num_unique_conversion_events.versions[0]['source-label'] }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "Name", + "displayName": "Name", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "Email", + "displayName": "Email", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "Sync timestamp", + "displayName": "Sync timestamp", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + } + ] + }, + "options": {} + }, + "id": "1e4084bd-b7fb-41f1-a340-1414ef134468", + "name": "Google Sheets", + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4, + "position": [ + 1140, + 440 + ], + "notesInFlow": true, + "credentials": { + "googleSheetsOAuth2Api": { + "id": "FrRoXgPJOrFwkeN4", + "name": "Replace me with your own Sheets credential" + } + }, + "notes": "Append new contact to sheet" + } + ], + "pinData": { + "HubSpot Trigger": [ + { + "json": { + "vid": 51, + "canonical-vid": 51, + "merged-vids": [], + "portal-id": 8924380, + "is-contact": true, + "properties": { + "hs_latest_source_data_2": { + "value": "sample-contact", + "versions": [ + { + "value": "sample-contact", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_latest_source_data_1": { + "value": "API", + "versions": [ + { + "value": "API", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_is_unworked": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045782, + "selected": false + } + ] + }, + "firstname": { + "value": "Brian", + "versions": [ + { + "value": "Brian", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "associatedcompanyid": { + "value": "4931550080", + "versions": [ + { + "value": "4931550080", + "source-type": "CALCULATED", + "source-id": "RollupProperties", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827057264, + "selected": false + } + ] + }, + "city": { + "value": "Cambridge", + "versions": [ + { + "value": "Cambridge", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "num_unique_conversion_events": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_latest_source": { + "value": "OFFLINE", + "versions": [ + { + "value": "OFFLINE", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_pipeline": { + "value": "contacts-lifecycle-pipeline", + "versions": [ + { + "value": "contacts-lifecycle-pipeline", + "source-type": "MIGRATION", + "source-id": "BackfillHsPipelineForContacts", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1628846625829, + "selected": false + } + ] + }, + "hs_analytics_revenue": { + "value": "0.0", + "versions": [ + { + "value": "0.0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_num_broadcast_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "createdate": { + "value": "1606827045698", + "versions": [ + { + "value": "1606827045698", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045698, + "selected": false + } + ] + }, + "hs_analytics_num_visits": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_linkedin_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_marketable_until_renewal": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "hs_marketable_status": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "hs_analytics_source": { + "value": "OFFLINE", + "versions": [ + { + "value": "OFFLINE", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_email_domain": { + "value": "hubspot.com", + "versions": [ + { + "value": "hubspot.com", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_analytics_num_page_views": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_marketable_reason_id": { + "value": "Sample Contact", + "versions": [ + { + "value": "Sample Contact", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "company": { + "value": "HubSpot", + "versions": [ + { + "value": "HubSpot", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "state": { + "value": "MA", + "versions": [ + { + "value": "MA", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "email": { + "value": "bh@hubspot.com", + "versions": [ + { + "value": "bh@hubspot.com", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_latest_source_timestamp": { + "value": "1606827045720", + "versions": [ + { + "value": "1606827045720", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1651750884919, + "selected": false + } + ] + }, + "website": { + "value": "http://www.HubSpot.com", + "versions": [ + { + "value": "http://www.HubSpot.com", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_marketable_reason_type": { + "value": "SAMPLE_CONTACT", + "versions": [ + { + "value": "SAMPLE_CONTACT", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "jobtitle": { + "value": "CEO", + "versions": [ + { + "value": "CEO", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "lastmodifieddate": { + "value": "1651750891986", + "versions": [ + { + "value": "1651750891986", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1651750891986, + "selected": false + }, + { + "value": "1639158305597", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158305597, + "selected": false + }, + { + "value": "1628846625829", + "source-type": "CALCULATED", + "source-id": "BackfillHsPipelineForContacts", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1628846625829, + "selected": false + } + ] + }, + "hs_analytics_first_timestamp": { + "value": "1606827045444", + "versions": [ + { + "value": "1606827045444", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_google_plus_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_analytics_average_page_views": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "lastname": { + "value": "Halligan (Sample Contact)", + "versions": [ + { + "value": "Halligan (Sample Contact)", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_all_contact_vids": { + "value": "51", + "versions": [ + { + "value": "51", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "twitterhandle": { + "value": "bhalligan", + "versions": [ + { + "value": "bhalligan", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_social_facebook_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_is_contact": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045698, + "selected": false + } + ] + }, + "num_conversion_events": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "twitterprofilephoto": { + "value": "https://pbs.twimg.com/profile_images/3491742741/212e42c07d3348251da10872e85aa6b0.jpeg", + "versions": [ + { + "value": "https://pbs.twimg.com/profile_images/3491742741/212e42c07d3348251da10872e85aa6b0.jpeg", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_object_id": { + "value": "51", + "versions": [ + { + "value": "51", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_analytics_num_event_completions": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_twitter_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_analytics_source_data_2": { + "value": "sample-contact", + "versions": [ + { + "value": "sample-contact", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827054762, + "selected": false + } + ] + }, + "hs_lifecyclestage_lead_date": { + "value": "1606827045444", + "versions": [ + { + "value": "1606827045444", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_analytics_source_data_1": { + "value": "API", + "versions": [ + { + "value": "API", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827054762, + "selected": false + } + ] + }, + "lifecyclestage": { + "value": "lead", + "versions": [ + { + "value": "lead", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + } + }, + "form-submissions": [], + "list-memberships": [], + "identity-profiles": [ + { + "vid": 51, + "saved-at-timestamp": 1606827045720, + "deleted-changed-timestamp": 0, + "identities": [ + { + "type": "EMAIL", + "value": "bh@hubspot.com", + "timestamp": 1606827045444, + "is-primary": true + }, + { + "type": "LEAD_GUID", + "value": "d3749acc-06e1-4511-84fd-7b0d847f6eff", + "timestamp": 1606827045717 + } + ] + } + ], + "merge-audits": [], + "associated-company": { + "company-id": 4931550080, + "portal-id": 8924380, + "properties": { + "country": { + "value": "United States" + }, + "city": { + "value": "Cambridge" + }, + "num_associated_contacts": { + "value": "2" + }, + "timezone": { + "value": "America/New_York" + }, + "facebook_company_page": { + "value": "https://www.facebook.com/hubspot" + }, + "createdate": { + "value": "1606827053844" + }, + "description": { + "value": "HubSpot is an American developer and marketer of software products for inbound marketing, sales, and customer service." + }, + "hs_analytics_latest_source_data_2": { + "value": "sample-contact" + }, + "hs_analytics_latest_source_data_1": { + "value": "API" + }, + "hs_num_blockers": { + "value": "0" + }, + "industry": { + "value": "COMPUTER_SOFTWARE" + }, + "total_money_raised": { + "value": "100.5M" + }, + "web_technologies": { + "value": "unbounce;instagram;app_nexus;piwik;google_analytics;mixpanel;google_tag_manager;facebook_advertiser;salesforce;cloud_flare;dstillery;twitter_button;hubspot;vidyard;facebook_connect;crazy_egg;amazon__cloudfront;wistia;optimizely" + }, + "numberofemployees": { + "value": "5000" + }, + "hs_analytics_num_visits": { + "value": "0" + }, + "linkedin_company_page": { + "value": "https://www.linkedin.com/company/hubspot" + }, + "hs_analytics_latest_source_timestamp": { + "value": "1606827045720" + }, + "hs_analytics_source": { + "value": "OFFLINE" + }, + "annualrevenue": { + "value": "250000000" + }, + "founded_year": { + "value": "2006" + }, + "hs_annual_revenue_currency_code": { + "value": "USD" + }, + "hs_analytics_num_page_views": { + "value": "0" + }, + "state": { + "value": "MA" + }, + "linkedinbio": { + "value": "HubSpot is an American developer and marketer of software products for inbound marketing, sales, and customer service." + }, + "hs_num_open_deals": { + "value": "0" + }, + "zip": { + "value": "02141" + }, + "website": { + "value": "hubspot.com" + }, + "address": { + "value": "25 First Street" + }, + "hs_analytics_first_timestamp": { + "value": "1606827045444" + }, + "first_contact_createdate": { + "value": "1606827045444" + }, + "twitterhandle": { + "value": "HubSpot" + }, + "hs_target_account_probability": { + "value": "0.49565839767456055" + }, + "hs_lastmodifieddate": { + "value": "1653392172246" + }, + "hs_num_decision_makers": { + "value": "0" + }, + "phone": { + "value": "+1 888-482-7768" + }, + "domain": { + "value": "hubspot.com" + }, + "hs_num_child_companies": { + "value": "0" + }, + "hs_num_contacts_with_buying_roles": { + "value": "0" + }, + "hs_object_id": { + "value": "4931550080" + }, + "is_public": { + "value": "true" + }, + "name": { + "value": "HubSpot, Inc." + }, + "hs_analytics_source_data_2": { + "value": "sample-contact" + }, + "hs_analytics_latest_source": { + "value": "OFFLINE" + }, + "hs_analytics_source_data_1": { + "value": "API" + } + } + } + } + } + ] + }, + "connections": { + "HubSpot Trigger": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "Is Gmail, Don't Add to Sheet", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Google Sheets", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "4748dbbc-75dd-400e-98d1-41bbd82c7208", + "id": "cGYp8fpTdh3LAgP5", + "meta": { + "instanceId": "dbd43d88d26a9e30d8aadc002c9e77f1400c683dd34efe3778d43d27250dde50" + }, + "tags": [ + { + "createdAt": "2023-09-21T09:36:34.726Z", + "updatedAt": "2023-09-21T09:36:52.231Z", + "id": "vh6ctEIEfFztmSF2", + "name": "release-template-version" + } + ] +} \ No newline at end of file diff --git a/cypress/fixtures/Plan_data_opt_in_trial.json b/cypress/fixtures/Plan_data_opt_in_trial.json new file mode 100644 index 0000000000000..7a805708c651d --- /dev/null +++ b/cypress/fixtures/Plan_data_opt_in_trial.json @@ -0,0 +1,28 @@ +{ + "id": 200, + "planId": 1, + "pruneExecutionsInterval": 168, + "monthlyExecutionsLimit": 1000, + "activeWorkflowsLimit": 20, + "credentialsLimit": 100, + "supportTier": "community", + "displayName": "Trial", + "enabledFeatures": ["userManagement", "advancedExecutionFilters", "sharing"], + "licenseFeatures": { + "feat:sharing": true, + "feat:advancedExecutionFilters": true, + "quota:users": -1, + "quota:maxVariables": -1, + "feat:variables": true + }, + "metadata": { + "version": "v1", + "group": "trial", + "slug": "trial-2", + "trial": { + "length": 14, + "gracePeriod": 3 + } + }, + "expirationDate": "2023-08-30T15:47:27.611Z" +} diff --git a/cypress/fixtures/Schedule_pinned.json b/cypress/fixtures/Schedule_pinned.json new file mode 100644 index 0000000000000..e27623b6fae61 --- /dev/null +++ b/cypress/fixtures/Schedule_pinned.json @@ -0,0 +1,313 @@ +{ + "name": "Schedule + pinned", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + {} + ] + } + }, + "id": "66358c29-b263-43dd-be25-3b068b0a88eb", + "name": "Schedule Trigger", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.1, + "position": [ + 660, + 340 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "6d903354-4e59-4032-81fe-426a5d6ec33c", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 860, + 240 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d8a1e9cf-81d3-400f-97d4-ad6167e7b236", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 860, + 440 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "bdc41148-067e-4649-8f21-5707b128d877", + "name": "Edit Fields2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1080, + 440 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d5a4337f-a6b3-4b51-9b02-e668593d9ae8", + "name": "Edit Fields3", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1300, + 440 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "fbc23f60-e7f6-4423-9329-33b0e4809a9a", + "name": "Edit Fields4", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1500, + 440 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "eaee47b0-94ec-4137-bfeb-a6c1a2c63f81", + "name": "Edit Fields5", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1080, + 240 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "eabb6308-21e9-4e59-8f74-9220a03c3186", + "name": "Edit Fields6", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1300, + 240 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "8812a45b-5545-4080-aad8-8e9f7b17ecd7", + "name": "Edit Fields7", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1500, + 240 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d5ea3c5b-0b3e-4514-93e1-9c88563bab5c", + "name": "Edit Fields9", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1700, + 240 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "7af34474-5cd0-40b1-abea-850858e3b495", + "name": "Edit Fields10", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1700, + 440 + ] + } + ], + "pinData": { + "Schedule Trigger": [ + { + "json": { + "name": "First item", + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "code": 2 + } + } + ], + "Edit Fields7": [ + { + "json": { + "name": "First item", + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "code": 2 + } + } + ], + "Edit Fields2": [ + { + "json": { + "name": "First item", + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "code": 2 + } + } + ] + }, + "connections": { + "Schedule Trigger": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + }, + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "Edit Fields3", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields3": { + "main": [ + [ + { + "node": "Edit Fields4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields5": { + "main": [ + [ + { + "node": "Edit Fields6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields6": { + "main": [ + [ + { + "node": "Edit Fields7", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Edit Fields5", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields7": { + "main": [ + [ + { + "node": "Edit Fields9", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields4": { + "main": [ + [ + { + "node": "Edit Fields10", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "9b6c68c0-f94f-45bc-a604-bf97d17a47ac", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7" + }, + "id": "nWzcnYUb3AVaZpHG", + "tags": [] +} diff --git a/cypress/fixtures/Settings_user_activation_modal_enabled.json b/cypress/fixtures/Settings_user_activation_modal_enabled.json deleted file mode 100644 index 8ce6a5fca67e2..0000000000000 --- a/cypress/fixtures/Settings_user_activation_modal_enabled.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "data": { - "endpointWebhook": "webhook", - "endpointWebhookTest": "webhook-test", - "saveDataErrorExecution": "all", - "saveDataSuccessExecution": "all", - "saveManualExecutions": false, - "executionTimeout": -1, - "maxExecutionTimeout": 3600, - "workflowCallerPolicyDefaultOption": "workflowsFromSameOwner", - "timezone": "America/New_York", - "urlBaseWebhook": "http://localhost:5678/", - "urlBaseEditor": "http://localhost:5678", - "versionCli": "0.221.2", - "oauthCallbackUrls": { - "oauth1": "http://localhost:5678/rest/oauth1-credential/callback", - "oauth2": "http://localhost:5678/rest/oauth2-credential/callback" - }, - "versionNotifications": { - "enabled": true, - "endpoint": "https://api.n8n.io/api/versions/", - "infoUrl": "https://docs.n8n.io/release-notes/" - }, - "instanceId": "c229842c6d1e217486d04caf7223758e08385156ca87a58286c850760c7161f4", - "telemetry": { - "enabled": true - }, - "posthog": { - "enabled": false, - "apiHost": "https://ph.n8n.io", - "apiKey": "phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo", - "autocapture": false, - "disableSessionRecording": true, - "debug": false - }, - "personalizationSurveyEnabled": false, - "userActivationSurveyEnabled": true, - "defaultLocale": "en", - "userManagement": { - "enabled": true, - "showSetupOnFirstLoad": false, - "smtpSetup": false - }, - "sso": { - "saml": { - "loginEnabled": false, - "loginLabel": "" - }, - "ldap": { - "loginEnabled": false, - "loginLabel": "" - } - }, - "publicApi": { - "enabled": false, - "latestVersion": 1, - "path": "api", - "swaggerUi": { - "enabled": true - } - }, - "workflowTagsDisabled": false, - "logLevel": "info", - "hiringBannerEnabled": true, - "templates": { - "enabled": true, - "host": "https://api.n8n.io/api/" - }, - "onboardingCallPromptEnabled": true, - "executionMode": "regular", - "pushBackend": "sse", - "communityNodesEnabled": true, - "deployment": { - "type": "default" - }, - "isNpmAvailable": false, - "allowedModules": {}, - "enterprise": { - "sharing": true, - "ldap": true, - "saml": false, - "logStreaming": false, - "advancedExecutionFilters": false - }, - "hideUsagePage": false, - "license": { - "environment": "production" - } - } -} diff --git a/cypress/fixtures/Suggested_Templates.json b/cypress/fixtures/Suggested_Templates.json new file mode 100644 index 0000000000000..3f69c4b1a90bb --- /dev/null +++ b/cypress/fixtures/Suggested_Templates.json @@ -0,0 +1,655 @@ +{ + "sections": [ + { + "name": "Lead enrichment", + "description": "Explore curated lead enrichment workflows or start fresh with a blank canvas", + "workflows": [ + { + "title": "Score new leads with AI from Facebook Lead Ads with AI and get notifications for high scores on Slack", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 24, + "icon": "fa:code-branch", + "defaults": { + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "displayName": "Merge" + } + ] + }, + { + "title": "Verify the email address every time a contact is created in HubSpot", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "name": "n8n-nodes-base.function", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Function", + "typeVersion": 1 + }, + { + "id": 24, + "icon": "fa:code-branch", + "name": "n8n-nodes-base.merge", + "defaults": { + "name": "Merge", + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Merge", + "typeVersion": 2 + } + ] + }, + { + "title": "Enrich leads from HubSpot with company information via OpenAi", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "displayName": "Function" + } + ] + }, + { + "title": "Score new lead submissions from Facebook Lead Ads with AI and notify me on Slack when it is a high score lead", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "displayName": "Function" + }, + { + "id": 24, + "icon": "fa:code-branch", + "defaults": { + "name": "Merge", + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "displayName": "Merge" + } + ] + } + ] + } + ] +} diff --git a/cypress/fixtures/Test_Template_1.json b/cypress/fixtures/Test_Template_1.json new file mode 100644 index 0000000000000..f15970677e1d5 --- /dev/null +++ b/cypress/fixtures/Test_Template_1.json @@ -0,0 +1,177 @@ +{ + "workflow": { + "id": 1205, + "name": "Promote new Shopify products on Twitter and Telegram", + "views": 478, + "recentViews": 9880, + "totalViews": 478, + "createdAt": "2021-08-24T10:40:50.007Z", + "description": "This workflow automatically promotes your new Shopify products on Twitter and Telegram. This workflow is also featured in the blog post [*6 e-commerce workflows to power up your Shopify store*](https://n8n.io/blog/no-code-ecommerce-workflow-automations/#promote-your-new-products-on-social-media).\n\n## Prerequisites\n\n- A Shopify account and [credentials](https://docs.n8n.io/integrations/credentials/shopify/)\n- A Twitter account and [credentials](https://docs.n8n.io/integrations/credentials/twitter/)\n- A Telegram account and [credentials](https://docs.n8n.io/integrations/credentials/telegram/) for the channel you want to send messages to.\n\n## Nodes\n\n- [Shopify Trigger node](https://docs.n8n.io/integrations/trigger-nodes/n8n-nodes-base.shopifytrigger/) triggers the workflow when you create a new product in Shopify.\n- [Twitter node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.twitter/) posts a tweet with the text \"Hey there, my design is now on a new product! Visit my {shop name} to get this cool {product title} (and check out more {product type})\".\n- [Telegram node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.telegram/) posts a message with the same text as above in a Telegram channel.", + "workflow": { + "nodes": [ + { + "name": "Twitter", + "type": "n8n-nodes-base.twitter", + "position": [ + 720, + -220 + ], + "parameters": { + "text": "=Hey there, my design is now on a new product ✨\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}}) 🛍️", + "additionalFields": {} + }, + "credentials": { + "twitterOAuth1Api": "twitter" + }, + "typeVersion": 1 + }, + { + "name": "Telegram", + "type": "n8n-nodes-base.telegram", + "position": [ + 720, + -20 + ], + "parameters": { + "text": "=Hey there, my design is now on a new product!\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}})", + "chatId": "123456", + "additionalFields": {} + }, + "credentials": { + "telegramApi": "telegram_habot" + }, + "typeVersion": 1 + }, + { + "name": "product created", + "type": "n8n-nodes-base.shopifyTrigger", + "position": [ + 540, + -110 + ], + "webhookId": "2a7e0e50-8f09-4a2b-bf54-a849a6ac4fe0", + "parameters": { + "topic": "products/create" + }, + "credentials": { + "shopifyApi": "shopify_nodeqa" + }, + "typeVersion": 1 + } + ], + "connections": { + "product created": { + "main": [ + [ + { + "node": "Twitter", + "type": "main", + "index": 0 + }, + { + "node": "Telegram", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 3, + "nodeTypes": { + "n8n-nodes-base.twitter": { + "count": 1 + }, + "n8n-nodes-base.telegram": { + "count": 1 + }, + "n8n-nodes-base.shopifyTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "lorenanda" + }, + "nodes": [ + { + "id": 49, + "icon": "file:telegram.svg", + "name": "n8n-nodes-base.telegram", + "defaults": { + "name": "Telegram" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Telegram", + "typeVersion": 1 + }, + { + "id": 107, + "icon": "file:shopify.svg", + "name": "n8n-nodes-base.shopifyTrigger", + "defaults": { + "name": "Shopify Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Shopify Trigger", + "typeVersion": 1 + }, + { + "id": 325, + "icon": "file:x.svg", + "name": "n8n-nodes-base.twitter", + "defaults": { + "name": "X" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K" + }, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + } + ], + "displayName": "X (Formerly Twitter)", + "typeVersion": 2 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + }, + { + "id": 19, + "name": "Marketing & Growth" + } + ], + "image": [ + { + "id": 527, + "url": "https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/89a078b208fe4c6181902608b1cd1332.png" + } + ] + } +} diff --git a/cypress/fixtures/Test_Template_2.json b/cypress/fixtures/Test_Template_2.json new file mode 100644 index 0000000000000..29001a78e36e5 --- /dev/null +++ b/cypress/fixtures/Test_Template_2.json @@ -0,0 +1,182 @@ +{ + "workflow": { + "id": 1344, + "name": "Save email attachments to Nextcloud", + "views": 650, + "recentViews": 9887, + "totalViews": 650, + "createdAt": "2021-11-29T13:59:16.771Z", + "description": "This workflow will take all emails you put into a certain folder, upload any attachements to Nextcloud, and mark the emails as read (configurable).\n\nAttachements will be saved with automatically generated filenames:\n`2021-01-01_From-Sender-Name_Filename-of-attachement.pdf`\n\nInstructions:\n1. **Allow lodash to be used in n8n** (or rewrite the code...)\n `NODE_FUNCTION_ALLOW_EXTERNAL=lodash` (environment variable)\n2. Import workflow\n3. Set credentials for Email & Nextcloud nodes\n4. Configure to use correct folder / custom filters\n5. Activate\n\nCustom filter examples:\n- Only unread emails:\n `Custom Email Config` = `[\"UNSEEN\"]`\n- Filter emails by 'to' address:\n `Custom Email Config` = `[[\"TO\", \"example+invoices@posteo.de\"]]`", + "workflow": { + "nodes": [ + { + "name": "IMAP Email", + "type": "n8n-nodes-base.emailReadImap", + "position": [ + 240, + 420 + ], + "parameters": { + "format": "resolved", + "mailbox": "Invoices", + "options": { + "customEmailConfig": "[\"ALL\"]" + } + }, + "typeVersion": 1 + }, + { + "name": "Nextcloud", + "type": "n8n-nodes-base.nextCloud", + "position": [ + 940, + 420 + ], + "parameters": { + "path": "=Documents/Invoices/{{$json[\"date\"]}}_{{$json[\"from\"]}}_{{$binary.file.fileName}}", + "binaryDataUpload": true, + "binaryPropertyName": "file" + }, + "typeVersion": 1 + }, + { + "name": "Map each attachment", + "type": "n8n-nodes-base.function", + "position": [ + 620, + 420 + ], + "parameters": { + "functionCode": "const _ = require('lodash')\n\nconst sanitize = str => _.chain(str)\n .replace(/[^A-Za-z0-9&.-]/g, '-') // sanitise via whitelist of characters\n .replace(/-(?=-)/g, '') // remove repeated dashes - https://regexr.com/6ag8h\n .trim('-') // trim any leading/trailing dashes\n .truncate({\n length: 60,\n omission: '-' // when the string ends with '-', you'll know it was truncated\n })\n .value()\n\nconst result = _.flatMap(items.map(item => {\n //console.log({item})\n\n // Maps each attachment to a separate item\n return _.values(item.binary).map(file => {\n console.log(\"Saving attachement:\", file.fileName, 'from:', ...item.json.from.value)\n \n // sanitize filename but exclude extension\n const filename_parts = file.fileName.split('.')\n const ext = _.slice(filename_parts, filename_parts.length-1)\n const filename_main = _.join(_.dropRight(filename_parts), '.')\n file.fileName = sanitize(filename_main) + '.' + ext\n \n return {\n json: {\n from: sanitize(item.json.from.value[0].name),\n date: sanitize(new Date(item.json.date).toISOString().split(\"T\")[0]) // get date part \"2020-01-01\"\n }, \n binary: { file }\n }\n })\n}))\n\n//console.log(result)\nreturn result" + }, + "typeVersion": 1 + } + ], + "connections": { + "IMAP Email": { + "main": [ + [ + { + "node": "Map each attachment", + "type": "main", + "index": 0 + } + ] + ] + }, + "Map each attachment": { + "main": [ + [ + { + "node": "Nextcloud", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "lastUpdatedBy": 11, + "workflowInfo": { + "nodeCount": 3, + "nodeTypes": { + "n8n-nodes-base.function": { + "count": 1 + }, + "n8n-nodes-base.nextCloud": { + "count": 1 + }, + "n8n-nodes-base.emailReadImap": { + "count": 1 + } + } + }, + "user": { + "username": "tennox" + }, + "nodes": [ + { + "id": 10, + "icon": "fa:inbox", + "name": "n8n-nodes-base.emailReadImap", + "defaults": { + "name": "Email Trigger (IMAP)", + "color": "#44AA22" + }, + "iconData": { + "icon": "inbox", + "type": "icon" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Email Trigger (IMAP)", + "typeVersion": 2 + }, + { + "id": 14, + "icon": "fa:code", + "name": "n8n-nodes-base.function", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Function", + "typeVersion": 1 + }, + { + "id": 25, + "icon": "file:nextcloud.svg", + "name": "n8n-nodes-base.nextCloud", + "defaults": { + "name": "Nextcloud" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzYgNTEiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI0EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9IkEiIG92ZXJmbG93PSJ2aXNpYmxlIj48cGF0aCBkPSJNMzcuNTMzIDBjLTcuNzcgMC0xNC4zNTUgNS4yNjgtMTYuMzk2IDEyLjM3OS0xLjc3OC0zLjgxOS01LjU5Ny02LjQ1My0xMC4wNzUtNi40NTNDNS4wMDQgNS45MjYgMCAxMC45MzEgMCAxNy4wNTRhMTEuMTYgMTEuMTYgMCAwIDAgMTEuMTI4IDExLjEyOGM0LjQxMiAwIDguMjk3LTIuNjM0IDEwLjA3NS02LjQ1M2ExNi45OSAxNi45OSAwIDAgMCAxNi4zMyAxMi4zNzljNy43MDQgMCAxNC4yODktNS4yMDIgMTYuMzk2LTEyLjI0OCAxLjc3OCAzLjY4NyA1LjU5NyA2LjI1NiA5Ljk0MyA2LjI1NkExMS4xNiAxMS4xNiAwIDAgMCA3NSAxNi45ODljMC02LjEyNC01LjAwNC0xMS4wNjItMTEuMTI4LTExLjA2Mi00LjM0NiAwLTguMTY1IDIuNTY4LTkuOTQzIDYuMjU2QzUxLjgyMiA1LjIwMiA0NS4zMDMgMCAzNy41MzMgMHptMCA2LjUxOWExMC40OCAxMC40OCAwIDAgMSAxMC41MzUgMTAuNTM2QTEwLjQ4IDEwLjQ4IDAgMCAxIDM3LjUzMyAyNy41OWExMC40OCAxMC40OCAwIDAgMS0xMC41MzYtMTAuNTM1QTEwLjQ4IDEwLjQ4IDAgMCAxIDM3LjUzMyA2LjUxOXptLTI2LjQwNSA1LjkyNmE0LjU4IDQuNTggMCAwIDEgNC42MDkgNC42MDkgNC41OCA0LjU4IDAgMCAxLTQuNjA5IDQuNjA5IDQuNTggNC41OCAwIDAgMS00LjYwOS00LjYwOSA0LjU4IDQuNTggMCAwIDEgNC42MDktNC42MDl6bTUyLjc0NCAwYTQuNTggNC41OCAwIDAgMSA0LjYwOSA0LjYwOSA0LjYwOSA0LjYwOSAwIDEgMS05LjIxOCAwYy4wNjYtMi41NjggMi4wNDEtNC42MDkgNC42MDktNC42MDl6TTE5LjE3NiA0MS45NTdjMS44MjcgMCAyLjg1IDEuMzAxIDIuODUgMy4yNTIgMCAuMTg2LS4xNTUuMzQxLS4zNDEuMzQxSDE2Ljc2Yy4wMzEgMS43MzQgMS4yMzkgMi43MjYgMi42MzMgMi43MjZhMi44OSAyLjg5IDAgMCAwIDEuNzk2LS42MTljLjE4Ni0uMTI0LjM0MS0uMDkzLjQzNC4wOTNsLjA5My4xNTVjLjA5My4xNTUuMDYyLjMxLS4wOTMuNDM0YTMuODQgMy44NCAwIDAgMS0yLjI2MS43NDNjLTIuMDEzIDAtMy41NjItMS40NTYtMy41NjItMy41NjIuMDMxLTIuMjMgMS41MTgtMy41NjIgMy4zNzYtMy41NjJ6bTEuODg5IDIuOTExYy0uMDYyLTEuNDI1LS45MjktMi4xMzctMS45Mi0yLjEzNy0xLjE0NiAwLTIuMTM3Ljc0My0yLjM1NCAyLjEzN2g0LjI3NHptMTAuMjUzLTEuOTJ2LS43NzQtMS42MTFjMC0uMjE3LjEyNC0uMzQxLjM0MS0uMzQxaC4yNDhjLjIxNyAwIC4zMS4xMjQuMzEuMzQxdjEuNjExaDEuMzk0Yy4yMTcgMCAuMzQxLjEyNC4zNDEuMzQxdi4wOTNjMCAuMjE3LS4xMjQuMzEtLjM0MS4zMWgtMS4zOTR2My40MDdjMCAxLjU4Ljk2IDEuNzY2IDEuNDg3IDEuNzk2LjI3OS4wMzEuMzcyLjA5My4zNzIuMzQxdi4xODZjMCAuMjE3LS4wOTMuMzEtLjM3Mi4zMS0xLjQ4NyAwLTIuMzg1LS44OTgtMi4zODUtMi41MDl2LTMuNXptNy4wOTMtLjk5MWMxLjE3NyAwIDEuOTIuNDk2IDIuMjYxLjc3NC4xNTUuMTI0LjE4Ni4yNzkuMDMxLjQ2NWwtLjA5My4xNTVjLS4xMjQuMTg2LS4yNzkuMTg2LS40NjUuMDYyLS4zMS0uMjE3LS44OTgtLjYxOS0xLjcwMy0uNjE5LTEuNDg3IDAtMi42NjQgMS4xMTUtMi42NjQgMi43NTcgMCAxLjYxMSAxLjE3NyAyLjcyNiAyLjY2NCAyLjcyNi45NiAwIDEuNjExLS40MzQgMS45Mi0uNzEyLjE4Ni0uMTI0LjMxLS4wOTMuNDM0LjA5M2wuMDkzLjEyNGMuMDkzLjE4Ni4wNjIuMzEtLjA5My40NjVhMy44MSAzLjgxIDAgMCAxLTIuNDE2Ljg2N2MtMi4wMTMgMC0zLjU2Mi0xLjQ1Ni0zLjU2Mi0zLjU2Mi4wMzEtMi4xMDYgMS41OC0zLjU5MyAzLjU5My0zLjU5M3ptNC4xMTktMi4xOTljMC0uMjE3LS4xMjQtLjM0MS4wOTMtLjM0MWguMjQ4Yy4yMTcgMCAuNTU4LjEyNC41NTguMzQxdjcuNDAzYzAgLjg2Ny40MDMuOTYuNzEyLjk5MS4xNTUgMCAuMjc5LjA5My4yNzkuMzF2LjIxN2MwIC4yMTctLjA5My4zNDEtLjM0MS4zNDEtLjU1NyAwLTEuNTQ5LS4xODYtMS41NDktMS42NzN2LTcuNTg5em02LjM1IDIuMTk5YzEuOTgyIDAgMy41OTMgMS41MTggMy41OTMgMy41MzEgMCAyLjA0NC0xLjYxMSAzLjU5My0zLjU5MyAzLjU5M3MtMy41OTMtMS41NDktMy41OTMtMy41OTNjMC0yLjAxMyAxLjYxMS0zLjUzMSAzLjU5My0zLjUzMXptMCA2LjMxOWMxLjQ1NiAwIDIuNjMzLTEuMTc3IDIuNjMzLTIuNzg4IDAtMS41NDktMS4xNzctMi42OTUtMi42MzMtMi42OTVhMi42NyAyLjY3IDAgMCAwLTIuNjY0IDIuNjk1Yy4wMzEgMS41OCAxLjIwOCAyLjc4OCAyLjY2NCAyLjc4OHptMTUuNDU2LTYuMzE5YTIuNDUgMi40NSAwIDAgMSAyLjIzIDEuMzYzaC4wMzFzLS4wMzEtLjIxNy0uMDMxLS41MjZ2LTMuMDY2YzAtLjIxNy0uMDkzLS4zNDEuMTI0LS4zNDFoLjI0OGMuMjE3IDAgLjU1OC4xMjQuNTU4LjM0MXY4LjgyN2MwIC4yMTctLjA5My4zNDEtLjMxLjM0MWgtLjIxN2MtLjIxNyAwLS4zNDEtLjA5My0uMzQxLS4zMXYtLjUyN2MwLS4yNDguMDYyLS40MzQuMDYyLS40MzRoLS4wMzFzLS41ODkgMS40MjUtMi4zNTQgMS40MjVjLTEuODI3IDAtMi45NzMtMS40NTYtMi45NzMtMy41NjItLjA2Mi0yLjEwNiAxLjIwOC0zLjUzMSAzLjAwNC0zLjUzMWgwem0uMDMxIDYuMzE5YzEuMTQ2IDAgMi4xOTktLjgwNSAyLjE5OS0yLjc1NyAwLTEuMzk0LS43MTItMi43MjYtMi4xNjgtMi43MjYtMS4yMDggMC0yLjE5OS45OTEtMi4xOTkgMi43MjYuMDMxIDEuNjczLjg5OCAyLjc1NyAyLjE2OCAyLjc1N3ptLTU2LjU1OC42NWguMjQ4Yy4yMTcgMCAuMzQxLS4xMjQuMzQxLS4zNDF2LTYuNjI4YzAtMS4wNTMgMS4xNDYtMS43OTYgMi40NDctMS43OTZzMi40NDcuNzQzIDIuNDQ3IDEuNzk2djYuNjU5YzAgLjIxNy4xMjQuMzQxLjM0MS4zNDFoLjI0OGMuMjE3IDAgLjMxLS4xMjQuMzEtLjM0MXYtNi43MjFjMC0xLjc2Ni0xLjc2NS0yLjYzMy0zLjM3Ni0yLjYzM2gwIDAgMCAwYy0xLjU0OSAwLTMuMzE0Ljg2Ny0zLjMxNCAyLjYzM3Y2LjY5YzAgLjIxNy4wOTMuMzQxLjMxLjM0MXptNTEuNjk1LTYuODE0aC0uMjQ4Yy0uMjE3IDAtLjM0MS4xMjQtLjM0MS4zNDF2My43NDhjMCAxLjA1My0uNjgxIDIuMDEzLTIuMDEzIDIuMDEzLTEuMzAxIDAtMi4wMTMtLjk2LTIuMDEzLTIuMDEzdi0zLjc0OGMwLS4yMTctLjEyNC0uMzQxLS4zNDEtLjM0MUg1NC4zYy0uMjE3IDAtLjMxLjEyNC0uMzEuMzQxdjMuOTk2YzAgMS43NjUgMS4zMDEgMi42MzMgMi45MTIgMi42MzNoMCAwIDAgMGMxLjYxMSAwIDIuOTExLS44NjcgMi45MTEtMi42MzN2LTMuOTk2Yy4wMzEtLjIxNy0uMDkzLS4zNDEtLjMxLS4zNDFoMHptLTMwLjY2NC0uMDMxYy0uMDYyIDAtLjE1NS4wNjItLjIxNy4xNTVsLTEuMjM5IDEuNDg3LS45MjkgMS4xMTUtMS40MjUtMS43MDQtLjc3NC0uOTI5Yy0uMDYyLS4wOTMtLjE1NS0uMTI0LS4yMTctLjEyNHMtLjE1NS4wMzEtLjI0OC4wOTNsLS4xODYuMTU1Yy0uMTU1LjEyNC0uMTU1LjI3OS0uMDMxLjQ2NWwxLjIzOSAxLjQ4NyAxLjA1MyAxLjIzOS0xLjUxOCAxLjgyN2gwbC0uNzc0LjkyOWMtLjEyNC4xNTUtLjEyNC4zNDEuMDMxLjQ5NmwuMTg2LjE1NWMuMTU1LjEyNC4zMS4wOTMuNDY1LS4wNjJsMS4yMzktMS40ODcuOTI5LTEuMTE1IDEuNDI1IDEuNzA0aDBsLjc3NC45MjljLjEyNC4xNTUuMzEuMTg2LjQ2NS4wMzFsLjE4Ni0uMTU1Yy4xNTUtLjEyNC4xNTUtLjI3OS4wMzEtLjQ2NWwtMS4yMzktMS40ODctMS4wNTMtMS4yMzkgMS41MTgtMS44MjdoMGwuNzc0LS45MjljLjEyNC0uMTU1LjEyNC0uMzQxLS4wMzEtLjQ5NWwtLjE4Ni0uMTg2Yy0uMDkzLS4wNjItLjE1NS0uMDkzLS4yNDgtLjA2MmgweiIgZmlsbD0iIzAwODJjOSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiLz48L3N5bWJvbD48L3N2Zz4=" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Nextcloud", + "typeVersion": 1 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + }, + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "image": [] + } +} diff --git a/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json b/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json new file mode 100644 index 0000000000000..f740bc1df63d1 --- /dev/null +++ b/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json @@ -0,0 +1,160 @@ +{ + "name": "Test Workflow pairedItem incomplete manual bug", + "nodes": [ + { + "parameters": {}, + "id": "f26332f3-c61a-4843-94bd-64a73ad161ff", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 860, + 340 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "bd522794-d056-48b8-9204-26f7d68288d9", + "name": "test", + "value": "a", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "fae0c907-e2bf-4ecf-82be-f9caa209f925", + "name": "Init Data", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 1080, + 340 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "8db21b4b-1675-4e63-b092-7fcc45a86547", + "leftValue": "={{ $json.test }}", + "rightValue": "b", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "f7990edd-2c0f-42e6-b3ce-74c7df02b6a4", + "name": "If", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 1300, + 340 + ] + }, + { + "parameters": {}, + "id": "850d48f5-0689-4cab-b30c-30e179577c82", + "name": "NoOp1", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1540, + 200 + ] + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "bd522794-d056-48b8-9204-26f7d68288d9", + "name": "test2", + "value": "={{ $('Init Data').item.json.test }}", + "type": "string" + } + ] + }, + "options": {} + }, + "id": "91d93c3a-a557-465e-812b-266d6277b279", + "name": "Test Expression", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 1540, + 440 + ] + } + ], + "pinData": {}, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Init Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Init Data": { + "main": [ + [ + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "NoOp1", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Test Expression", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "765a6d9b-d667-4a59-9bd7-b0bc2627b008", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff" + }, + "id": "qnGQYw8TD58xs214", + "tags": [] +} diff --git a/cypress/fixtures/Test_ado_1338.json b/cypress/fixtures/Test_ado_1338.json new file mode 100644 index 0000000000000..0609ae6e55f75 --- /dev/null +++ b/cypress/fixtures/Test_ado_1338.json @@ -0,0 +1,632 @@ +{ + "meta": { + "instanceId": "2be09fdcb9594c0827fd4cee80f7e590c93297d9217685f34c2250fe3144ef0c" + }, + "nodes": [ + { + "parameters": {}, + "id": "6dace68e-0727-472d-a212-00863acb64d6", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -340, + 660 + ] + }, + { + "parameters": { + "resource": "user", + "operation": "getAll", + "flag": "new", + "returnAll": true, + "options": {} + }, + "id": "2465a943-0d2c-480d-a98a-a67e92151367", + "name": "Discourse", + "type": "n8n-nodes-base.discourse", + "typeVersion": 1, + "position": [ + -120, + 660 + ] + }, + { + "parameters": { + "conditions": { + "dateTime": [ + { + "value1": "={{ $json.user.created_at }}", + "operation": "before", + "value2": "={{ $today.minus(6,\"day\") }}" + } + ], + "number": [ + { + "value1": "={{ $json.user.accepted_answers }}", + "operation": "larger", + "value2": 1 + }, + { + "value1": "={{ $json.user.post_count }}", + "operation": "larger", + "value2": 4 + } + ] + } + }, + "id": "ce1b80bb-08db-42cf-b7d9-56df74044f5c", + "name": "Filter", + "type": "n8n-nodes-base.filter", + "typeVersion": 1, + "position": [ + 600, + 640 + ] + }, + { + "parameters": { + "resource": "user", + "operation": "get", + "username": "={{ $json.username }}" + }, + "id": "ad3c141b-7aee-449b-8254-f21815a3d124", + "name": "Discourse1", + "type": "n8n-nodes-base.discourse", + "typeVersion": 1, + "position": [ + 340, + 840 + ] + }, + { + "parameters": { + "batchSize": 5, + "options": {} + }, + "id": "97fa87d0-ba76-4156-aa40-6bccd4775cdc", + "name": "Loop Over Items", + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 100, + 660 + ], + "disabled": true + }, + { + "parameters": { + "amount": 4, + "unit": "seconds" + }, + "id": "4f7f4b5d-2e02-4479-a4ee-9818f5b3e6de", + "name": "Wait", + "type": "n8n-nodes-base.wait", + "typeVersion": 1, + "position": [ + 580, + 840 + ], + "webhookId": "6bbd5e21-6022-475d-ace1-2aeb73e899d2" + }, + { + "parameters": {}, + "id": "a6cfc3b9-0d7a-4d4e-99c4-eba5085947d0", + "name": "No Operation, do nothing", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 340, + 640 + ] + }, + { + "parameters": { + "content": "### filtering\n- Forum account older than 6 days\n- 2+ replies marked as answer\n- 5+ posts" + }, + "id": "580c80dc-cf95-413c-9465-29c9dc66ef6e", + "name": "Sticky Note", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 640, + 420 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Discourse", + "type": "main", + "index": 0 + } + ] + ] + }, + "Discourse": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Discourse1": { + "main": [ + [ + { + "node": "Wait", + "type": "main", + "index": 0 + } + ] + ] + }, + "Loop Over Items": { + "main": [ + [ + { + "node": "No Operation, do nothing", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Discourse1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "No Operation, do nothing": { + "main": [ + [ + { + "node": "Filter", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Discourse": [ + { + "id": 1, + "user": "name" + } + ], + "Wait": [ + { + "user_badges": [], + "user": { + "id": 1, + "username": "User", + "name": "User", + "avatar_template": "/user_avatar/community.n8n.io/user/{size}/2.png", + "last_posted_at": "2023-11-02T16:16:05.615Z", + "last_seen_at": "2023-11-02T16:15:42.734Z", + "created_at": "2023-11-01T15:16:53.268Z", + "ignored": false, + "muted": false, + "can_ignore_user": true, + "can_mute_user": true, + "can_send_private_messages": true, + "can_send_private_message_to_user": true, + "trust_level": 0, + "moderator": false, + "admin": false, + "title": null, + "badge_count": 0, + "user_fields": { + "1": null + }, + "custom_fields": {}, + "time_read": 121, + "recent_time_read": 121, + "primary_group_id": null, + "primary_group_name": null, + "flair_group_id": null, + "flair_name": null, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "featured_topic": null, + "pending_posts_count": 0, + "staged": false, + "can_edit": true, + "can_edit_username": true, + "can_edit_email": true, + "can_edit_name": true, + "uploaded_avatar_id": 31486, + "has_title_badges": false, + "pending_count": 0, + "profile_view_count": 7, + "second_factor_enabled": false, + "can_upload_profile_header": true, + "can_upload_user_card_background": true, + "post_count": 1, + "can_be_deleted": true, + "can_delete_all_posts": true, + "locale": "en", + "muted_category_ids": [], + "regular_category_ids": [], + "watched_tags": [], + "watching_first_post_tags": [], + "tracked_tags": [], + "muted_tags": [], + "tracked_category_ids": [], + "watched_category_ids": [], + "watched_first_post_category_ids": [], + "system_avatar_upload_id": null, + "system_avatar_template": "/letter_avatar_proxy/v4/letter/c/3be4f8/{size}.png", + "custom_avatar_upload_id": 31486, + "custom_avatar_template": "/user_avatar/community.n8n.io/user/{size}/2.png", + "muted_usernames": [], + "ignored_usernames": [], + "allowed_pm_usernames": [], + "mailing_list_posts_per_day": 100, + "can_change_bio": true, + "can_change_location": true, + "can_change_website": true, + "can_change_tracking_preferences": true, + "user_api_keys": null, + "user_auth_tokens": [], + "user_notification_schedule": { + "enabled": false, + "day_0_start_time": 480, + "day_0_end_time": 1020, + "day_1_start_time": 480, + "day_1_end_time": 1020, + "day_2_start_time": 480, + "day_2_end_time": 1020, + "day_3_start_time": 480, + "day_3_end_time": 1020, + "day_4_start_time": 480, + "day_4_end_time": 1020, + "day_5_start_time": 480, + "day_5_end_time": 1020, + "day_6_start_time": 480, + "day_6_end_time": 1020 + }, + "use_logo_small_as_avatar": false, + "reminders_frequency": [ + { + "name": "discourse_assign.reminders_frequency.never", + "value": 0 + }, + { + "name": "discourse_assign.reminders_frequency.daily", + "value": 1440 + }, + { + "name": "discourse_assign.reminders_frequency.weekly", + "value": 10080 + }, + { + "name": "discourse_assign.reminders_frequency.monthly", + "value": 43200 + }, + { + "name": "discourse_assign.reminders_frequency.quarterly", + "value": 129600 + } + ], + "assign_icon": "user-plus", + "assign_path": "/u/User/activity/assigned", + "accepted_answers": 0, + "featured_user_badge_ids": [], + "invited_by": null, + "groups": [ + { + "id": 10, + "automatic": true, + "name": "trust_level_0", + "display_name": "trust_level_0", + "user_count": 9295, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + } + ], + "group_users": [ + { + "group_id": 10, + "user_id": 1, + "notification_level": 3 + } + ], + "user_option": { + "user_id": 1, + "mailing_list_mode": false, + "mailing_list_mode_frequency": 1, + "email_digests": false, + "email_level": 1, + "email_messages_level": 0, + "external_links_in_new_tab": true, + "color_scheme_id": null, + "dark_scheme_id": null, + "dynamic_favicon": true, + "enable_quoting": true, + "enable_defer": false, + "digest_after_minutes": 0, + "automatically_unpin_topics": true, + "auto_track_topics_after_msecs": 300000, + "notification_level_when_replying": 2, + "new_topic_duration_minutes": 2880, + "email_previous_replies": 2, + "email_in_reply_to": false, + "like_notification_frequency": 1, + "include_tl0_in_digests": false, + "theme_ids": [ + 7 + ], + "theme_key_seq": 0, + "allow_private_messages": true, + "enable_allowed_pm_users": false, + "homepage_id": null, + "hide_profile_and_presence": false, + "text_size": "normal", + "text_size_seq": 0, + "title_count_mode": "notifications", + "bookmark_auto_delete_preference": 3, + "timezone": "Europe/Berlin", + "skip_new_user_tips": false, + "default_calendar": "none_selected", + "oldest_search_log_date": null, + "seen_popups": [ + 1, + 3 + ] + } + } + }], + "No Operation, do nothing": [ + { + "user_badges": [], + "user": { + "id": 1, + "username": "User", + "name": "User", + "avatar_template": "/user_avatar/community.n8n.io/user/{size}/2.png", + "last_posted_at": "2023-11-02T16:16:05.615Z", + "last_seen_at": "2023-11-02T16:15:42.734Z", + "created_at": "2023-11-01T15:16:53.268Z", + "ignored": false, + "muted": false, + "can_ignore_user": true, + "can_mute_user": true, + "can_send_private_messages": true, + "can_send_private_message_to_user": true, + "trust_level": 0, + "moderator": false, + "admin": false, + "title": null, + "badge_count": 0, + "user_fields": { + "1": null + }, + "custom_fields": {}, + "time_read": 121, + "recent_time_read": 121, + "primary_group_id": null, + "primary_group_name": null, + "flair_group_id": null, + "flair_name": null, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "featured_topic": null, + "pending_posts_count": 0, + "staged": false, + "can_edit": true, + "can_edit_username": true, + "can_edit_email": true, + "can_edit_name": true, + "uploaded_avatar_id": 31486, + "has_title_badges": false, + "pending_count": 0, + "profile_view_count": 7, + "second_factor_enabled": false, + "can_upload_profile_header": true, + "can_upload_user_card_background": true, + "post_count": 1, + "can_be_deleted": true, + "can_delete_all_posts": true, + "locale": "en", + "muted_category_ids": [], + "regular_category_ids": [], + "watched_tags": [], + "watching_first_post_tags": [], + "tracked_tags": [], + "muted_tags": [], + "tracked_category_ids": [], + "watched_category_ids": [], + "watched_first_post_category_ids": [], + "system_avatar_upload_id": null, + "system_avatar_template": "/letter_avatar_proxy/v4/letter/c/3be4f8/{size}.png", + "custom_avatar_upload_id": 31486, + "custom_avatar_template": "/user_avatar/community.n8n.io/user/{size}/2.png", + "muted_usernames": [], + "ignored_usernames": [], + "allowed_pm_usernames": [], + "mailing_list_posts_per_day": 100, + "can_change_bio": true, + "can_change_location": true, + "can_change_website": true, + "can_change_tracking_preferences": true, + "user_api_keys": null, + "user_auth_tokens": [], + "user_notification_schedule": { + "enabled": false, + "day_0_start_time": 480, + "day_0_end_time": 1020, + "day_1_start_time": 480, + "day_1_end_time": 1020, + "day_2_start_time": 480, + "day_2_end_time": 1020, + "day_3_start_time": 480, + "day_3_end_time": 1020, + "day_4_start_time": 480, + "day_4_end_time": 1020, + "day_5_start_time": 480, + "day_5_end_time": 1020, + "day_6_start_time": 480, + "day_6_end_time": 1020 + }, + "use_logo_small_as_avatar": false, + "reminders_frequency": [ + { + "name": "discourse_assign.reminders_frequency.never", + "value": 0 + }, + { + "name": "discourse_assign.reminders_frequency.daily", + "value": 1440 + }, + { + "name": "discourse_assign.reminders_frequency.weekly", + "value": 10080 + }, + { + "name": "discourse_assign.reminders_frequency.monthly", + "value": 43200 + }, + { + "name": "discourse_assign.reminders_frequency.quarterly", + "value": 129600 + } + ], + "assign_icon": "user-plus", + "assign_path": "/u/User/activity/assigned", + "accepted_answers": 0, + "featured_user_badge_ids": [], + "invited_by": null, + "groups": [ + { + "id": 10, + "automatic": true, + "name": "trust_level_0", + "display_name": "trust_level_0", + "user_count": 9295, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + } + ], + "group_users": [ + { + "group_id": 10, + "user_id": 1, + "notification_level": 3 + } + ], + "user_option": { + "user_id": 1, + "mailing_list_mode": false, + "mailing_list_mode_frequency": 1, + "email_digests": false, + "email_level": 1, + "email_messages_level": 0, + "external_links_in_new_tab": true, + "color_scheme_id": null, + "dark_scheme_id": null, + "dynamic_favicon": true, + "enable_quoting": true, + "enable_defer": false, + "digest_after_minutes": 0, + "automatically_unpin_topics": true, + "auto_track_topics_after_msecs": 300000, + "notification_level_when_replying": 2, + "new_topic_duration_minutes": 2880, + "email_previous_replies": 2, + "email_in_reply_to": false, + "like_notification_frequency": 1, + "include_tl0_in_digests": false, + "theme_ids": [ + 7 + ], + "theme_key_seq": 0, + "allow_private_messages": true, + "enable_allowed_pm_users": false, + "homepage_id": null, + "hide_profile_and_presence": false, + "text_size": "normal", + "text_size_seq": 0, + "title_count_mode": "notifications", + "bookmark_auto_delete_preference": 3, + "timezone": "Europe/Berlin", + "skip_new_user_tips": false, + "default_calendar": "none_selected", + "oldest_search_log_date": null, + "seen_popups": [ + 1, + 3 + ] + } + } + }] + } +} diff --git a/cypress/fixtures/Test_workflow-actions_paste-data.json b/cypress/fixtures/Test_workflow-actions_paste-data.json index f3916fca15af1..c7342ce39641f 100644 --- a/cypress/fixtures/Test_workflow-actions_paste-data.json +++ b/cypress/fixtures/Test_workflow-actions_paste-data.json @@ -1,42 +1,128 @@ { "meta": { - "instanceId": "1a30c82b98a30444ad25bce513655a5e02be772d361403542c23172be6062f04" + "templateCredsSetupCompleted": true, + "instanceId": "669258f419ee5d9faf0a484944244a47fcc28c541f3c6c874e50a171a0be1e6b" }, "nodes": [ - { - "parameters": { - "rule": { - "interval": [{}] - } - }, - "id": "a898563b-d2a4-4b15-a979-366872e801b0", - "name": "Schedule Trigger", - "type": "n8n-nodes-base.scheduleTrigger", - "typeVersion": 1, - "position": [420, 260] + { + "parameters": { + "rule": { + "interval": [ + {} + ] + } }, - { - "parameters": { - "options": {} - }, - "id": "b9a13e3d-bfa5-4873-959f-fd3d67e380d9", - "name": "Set", - "type": "n8n-nodes-base.set", - "typeVersion": 1, - "position": [640, 260] - } + "id": "54b1cdeb-b453-4568-8107-c17fcf2aa25a", + "name": "Schedule Trigger", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1, + "position": [ + 240, + 560 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "5f6dffef-61f7-459d-930c-ef701d08d49a", + "name": "Set", + "type": "n8n-nodes-base.set", + "typeVersion": 1, + "position": [ + 460, + 460 + ] + }, + { + "parameters": { + "rules": { + "rules": [ + { + "outputKey": "a" + } + ] + } + }, + "id": "c07d3a12-1ee2-4131-bec2-ab366457d042", + "name": "Old version Switch Node", + "type": "n8n-nodes-base.switch", + "typeVersion": 2, + "position": [ + 460, + 680 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "182a833d-3b93-4e86-a0db-3ceb19d6562b", + "name": "Loop Over Items", + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 720, + 560 + ] + }, + { + "parameters": {}, + "id": "b0d0aeb7-0c8f-4810-8b78-6c0db3c9a486", + "name": "Replace Me", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 900, + 560 + ] + } ], "connections": { - "Schedule Trigger": { - "main": [ - [ - { - "node": "Set", - "type": "main", - "index": 0 - } - ] - ] - } - } -} + "Schedule Trigger": { + "main": [ + [ + { + "node": "Set", + "type": "main", + "index": 0 + }, + { + "node": "Old version Switch Node", + "type": "main", + "index": 0 + }, + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Loop Over Items": { + "main": [ + null, + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Replace Me": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} + } diff --git a/cypress/fixtures/Test_workflow_3.json b/cypress/fixtures/Test_workflow_3.json index 846b50cf193c8..43313c1f602b1 100644 --- a/cypress/fixtures/Test_workflow_3.json +++ b/cypress/fixtures/Test_workflow_3.json @@ -20,20 +20,21 @@ }, { "parameters": { - "values": { - "string": [ - { - "name": "other", - "value": "" - } + "assignments": { + "assignments": [ + { + "id": "2b0f25a2-9483-4579-9f6d-91b7ac2fcb71", + "name": "other", + "value": "", + "type": "string" + } ] - }, - "options": {} + } }, "id": "2dfc690a-95cf-48c2-85a6-2b3bb8cd1d1d", "name": "Set", "type": "n8n-nodes-base.set", - "typeVersion": 1, + "typeVersion": 3.3, "position": [ 920, 300 @@ -43,21 +44,22 @@ "id": "9bee04af-1bfc-4be2-a704-e975cb887ced", "name": "Set1", "type": "n8n-nodes-base.set", - "typeVersion": 1, + "typeVersion": 3.3, "position": [ 1120, 300 ], "parameters": { - "values": { - "string": [ - { - "name": "other", - "value": "" - } + "assignments": { + "assignments": [ + { + "id": "2b0f25a2-9483-4579-9f6d-91b7ac2fcb71", + "name": "other", + "value": "", + "type": "string" + } ] - }, - "options": {} + } } } ], @@ -118,4 +120,4 @@ "instanceId": "fe45a93dd232270eb40d3ba1f7907ad3935bbd72ad5e4ee09ff61e96674f9aef" }, "tags": [] -} \ No newline at end of file +} diff --git a/cypress/fixtures/Test_workflow_5.json b/cypress/fixtures/Test_workflow_5.json index 6b87fc33b70a1..5771e197d9e56 100644 --- a/cypress/fixtures/Test_workflow_5.json +++ b/cypress/fixtures/Test_workflow_5.json @@ -40,7 +40,7 @@ { "parameters": {}, "id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e", - "name": "When clicking \"Execute Workflow\"", + "name": "When clicking \"Test workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ @@ -50,7 +50,6 @@ }, { "parameters": { - "operation": "sort", "sortFieldsUi": { "sortField": [ { @@ -61,9 +60,9 @@ "options": {} }, "id": "555a150c-d735-4331-b628-c1f1cfed2da1", - "name": "Item Lists", - "type": "n8n-nodes-base.itemLists", - "typeVersion": 2, + "name": "Sort", + "type": "n8n-nodes-base.sort", + "typeVersion": 1, "position": [ -280, 580 @@ -182,7 +181,7 @@ "main": [ [ { - "node": "Item Lists", + "node": "Sort", "type": "main", "index": 0 } @@ -200,7 +199,7 @@ ] ] }, - "When clicking \"Execute Workflow\"": { + "When clicking \"Test workflow\"": { "main": [ [ { @@ -216,7 +215,7 @@ ] ] }, - "Item Lists": { + "Sort": { "main": [ [ { @@ -289,4 +288,4 @@ ] } } -} \ No newline at end of file +} diff --git a/cypress/fixtures/Test_workflow_filter.json b/cypress/fixtures/Test_workflow_filter.json new file mode 100644 index 0000000000000..e5aad933882f7 --- /dev/null +++ b/cypress/fixtures/Test_workflow_filter.json @@ -0,0 +1,153 @@ +{ + "name": "Filter test", + "nodes": [ + { + "parameters": {}, + "id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -60, + 480 + ] + }, + { + "parameters": { + "jsCode": "return [\n {\n \"label\": \"Apple\",\n tags: [],\n meta: {foo: 'bar'}\n },\n {\n \"label\": \"Banana\",\n tags: ['exotic'],\n meta: {}\n },\n {\n \"label\": \"Pear\",\n tags: ['other'],\n meta: {}\n },\n {\n \"label\": \"Orange\",\n meta: {}\n }\n]" + }, + "id": "60697c7f-3948-4790-97ba-8aba03d02ac2", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 160, + 480 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "" + }, + "conditions": [ + { + "leftValue": "={{ $json.tags }}", + "rightValue": "exotic", + "operator": { + "type": "array", + "operation": "contains", + "rightType": "any" + } + }, + { + "leftValue": "={{ $json.meta }}", + "rightValue": "", + "operator": { + "type": "object", + "operation": "notEmpty", + "singleValue": true + } + }, + { + "leftValue": "={{ $json.label }}", + "rightValue": "Pea", + "operator": { + "type": "string", + "operation": "startsWith", + "rightType": "string" + } + } + ], + "combinator": "or" + }, + "options": {} + }, + "id": "7531191b-5ac3-45dc-8afb-27ae83d8f33a", + "name": "If", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 380, + 480 + ] + }, + { + "parameters": {}, + "id": "d8c614ea-0bbf-4b12-ad7d-c9ebe09ce583", + "name": "Then", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 600, + 400 + ] + }, + { + "parameters": {}, + "id": "69364770-60d2-4ef4-9f29-9570718a9a10", + "name": "Else", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 600, + 580 + ] + } + ], + "pinData": {}, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [ + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "Then", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Else", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "a6249f48-d88f-4b80-9ed9-79555e522d48", + "id": "BWUTRs5RHxVgQ4uT", + "meta": { + "instanceId": "78577815012af39cf16dad7a787b0898c42fb7514b8a7f99b2136862c2af502c" + }, + "tags": [] +} diff --git a/cypress/fixtures/Test_workflow_form_switch.json b/cypress/fixtures/Test_workflow_form_switch.json new file mode 100644 index 0000000000000..78349c3ae5554 --- /dev/null +++ b/cypress/fixtures/Test_workflow_form_switch.json @@ -0,0 +1,78 @@ +{ + "name": "My workflow 8", + "nodes": [ + { + "parameters": { + "path": "d1cba915-ca18-4425-bcfb-133205fc815a", + "formTitle": "test", + "formFields": { + "values": [ + { + "fieldLabel": "test" + } + ] + }, + "options": {} + }, + "id": "9e685367-fb94-4376-a9a4-7f311d9f7e2d", + "name": "n8n Form Trigger", + "type": "n8n-nodes-base.formTrigger", + "typeVersion": 2, + "position": [ + 620, + 580 + ], + "webhookId": "d1cba915-ca18-4425-bcfb-133205fc815a" + }, + { + "parameters": {}, + "id": "0f4dfe66-51c0-4378-9eab-680f8140a572", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 2, + "position": [ + 800, + 580 + ] + } + ], + "pinData": { + "n8n Form Trigger": [ + { + "json": { + "name": "First item", + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "code": 2 + } + } + ] + }, + "connections": { + "n8n Form Trigger": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "d6c14bc8-a69f-47bb-b5ba-fe6e9db0a3a4", + "id": "UQSimcMQJGbTeTLG", + "meta": { + "instanceId": "a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0" + }, + "tags": [] +} \ No newline at end of file diff --git a/cypress/fixtures/Test_workflow_ndv_version.json b/cypress/fixtures/Test_workflow_ndv_version.json new file mode 100644 index 0000000000000..7f3ba16f43277 --- /dev/null +++ b/cypress/fixtures/Test_workflow_ndv_version.json @@ -0,0 +1,41 @@ +{ + "name": "Node versions", + "nodes": [ + { + "id": "2acca986-10a6-451e-b20a-86e95b50e627", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [460, 460] + }, + { + "id": "1ea0a87c-3395-4bd9-84fb-cf8b0f769cc4", + "name": "Function", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [960, 460] + }, + { + "id": "30bb9fab-bc89-4309-b42b-0fc586519c76", + "name": "Edit Fields (old)", + "type": "n8n-nodes-base.set", + "typeVersion": 2, + "position": [800, 460] + }, + { + "id": "a266b96c-3539-4034-b24c-c86c6d0ca31e", + "name": "Edit Fields (no typeVersion)", + "type": "n8n-nodes-base.set", + "position": [1120, 460] + }, + { + "id": "273f60c9-08e7-457e-b01d-31e16c565171", + "name": "Edit Fields (latest)", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [640, 460] + } + ], + "connections": {}, + "pinData": {} +} diff --git a/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json b/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json new file mode 100644 index 0000000000000..2a9e75e11b4ab --- /dev/null +++ b/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json @@ -0,0 +1,115 @@ +{ + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "2be09fdcb9594c0827fd4cee80f7e590c93297d9217685f34c2250fe3144ef0c" + }, + "nodes": [ + { + "parameters": {}, + "id": "09e4325e-ede1-40cf-a1ba-58612bbc7f1b", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 820, + 400 + ] + }, + { + "parameters": { + "category": "randomData" + }, + "id": "4920bf3a-9978-4196-9dcb-8c2892e5641b", + "name": "DebugHelper", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [ + 1040, + 400 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "7508343e-3e99-4d12-96e4-00a35a3d4306", + "leftValue": "={{ $json.email }}", + "rightValue": ".", + "operator": { + "type": "string", + "operation": "contains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "4f6a6a4e-19b6-43f5-ba5c-e40b09d7f873", + "name": "Filter", + "type": "n8n-nodes-base.filter", + "typeVersion": 2, + "position": [ + 1260, + 400 + ] + }, + { + "parameters": { + "chatId": "123123", + "text": "1123123", + "additionalFields": {} + }, + "id": "1765f352-fc12-4fab-9c24-d666a150266f", + "name": "Telegram", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [ + 1480, + 400 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "DebugHelper", + "type": "main", + "index": 0 + } + ] + ] + }, + "DebugHelper": { + "main": [ + [ + { + "node": "Filter", + "type": "main", + "index": 0 + } + ] + ] + }, + "Filter": { + "main": [ + [ + { + "node": "Telegram", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} +} diff --git a/cypress/fixtures/Test_workflow_schema_test.json b/cypress/fixtures/Test_workflow_schema_test.json index 0db43a5ea458f..8c83c4f20e633 100644 --- a/cypress/fixtures/Test_workflow_schema_test.json +++ b/cypress/fixtures/Test_workflow_schema_test.json @@ -47,7 +47,7 @@ { "parameters": {}, "id": "58512a93-dabf-4584-817f-27c608c1bdd5", - "name": "When clicking \"Execute Workflow\"", + "name": "When clicking \"Test workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ @@ -69,7 +69,7 @@ ] ] }, - "When clicking \"Execute Workflow\"": { + "When clicking \"Test workflow\"": { "main": [ [ { diff --git a/cypress/fixtures/Test_workflow_schema_test_pinned_data.json b/cypress/fixtures/Test_workflow_schema_test_pinned_data.json index 5233a17848a26..8bd5ef783db56 100644 --- a/cypress/fixtures/Test_workflow_schema_test_pinned_data.json +++ b/cypress/fixtures/Test_workflow_schema_test_pinned_data.json @@ -47,7 +47,7 @@ { "parameters": {}, "id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9", - "name": "When clicking \"Execute Workflow\"", + "name": "When clicking \"Test workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ @@ -552,7 +552,7 @@ ] ] }, - "When clicking \"Execute Workflow\"": { + "When clicking \"Test workflow\"": { "main": [ [ { diff --git a/cypress/fixtures/Test_workflow_webhook_with_pin_data.json b/cypress/fixtures/Test_workflow_webhook_with_pin_data.json new file mode 100644 index 0000000000000..fb632bcf36383 --- /dev/null +++ b/cypress/fixtures/Test_workflow_webhook_with_pin_data.json @@ -0,0 +1,151 @@ +{ + "name": "PinData Test", + "nodes": [ + { + "parameters": {}, + "id": "0a60e507-7f34-41c0-a0f9-697d852033b6", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 780, + 320 + ] + }, + { + "parameters": { + "path": "b0d79ddb-df2d-49b1-8555-9fa2b482608f", + "responseMode": "lastNode", + "options": {} + }, + "id": "66425ce3-450d-4aa6-a53b-a701ab89c2de", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 780, + 540 + ], + "webhookId": "b0d79ddb-df2d-49b1-8555-9fa2b482608f" + }, + { + "parameters": { + "fields": { + "values": [ + { + "name": "nodeData", + "stringValue": "init" + } + ] + }, + "include": "none", + "options": {} + }, + "id": "3211b3c5-49e9-4694-8f86-7a5783bc653a", + "name": "Init Data", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1000, + 320 + ] + }, + { + "parameters": { + "fields": { + "values": [ + { + "name": "nodeData", + "stringValue": "pin" + } + ] + }, + "options": {} + }, + "id": "97b31120-4720-4632-9d35-356f345119f7", + "name": "Pin Data", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1240, + 320 + ] + }, + { + "parameters": {}, + "id": "1ee7be4f-7006-43bf-bb0c-29db3058a399", + "name": "End", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1460, + 320 + ] + } + ], + "pinData": { + "Pin Data": [ + { + "json": { + "nodeData": "pin-overwritten" + } + } + ] + }, + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Init Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Init Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Init Data": { + "main": [ + [ + { + "node": "Pin Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Pin Data": { + "main": [ + [ + { + "node": "End", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "ded8577a-3ed2-4611-842c-a7922ec58b98", + "id": "weofVLZo0ssmPDrV", + "meta": { + "instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff" + }, + "tags": [] + } diff --git a/cypress/fixtures/Test_workflow_xml_output.json b/cypress/fixtures/Test_workflow_xml_output.json new file mode 100644 index 0000000000000..17449bc56d9ab --- /dev/null +++ b/cypress/fixtures/Test_workflow_xml_output.json @@ -0,0 +1,53 @@ +{ + "meta": { + "instanceId": "2d1cf27f75b18bb9e146336f791c37884f4fc7ddb97c2def27c0444d106778bf" + }, + "nodes": [ + { + "parameters": {}, + "id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 420, + 220 + ] + }, + { + "parameters": { + "fields": { + "values": [ + { + "name": "body", + "stringValue": " Introduction to XML John Doe 2020 1234567890 Data Science Basics Jane Smith 2019 0987654321 Programming in Python Bob Johnson 2021 5432109876 " + } + ] + }, + "options": {} + }, + "id": "45888152-7c5f-4d88-9039-660c594da084", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 640, + 220 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} + } diff --git a/cypress/fixtures/Workflow_template_write_http_query.json b/cypress/fixtures/Workflow_template_write_http_query.json new file mode 100644 index 0000000000000..3187bccd617ea --- /dev/null +++ b/cypress/fixtures/Workflow_template_write_http_query.json @@ -0,0 +1,188 @@ +{ + "workflow": { + "id": 1, + "name": "Write HTTP query string on image", + "views": 116, + "recentViews": 9766, + "totalViews": 116, + "createdAt": "2019-08-31T00:13:41.893Z", + "description": "1. Receives data from an incoming HTTP Request\n1. Reads file from internet\n ![workflow-screenshot](fileId:14) \n1. Writes data on image\n1. Returns the data\n\nThe URL to call will look like this:\nhttp://localhost:5678/webhook-test/webhook/test?name=Jim\n\nOnce called it will return an image like this:\n\n![reponseimage.jpeg](fileId:4)", + "workflow": { + "nodes": [ + { + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "position": [ + 700, + 350 + ], + "parameters": { + "path": "test", + "responseData": "firstEntryBinary", + "responseMode": "lastNode" + }, + "typeVersion": 1 + }, + { + "name": "Edit Image", + "type": "n8n-nodes-base.editImage", + "position": [ + 1100, + 350 + ], + "parameters": { + "text": "=They found the killer it was {{$node[\"Webhook\"].data[\"query\"][\"name\"]}}!", + "fontSize": "=25", + "operation": "text", + "positionX": 150, + "positionY": 180, + "lineLength": 18 + }, + "typeVersion": 1 + }, + { + "name": "Read File URL", + "type": "n8n-nodes-base.httpRequest", + "position": [ + 900, + 350 + ], + "parameters": { + "url": "https://www.needpix.com/file_download.php?url=//storage.needpix.com/thumbs/newspaper-412809_1280.jpg", + "responseFormat": "file" + }, + "typeVersion": 1 + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Read File URL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Read File URL": { + "main": [ + [ + { + "node": "Edit Image", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "lastUpdatedBy": 11, + "workflowInfo": { + "nodeCount": 3, + "nodeTypes": { + "n8n-nodes-base.webhook": { + "count": 1 + }, + "n8n-nodes-base.editImage": { + "count": 1 + }, + "n8n-nodes-base.httpRequest": { + "count": 1 + } + } + }, + "user": { + "username": "jan" + }, + "nodes": [ + { + "id": 9, + "icon": "fa:image", + "name": "n8n-nodes-base.editImage", + "defaults": { + "name": "Edit Image", + "color": "#553399" + }, + "iconData": { + "icon": "image", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + }, + { + "id": 27, + "name": "Marketing" + } + ], + "displayName": "Edit Image", + "typeVersion": 1 + }, + { + "id": 19, + "icon": "file:httprequest.svg", + "name": "n8n-nodes-base.httpRequest", + "defaults": { + "name": "HTTP Request", + "color": "#0004F5" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDAgNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTQwIDIwQzQwIDguOTUzMTQgMzEuMDQ2OSAwIDIwIDBDOC45NTMxNCAwIDAgOC45NTMxNCAwIDIwQzAgMzEuMDQ2OSA4Ljk1MzE0IDQwIDIwIDQwQzMxLjA0NjkgNDAgNDAgMzEuMDQ2OSA0MCAyMFpNMjAgMzYuOTQ1OEMxOC44ODUyIDM2Ljk0NTggMTcuMTM3OCAzNS45NjcgMTUuNDk5OCAzMi42OTg1QzE0Ljc5NjQgMzEuMjkxOCAxNC4xOTYxIDI5LjU0MzEgMTMuNzUyNiAyNy42ODQ3SDI2LjE4OThDMjUuODA0NSAyOS41NDAzIDI1LjIwNDQgMzEuMjkwMSAyNC41MDAyIDMyLjY5ODVDMjIuODYyMiAzNS45NjcgMjEuMTE0OCAzNi45NDU4IDIwIDM2Ljk0NThaTTEyLjkwNjQgMjBDMTIuOTA2NCAyMS42MDk3IDEzLjAwODcgMjMuMTY0IDEzLjIwMDMgMjQuNjMwNUgyNi43OTk3QzI2Ljk5MTMgMjMuMTY0IDI3LjA5MzYgMjEuNjA5NyAyNy4wOTM2IDIwQzI3LjA5MzYgMTguMzkwMyAyNi45OTEzIDE2LjgzNiAyNi43OTk3IDE1LjM2OTVIMTMuMjAwM0MxMy4wMDg3IDE2LjgzNiAxMi45MDY0IDE4LjM5MDMgMTIuOTA2NCAyMFpNMjAgMy4wNTQxOUMyMS4xMTQ5IDMuMDU0MTkgMjIuODYyMiA0LjAzMDc4IDI0LjUwMDEgNy4zMDAzOUMyNS4yMDY2IDguNzE0MDggMjUuODA3MiAxMC40MDY3IDI2LjE5MiAxMi4zMTUzSDEzLjc1MDFDMTQuMTkzMyAxMC40MDQ3IDE0Ljc5NDIgOC43MTI1NCAxNS40OTk4IDcuMzAwNjRDMTcuMTM3NyA0LjAzMDgzIDE4Ljg4NTEgMy4wNTQxOSAyMCAzLjA1NDE5Wk0zMC4xNDc4IDIwQzMwLjE0NzggMTguNDA5OSAzMC4wNTQzIDE2Ljg2MTcgMjkuODIyNyAxNS4zNjk1SDM2LjMwNDJDMzYuNzI1MiAxNi44NDIgMzYuOTQ1OCAxOC4zOTY0IDM2Ljk0NTggMjBDMzYuOTQ1OCAyMS42MDM2IDM2LjcyNTIgMjMuMTU4IDM2LjMwNDIgMjQuNjMwNUgyOS44MjI3QzMwLjA1NDMgMjMuMTM4MyAzMC4xNDc4IDIxLjU5MDEgMzAuMTQ3OCAyMFpNMjYuMjc2NyA0LjI1NTExQzI3LjYzNjUgNi4zNjAxOSAyOC43MTEgOS4xMzIgMjkuMzc3NCAxMi4zMTUzSDM1LjEwNDZDMzMuMjUxMSA4LjY2OCAzMC4xMDcgNS43ODM0NiAyNi4yNzY3IDQuMjU1MTFaTTEwLjYyMjYgMTIuMzE1M0g0Ljg5MjkzQzYuNzUxNDcgOC42Njc4NCA5Ljg5MzUxIDUuNzgzNDEgMTMuNzIzMiA0LjI1NTEzQzEyLjM2MzUgNi4zNjAyMSAxMS4yODkgOS4xMzIwMSAxMC42MjI2IDEyLjMxNTNaTTMuMDU0MTkgMjBDMy4wNTQxOSAyMS42MDMgMy4yNzc0MyAyMy4xNTc1IDMuNjk0ODQgMjQuNjMwNUgxMC4xMjE3QzkuOTQ2MTkgMjMuMTQyIDkuODUyMjIgMjEuNTk0MyA5Ljg1MjIyIDIwQzkuODUyMjIgMTguNDA1NyA5Ljk0NjE5IDE2Ljg1OCAxMC4xMjE3IDE1LjM2OTVIMy42OTQ4NEMzLjI3NzQzIDE2Ljg0MjUgMy4wNTQxOSAxOC4zOTcgMy4wNTQxOSAyMFpNMjYuMjc2NiAzNS43NDI3QzI3LjYzNjUgMzMuNjM5MyAyOC43MTEgMzAuODY4IDI5LjM3NzQgMjcuNjg0N0gzNS4xMDQ2QzMzLjI1MSAzMS4zMzIyIDMwLjEwNjggMzQuMjE3OSAyNi4yNzY2IDM1Ljc0MjdaTTEzLjcyMzQgMzUuNzQyN0M5Ljg5MzY5IDM0LjIxNzkgNi43NTE1NSAzMS4zMzI0IDQuODkyOTMgMjcuNjg0N0gxMC42MjI2QzExLjI4OSAzMC44NjggMTIuMzYzNSAzMy42MzkzIDEzLjcyMzQgMzUuNzQyN1oiIGZpbGw9IiMwMDA0RjUiLz4KPC9zdmc+Cg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "HTTP Request", + "typeVersion": 3 + }, + { + "id": 47, + "icon": "file:webhook.svg", + "name": "n8n-nodes-base.webhook", + "defaults": { + "name": "Webhook" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0zNSwzN2MtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMzcuMiwzNywzNSwzN3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUsNDNjLTMsMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEsMS40LDIuOSwyLjMsNC43LDIuM2MzLjMsMCw2LTIuNyw2LTZzLTIuNy02LTYtNiBjLTEsMC0yLDAuMy0yLjksMC43bC0xLjcsMUwyMy4zLDE2bDMuNS0xLjlsNS4zLDkuNGMxLTAuMywyLTAuNSwzLTAuNWM1LjUsMCwxMCw0LjUsMTAsMTBTNDAuNSw0MywzNSw0M3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMTQsNDNDOC41LDQzLDQsMzguNSw0LDMzYzAtNC42LDMuMS04LjUsNy41LTkuN2wxLDMuOUM5LjksMjcuOSw4LDMwLjMsOCwzM2MwLDMuMywyLjcsNiw2LDYgczYtMi43LDYtNnYtMmgxNXY0SDIzLjhDMjIuOSwzOS42LDE4LjgsNDMsMTQsNDN6Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0iTTE0LDM3Yy0yLjIsMC00LTEuOC00LTRzMS44LTQsNC00czQsMS44LDQsNFMxNi4yLDM3LDE0LDM3eiIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSwxOWMtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMjcuMiwxOSwyNSwxOXoiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTUuNywzNEwxMi4zLDMybDUuOS05LjdjLTItMS45LTMuMi00LjUtMy4yLTcuM2MwLTUuNSw0LjUtMTAsMTAtMTBjNS41LDAsMTAsNC41LDEwLDEwIGMwLDAuOS0wLjEsMS43LTAuMywyLjVsLTMuOS0xYzAuMS0wLjUsMC4yLTEsMC4yLTEuNWMwLTMuMy0yLjctNi02LTZzLTYsMi43LTYsNmMwLDIuMSwxLjEsNCwyLjksNS4xbDEuNywxTDE1LjcsMzR6Ii8+PC9zdmc+Cg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Webhook", + "typeVersion": 1 + } + ], + "categories": [], + "image": [ + { + "id": 4, + "url": "data:image/jpeg;base64,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" + }, + { + "id": 14, + "url": "data:image/png;base64,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" + } + ] + } +} diff --git a/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json b/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json new file mode 100644 index 0000000000000..ffb7005f4fbd8 --- /dev/null +++ b/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json @@ -0,0 +1,156 @@ +{ + "meta": { + "instanceId": "abc" + }, + "nodes": [ + { + "parameters": {}, + "id": "bcb6abdf-d34b-4ea7-a8ed-58155b708c43", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 20, + 260 + ] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\nitem.json.message_count = Math.min(item.json.messages.length, 3);\n}\n\nreturn $input.all();" + }, + "id": "59c3889c-3671-4f49-b258-6131df8587d8", + "name": "Set thread properties1", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 500, + 520 + ] + }, + { + "parameters": { + "resource": "thread", + "operation": "get", + "threadId": "={{ $json.id }}", + "options": {} + }, + "id": "e102b72e-1e47-4004-a6b9-38cef75f44a1", + "name": "Get thread details1", + "type": "n8n-nodes-base.gmail", + "typeVersion": 2, + "position": [ + 300, + 520 + ] + }, + { + "parameters": { + "mode": "expression", + "output": "={{ $('Set thread properties1').item.json.message_count }}" + }, + "id": "f3e42f07-df82-42ba-8e99-97cda707a9d9", + "name": "Switch1", + "type": "n8n-nodes-base.switch", + "typeVersion": 1, + "position": [ + 1220, + 540 + ] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": true, + "value2": true + } + ] + } + }, + "id": "c7fe521e-8c02-44bf-8a14-482b39749508", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 720, + 520 + ] + }, + { + "parameters": {}, + "id": "3b9f6a05-7f19-46c5-95d1-5dec732f00ae", + "name": "No Operation, do nothing", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 960, + 400 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Get thread details1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set thread properties1": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get thread details1": { + "main": [ + [ + { + "node": "Set thread properties1", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "No Operation, do nothing", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Switch1", + "type": "main", + "index": 0 + } + ] + ] + }, + "No Operation, do nothing": { + "main": [ + [ + { + "node": "Switch1", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/cypress/fixtures/manual-partial-execution.json b/cypress/fixtures/manual-partial-execution.json new file mode 100644 index 0000000000000..9e43cd525bef0 --- /dev/null +++ b/cypress/fixtures/manual-partial-execution.json @@ -0,0 +1,107 @@ +{ + "meta": { + "templateCredsSetupCompleted": true + }, + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "f4467143-fdb9-46fa-8020-6417cc5eea7d", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [ + 1140, + 260 + ] + }, + { + "parameters": { + "path": "30ff316d-405f-4288-a0ac-e713546c9d4e", + "options": {} + }, + "id": "4760aafb-5d56-4633-99d3-7a97c576a216", + "name": "Webhook1", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [ + 680, + 340 + ], + "webhookId": "30ff316d-405f-4288-a0ac-e713546c9d4e" + }, + { + "parameters": { + "articleId": "123", + "additionalFields": {} + }, + "id": "8c811eca-8978-44d9-b8f7-ef2c7725784c", + "name": "Hacker News", + "type": "n8n-nodes-base.hackerNews", + "typeVersion": 1, + "position": [ + 920, + 260 + ] + }, + { + "parameters": { + "path": "4a3398e4-1388-4e10-9d21-add90b804955", + "options": {} + }, + "id": "1c2c2d06-45c9-4712-9fa0-c655bef8d0e5", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [ + 680, + 180 + ], + "webhookId": "4a3398e4-1388-4e10-9d21-add90b804955" + } + ], + "connections": { + "Webhook1": { + "main": [ + [ + { + "node": "Hacker News", + "type": "main", + "index": 0 + } + ] + ] + }, + "Hacker News": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Hacker News", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Webhook": [ + { + "name": "First item", + "code": 1 + } + ] + } +} diff --git a/cypress/fixtures/open_node_creator_for_connection.json b/cypress/fixtures/open_node_creator_for_connection.json new file mode 100644 index 0000000000000..78827d4083fd1 --- /dev/null +++ b/cypress/fixtures/open_node_creator_for_connection.json @@ -0,0 +1,110 @@ +{ + "name": "open_node_creator_for_connection", + "nodes": [ + { + "parameters": {}, + "id": "25ff0c17-7064-4e14-aec6-45c71d63201b", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 740, + 520 + ] + }, + { + "parameters": {}, + "id": "49f376ca-845b-4737-aac0-073d0e4fa95c", + "name": "Token Splitter", + "type": "@n8n/n8n-nodes-langchain.textSplitterTokenSplitter", + "typeVersion": 1, + "position": [ + 1180, + 540 + ] + }, + { + "parameters": {}, + "id": "d1db5111-4b01-4620-8ccb-a16ea576c363", + "name": "Memory", + "type": "@n8n/n8n-nodes-langchain.memoryXata", + "typeVersion": 1.2, + "position": [ + 940, + 540 + ], + "credentials": { + "xataApi": { + "id": "q1ckaYlHTWCYDtF0", + "name": "Xata Api account" + } + } + }, + { + "parameters": {}, + "id": "b08b6d3a-bef8-42ac-9cef-ec9d4e5402b1", + "name": "Output Parser", + "type": "@n8n/n8n-nodes-langchain.outputParserStructured", + "typeVersion": 1.1, + "position": [ + 1060, + 540 + ] + }, + { + "parameters": {}, + "id": "ee557938-9cf1-4b78-afef-c783c52fd307", + "name": "Tool", + "type": "@n8n/n8n-nodes-langchain.toolWikipedia", + "typeVersion": 1, + "position": [ + 1300, + 540 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "814f2e9c-cc7b-4f3c-89b4-d6eb82bc24df", + "name": "Embeddings", + "type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference", + "typeVersion": 1, + "position": [ + 1420, + 540 + ] + }, + { + "parameters": { + "tableName": { + "__rl": true, + "mode": "list", + "value": "" + }, + "options": {} + }, + "id": "e8569b0b-a580-4249-9c5e-f1feed5c644e", + "name": "Vector Store", + "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase", + "typeVersion": 1, + "position": [ + 1540, + 540 + ] + } + ], + "pinData": {}, + "connections": {}, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "8e90604c-f7e9-489d-8e43-1cc699b7db04", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" + }, + "id": "L3tgfoW660UOSuX6", + "tags": [] +} \ No newline at end of file diff --git a/cypress/fixtures/templates_search/all_templates_search_response.json b/cypress/fixtures/templates_search/all_templates_search_response.json new file mode 100644 index 0000000000000..5a0a1eb5ad5b1 --- /dev/null +++ b/cypress/fixtures/templates_search/all_templates_search_response.json @@ -0,0 +1,1071 @@ +{ + "totalWorkflows": 506, + "workflows": [ + { + "id": 60, + "name": "test1 test1", + "totalViews": 120000000, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2019-08-30T16:39:31.362Z", + "nodes": [ + { + "id": 11, + "icon": "file:amqp.png", + "name": "n8n-nodes-base.amqpTrigger", + "defaults": { + "name": "AMQP Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AMQP Trigger", + "typeVersion": 1 + }, + { + "id": 18, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilot", + "defaults": { + "name": "Autopilot" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:clearbit.svg", + "name": "n8n-nodes-base.clearbit", + "defaults": { + "name": "Clearbit" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Clearbit", + "typeVersion": 1 + }, + { + "id": 51, + "icon": "file:convertKit.svg", + "name": "n8n-nodes-base.convertKitTrigger", + "defaults": { + "name": "ConvertKit Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTcyIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNODIuNzIgMTI2LjMxNmMyOS43NyAwIDUyLjc4LTIyLjYyMiA1Mi43OC01MC41MjYgMC0yNi4xNDMtMjEuNjE3LTQyLjEwNi0zNS45MzUtNDIuMTA2LTE5Ljk0NSAwLTM1LjkzIDE0LjA4NC0zOC4xOTggMzQuOTg4LS40MTggMy44NTYtMy40NzYgNy4wOS03LjM1NSA3LjA2MS02LjQyMy0uMDQ2LTE1Ljc0Ni0uMS0yMS42NTgtLjA4LTIuNTU1LjAwOC00LjY2OS0yLjA2NS00LjU0My00LjYxOC44OS0xOC4xMjMgNi45MTQtMzUuMDcgMTguNDAyLTQ4LjA4N0M1OC45NzYgOC40ODggNzcuNTYxIDAgOTkuNTY1IDBjMzYuOTY5IDAgNzEuODY5IDMzLjc4NiA3MS44NjkgNzUuNzkgMCA0Ni41MDgtMzguMzEyIDg0LjIxLTg3LjkyNyA4NC4yMS0zNS4zODQgMC03MS4wMjEtMjMuMjU4LTgzLjQ2NC01NS43NzVhLjcwMi43MDIgMCAwMS0uMDMtLjM3N2MuMTY1LS45NjIuNDk0LTEuODQxLjgxOC0yLjcwNy40NzEtMS4yNTguOTMxLTIuNDg4Ljg2NC0zLjkwNmwtLjIxNS00LjUyOWE1LjUyMyA1LjUyMyAwIDAxMy4xOC01LjI2M2wxLjc5OC0uODQyYTYuOTgyIDYuOTgyIDAgMDAzLjkxMi01LjA3NSA2Ljk5MyA2Ljk5MyAwIDAxNi44ODctNS43MzZjNS4yODIgMCA5Ljg3NSAzLjUxNSAxMS41OSA4LjUxMiA4LjMwNyAyNC4yMTIgMjEuNTExIDQyLjAxNCA1My44NzMgNDIuMDE0eiIgZmlsbD0iI0ZCNjk3MCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + }, + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "ConvertKit Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 25, + "name": "test1 test1", + "totalViews": 120000000, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2019-08-18T18:17:58.358Z", + "nodes": [ + { + "id": 15, + "icon": "file:affinity.png", + "name": "n8n-nodes-base.affinity", + "defaults": { + "name": "Affinity" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAC/VBMVEUAAAAtTfMvVekpSN8rR+g2POglUecfWuY0PukdXeUoTucrS+c2POkfW+VGJusyQeghWOcjVecmUecNceQgWeY4OegiV+YeXOYxQ+gXZeUuR+gmUuc9MuoZYeZHJuszP+g4OulFKOsTauQpS+c7Ou02O+kvRegiVuZBLuoMdOQoTeoQbuQ9NekVaOVHJesQbeQhWOctR+gTauUyQuk9L+wqS+cjVeY0PugyQeg7NekXZeVDK+pHJesgWeYqTOglUugcYeg0PukNceRDK+tGJ+pFKOsyQeg4OekrSec2O+kyQOgkVOYOceQjVechWOY6NuglUecQbuQtSOchV+cxQek9MelAMOkcX+ZDKetEK+otR+dCL+oPZ+IcX+YSbOQ5OOgyQegKdeQUaeU3O+gcX+U2O+kdXeYgWuYmUeYZY+ZFJ+onT+gjVOcSbORFKesKdeMcXeYPbuMYZOQNbOYlUuYyQegcXuU4OehEKeopTechV+dHJusnUOdHJus0PukOceRGJuoPbeQyQegjVeY3O+kpTudGJ+svR+c6N+kcXuUVZ+VGJuoLdOMzQOgLdOQvRegKdeQPceQdXeQqTOZBLegQb+UiVuYeXOQlU+dBLelBLukPcOQTauQdXOUsSuYLd+MvR+hEK+otR+dDLOpHJusPceUiVeczQOkiVuU1Pek2POkgWOYeW+UfWucgY/c2RPcjXfQ3OugkVOc7PfU6NulMKfw4Q/kzQOlLKPgqTektSOclW/ZJKPM1Qe4iWu0YZegsUfUPePIkWvI2QfIeYe85O+1HJ+0tSeowRepAL+kiV+cySvgjXvYRdvQxSfMsTvAxRe09Ne0VbewMc+RILvsvTvk1Rvk7P/lDM/knWPcqVPYWb/Q6PfInVfAnU+xHJuseXuojV+obYOYZavQ3QfQMffMTcvNELvNAMvALe+8Odu8aZu0Mduo9M+oQcOkoUOgLdeU/OflIK/chYfY8OPIgXu8wQ+g8P/wsVPoeZfU6Ou8vSu5BL+0hWugSbea5VKRRAAAAqXRSTlMAAgQFBvkQ/fv+/f37+fv7+/v++/ng2b79/fz7/f35vfz7+0UV/fz8+/sj+Qv+/f38ek0uF/jtqIx1cGZfUU8eF/39/PTj4tvOy8Ozp5OIhnl4b2JQT0tGRDc1HA358O7p5NjU0sW6q6V5eG9rZlRDPDIuE/f26+ba2tHLx7+9urWuoaCamJeXlZGLin5kYllXKe/u6unf3dXTz8/Ix8XBurmdnJqVfnNZVQrS8AAABtNJREFUSMfllndQE0EUxveSCwES0rBEAggoRQRBwYqKvffee++99957712pwUgkVAGlSUdQsYJSLAj2ruPbC2HvxPFvZ9xMkne7+7tv79379g79K42ieHw+j6L+OMDjUX9DeYaITOMeUpV5gtIe/bZv7+cBM/gUQeHrOai5V/NBQ5kj0thTPDZ2SFSr1QU+HZs4I2Sg+QjNON0uQqfVRbRb3pwiNJulNyYkZyiVUqlxSOQdcU+9Bv4dfCT7bbytrURS77FOu2gQqpQSPnJenBNm4eDgIAXaWOD7qbMRdFIw0etZsZNTW1ugrSUS6wjdlN9pCnnvjW4YZhGWlpx2+3ZBa+PWgjtVayMKPk3vJjklOX3Pzn6r1UZIHksk2q2I4rK8xdENQ0XpCafq79xZv0vkdWMfwZWqnsgFNX1jaZmUdHffWa8ZXhvaax/Xq2etbY74nAvu/hnY6BMe+uPBY9UCH8GDhWOrds58ZWn56llPF6afnvI4XlIv3noo0YbIWaEwNY2eBCEfqgRWW/OqwEcc9+Dr++Bgy8zM/pAUPACntY4H6XMgR7LVI1elergMR3oFhGqqjX3sbS7bBAdnZg5CNKVXodGMCEh6O0+DNP4/9FCleDQEWNLVBa7b9/Lly8HBAxDN0lmlk0h0cNVk1Y4aECYXgrWb3Db2wXTcSBfErrYBOom1dgMEho6BUY7y3B4QEAWApSFA28ctdOHcF8928da65RAY4BZBjvLwaSyYAlgtDQHaPm7kUMRnwS7tI6wjlkJEYHdHBiYeqZ+m/BgCteYjfgDVwmfD8RimKuBZH4RVUrpDQNhkh4/KELX6OtBXME3yYxtvq1vFynYrsx+yqGps3RyLMIeM+Y3XdMbVQmj488q2tdVuZa/l+AehLOgSog1stCjMIjGxFhxBtRAa6x0ubtv27QDEI6ltFmgmjGrUCiLM9ooWiUQJCbUQTXNpyGPPu05ti9vTiCLKwxbkCYUfqhkhmgfsPVORKBSzkA9CwxgfPAYWy56C+Gxj9A00868SBDRCvXJVpqahoZiFRjG0L+MxBKxlklPxIh4jTLQnYjplSa+1Ex8qFKoyPcuhO25at+k9eCzp+2BQ48CoOtCyqJTwWLlG8agMs2RzWwG0OO7OmzfB2J/9DYsmM4AW+gtljnKNRqMoZ4m2Wuxrb2ODPUZYDj3hhom/n9/Nm/LYvhUs8ZgYTII91p+McbZAExN/TJMyJx5bd5UxqM37jiRXHOlWw5+Y+Af4+cnCL3BhOGqsZgxq87Ujjw2Tmpz8EqRBWxbUArS4q1qjZgxKPMZpwD61emICLUAYtQT2bA7bJ0MZEmLc2t7XhniMy9awsnpyIy9PGCBLqYZpsqY+yR+VypDIyEixL+Mx6rd1Tb9fo0aNa8PHLdhzQxiAa41sNMDCoyRR2WFUB8i54MpYqGyO7uwsV2CvDURGwyYEmulpvmHNOdhjGf0oI5qptU+N2TBFoWP37VyBZeSqExqveSr2Z8K3fszUFWqBr80VliMhalBk5+qa1QDRMJvQjI+mYo8lfGuJaAqPjS8Qix+M5+ynq4vs7O5vARYam0aYBY+ltzSMec73sbeP82DRw/aXuv0cMRd6uPTarpMeKRSExbXW86pYfKcp4pN0lbrVLeqGKTYtiwqPjZUrFGWPKlg4/ZBIePxCysglv3Crm9obQ4R+JwwQyuRyjVxDWAwbjYoUFIxnbb27YszNU6dDwKbhfoPFwKC4WAlMd4g0vt4ZIqIMMCizfdQj0AxbzD2/Ea4JtnKG9HoXlvLs123MY1Zzn1Wbv5hhgwJsxIGHZCil6sbsbI8uMb9VtxWrhwZ4OGPvfLwjs5+eyVLp7Yuki4e6FdYxjzlPcgo7+UvGY0I/9yhcLRWst0WiUpkI95nAM5/XudXmxQ6gKEBharOnVthjN7DH9LVGQYHRiNcpzcIhrSanuNFJoG+V9DZ0bMH+vGY1btxB7LHYA0PK+507pVuEWaTVYqUHwt2vX4N2zNHpu+fOndNsDOPPly2g0qu/w9WSP2mgt5F3rfWm6aKwhjlnAOD4eVch0OYvUt1GjPhZlGXnCmwDRDH7eRU/95vhsfMazXt4L9RU1DCnE8XxM9be8bykTh1z8zalpW520LJcG5T7aEKgUObu6KjJ16hU8ML1uZMLYUnSRheWAF0XmlvW/TFz9HcD6B557344QqXKVaqye+nrEWFZK6e3jX5eGBMTk5qaNaYZ0rPMzDkTq6SkBMWG5+aqus6qzJZr0DO3dVu5sltvmMF5QUHefbt3rd61e19n8tZfWZyEnC2OJJfiolx1Pg8av9LZKdzHvJX+7+0X6aNkPIGC7jsAAAAASUVORK5CYII=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Affinity", + "typeVersion": 1 + }, + { + "id": 21, + "icon": "file:comprehend.svg", + "name": "n8n-nodes-base.awsComprehend", + "defaults": { + "name": "AWS Comprehend" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3NSA3NSI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNjE3LjQ2IiB5MT0iLTY3NC41MyIgeDI9IjcyMy41MyIgeTI9Ii01NjguNDYiIGdyYWRpZW50VHJhbnNmb3JtPSJyb3RhdGUoLTkwIDY4My41IDI0LjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMDU1ZjRlIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTZjMGE3Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZGF0YS1uYW1lPSJUdXJxdW9pc2UgR3JhZGllbnQiIGQ9Ik0wIDBoNzV2NzVIMHoiIGZpbGw9InVybCgjYSkiLz48cGF0aCBkPSJNNDQuNSAzNC4ydi05LjdhMSAxIDAgMDAtLjI5LS43MWwtMTEtMTFhMSAxIDAgMDAtLjcxLS4yOWgtMTlhMSAxIDAgMDAtMSAxdjQzYTEgMSAwIDAwMSAxaDMwYTEgMSAwIDAwMS0xdi00LjQ0YTExLjggMTEuOCAwIDAxLTItMi4zdjUuNzRoLTI4di00MWgxN3YxMGExIDEgMCAwMDEgMWgxMHYxMWExMS41NiAxMS41NiAwIDAxMi0yLjN6bS0xMS0xMC43di03LjU5bDcuNTkgNy41OXptLTEwIDhoLTZ2LTJoNnptMTYgMGgtMTR2LTJoMTR6bTAgNmgtMjJ2LTJoMjJ6bTE1LjQ0IDI1aC00Ljg4YTEgMSAwIDAxLS45My0uNjJsLTEuMjEtM2ExIDEgMCAwMS4wOS0uOTQgMSAxIDAgMDEuODMtLjQ0aDcuMzJhMSAxIDAgMDEuODMuNDQgMSAxIDAgMDEuMDkuOTRsLTEuMjEgM2ExIDEgMCAwMS0uOTMuNjJ6bS00LjIxLTJoMy41NGwuNC0xaC00LjM0em0xMS42NC0xOWExMCAxMCAwIDAwLTE5Ljg3IDEuNjIgMTAgMTAgMCAwMDQuMjggOC4yIDMuODggMy44OCAwIDAxLjcyLjU5djMuNTlhMSAxIDAgMDAxIDFoOGExIDEgMCAwMDEtMXYtMy42YTQuMzMgNC4zMyAwIDAxLjcxLS41NyA5LjkyIDkuOTIgMCAwMDQuMjktOC4yIDEwLjE5IDEwLjE5IDAgMDAtLjEzLTEuNjV6bS01LjMyIDguMmMtLjU4LjQtMS41NSAxLjA3LTEuNTUgMi4xdjIuN2gtMnYtN2gydi0yaC02djJoMnY3aC0ydi0yLjY4YzAtMS0xLTEuNzMtMS41OC0yLjE0QTggOCAwIDExNTggMzcuMzJhNy44OSA3Ljg5IDAgMDEyLjM5IDQuNDcgOCA4IDAgMDEtMy4zNCA3Ljkxek0yOC41IDI1LjVoLTExdi0yaDExem0xIDE4aC0xMnYtMmgxMnptMTAgMGgtOHYtMmg4em0tOSA2aC0xM3YtMmgxM3oiIGRhdGEtbmFtZT0iSWNvbiBUZXN0IiBmaWxsPSIjZmZmIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Comprehend", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 16, + "name": "Create dynamic Twitter Profile Banner", + "totalViews": 120000, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2022-01-10T14:15:25.921Z", + "nodes": [] + }, + { + "id": 1073, + "name": "Scrape and Store Data from Multiple Pages Websites", + "totalViews": 2476, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-05-07T14:48:56.297Z", + "nodes": [ + { + "id": 13, + "icon": "file:asana.svg", + "name": "n8n-nodes-base.asanaTrigger", + "defaults": { + "name": "Asana Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGN4PSI1MCUiIGN5PSI1NSUiIGZ4PSI1MCUiIGZ5PSI1NSUiIHI9IjcyLjUwNyUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjkyNDA0IDAgMCAxIC4wMzggMCkiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjRkZCOTAwIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0Y5NUQ4RiIgb2Zmc2V0PSI2MCUiLz48c3RvcCBzdG9wLWNvbG9yPSIjRjk1MzUzIiBvZmZzZXQ9Ijk5LjkxJSIvPjwvcmFkaWFsR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik00NS41OTQgMjguNWMtNi45OTQuMDAzLTEyLjY2NCA1LjY3My0xMi42NjcgMTIuNjY3LjAwMyA2Ljk5NSA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2OCA2Ljk5NS0uMDA0IDEyLjY2NC01LjY3MyAxMi42NjctMTIuNjY4LS4wMDMtNi45OTQtNS42NzItMTIuNjY0LTEyLjY2Ny0xMi42Njd6bS0zMi45MjcuMDAxQzUuNjczIDI4LjUwNS4wMDMgMzQuMTc0IDAgNDEuMTdjLjAwMyA2Ljk5NCA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2NyA2Ljk5NS0uMDAzIDEyLjY2NC01LjY3MyAxMi42NjgtMTIuNjY3LS4wMDQtNi45OTUtNS42NzMtMTIuNjY0LTEyLjY2OC0xMi42Njh6TTQxLjc5IDEyLjY2N2MtLjAwMiA2Ljk5NS01LjY3MSAxMi42NjUtMTIuNjY2IDEyLjY3LTYuOTk1LS4wMDQtMTIuNjY0LTUuNjc0LTEyLjY2Ny0xMi42N0MxNi40NiA1LjY3MyAyMi4xMy4wMDMgMjkuMTIzIDBjNi45OTQuMDA0IDEyLjY2MyA1LjY3MyAxMi42NjYgMTIuNjY3eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjczMiAyLjczMikiIGZpbGw9InVybCgjYSkiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Asana Trigger", + "typeVersion": 1 + }, + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 15, + "icon": "file:affinity.png", + "name": "n8n-nodes-base.affinity", + "defaults": { + "name": "Affinity" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Affinity", + "typeVersion": 1 + }, + { + "id": 19, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilotTrigger", + "defaults": { + "name": "Autopilot Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot Trigger", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 31, + "icon": "file:bitwarden.svg", + "name": "n8n-nodes-base.bitwarden", + "defaults": { + "name": "Bitwarden" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48cGF0aCBkPSJNNTMuMzMzIDIuNjY3djMyYzAgMi4zODgtLjQ2NSA0Ljc1Ni0xLjM5NiA3LjEwM3MtMi4wODQgNC40My0zLjQ1OCA2LjI1LTMuMDE1IDMuNTktNC45MTcgNS4zMTItMy42NiAzLjE1My01LjI3MiA0LjI5MmwtNS4wNCAzLjIzLTMuNzMgMi4wNjItMS43Ny44MzRjLS4zMzMuMTY2LS42OTUuMjUtMS4wODMuMjVhMi40IDIuNCAwIDAxLTEuMDgzLS4yNWwtMS43Ny0uODM0LTMuNzMtMi4wNjItNS4wNDItMy4yM2MtMS42MS0xLjE0LTMuMzY4LTIuNTctNS4yNy00LjI5MnMtMy41NC0zLjQ5Mi00LjkxNi01LjMxMi0yLjUyOC0zLjkwMy0zLjQ2LTYuMjVTMCAzNy4wNTUgMCAzNC42Njd2LTMyQTIuNTYgMi41NiAwIDAxLjc5MS43OTIgMi41NiAyLjU2IDAgMDEyLjY2NiAwaDQ4Yy43MiAwIDEuMzQ2LjI2NCAxLjg3NC43OTJhMi41NiAyLjU2IDAgMDEuNzkyIDEuODc1bS04IDMyVjhIMjYuNjY2djQ3LjM3NWMzLjMwNS0xLjc1IDYuMjY0LTMuNjUzIDguODc1LTUuNzA4IDYuNTI3LTUuMTEgOS43OS0xMC4xMSA5Ljc5LTE1IiBzdHJva2U9Im5vbmUiIGZpbGw9IiMzYzhkYmMiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Bitwarden", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 39, + "icon": "file:circleCi.png", + "name": "n8n-nodes-base.circleCi", + "defaults": { + "name": "CircleCI" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAMAAABG8BK2AAAAmVBMVEUAAAAVFBUEAwQVExUAAAAAAAAVFBUWExYVFBUUFBQcHBwVFBUUFBQVExUVERURDxEVExUUExQVFBUUFBQVFBUVFBUUEhQUFBQVExUUFBQVFBUVExUUExQVExUWFhYZGRkVFRUVFBUVFBUVFBUVFBUVFBUUFBQWFBYWFhYTExMUFBQTExMUFBQVFBUVExUVFBUWFRYVFBUVFBX33+L1AAAAMnRSTlMA/QX5AwL7adxiCHqYux8QqUHn4cuOKZ9t79fEr4UiFAy19eyUgHJGLhpLNTPPWlI54+oyz24AAAM7SURBVFjDlJWJduIwDEUly3ZD2MtaylrWlnbaef//cYNLIIntAPNyTuDYyZWebCsUlVHurubdWn3QGDUG9Vq3pX+HDD0s7d44jKeJBZhxvknSGQ8dSD8Omde/4MRyEcOpX289CFJEPx3rwgszcjGLddzOkUg9ADmsABQQZdTpPh3eAylKNy4kI65sbpDe4hii/bOLeFsC7nXJmGpDM2cH9+QCbauMKVJNZsYjYuapIRWlLCZBUW6V6GtJKkbpQfC4BEk74GhaJh7lPmeSkiZP/YByn/MaJNNhwf9KeFSypeipisJnVXFqBY6mIcAxhFicZSVGYsg8L4+h11hhLON0rZNJsob7Z2PlaZK5WqpFLDkjyeZ72Fak2sPvTeKGIpw/V1vLZ3A4j9W+tAofTUBCWz11SaYeJMOWJ103p0zWVN3D+wSWgyrvSFUkw4yGY1BRSpMeBcYYf9NssSH+FMYUPTC0CziCbNH7YC8XN2PiH4wnn2OzvfyJgF8nVdmTZhA/9QNpohmLR1kVT5zRSpc6ZNPjCLuovifG23uOycqckzS1xH/h9QRvMYtYJ/m93hy8UNTFsfvZpuLQliWIS2niFbiX5gboZ/oCYN05Fly1X/x0Pojo/amsFuWLNABwPt+zQpUbkHJxto7uy1x/R8zC587LDdIXTLe86ILOeaOXlJ/Ycd7iOW8thtLytrfoU6h4DRjPaZanpiZs+XimN77DNUi0JSgasJQwL4sbmJGHaVwxOw8jrWpTXurWbe5ongye/6vF2nEQhmFoTRxPsCGBuEAlxJT7H45QF6UiH6d9wUvVJamT+v3+vQzeVHFPsY64/8Kn+oXjvx+FqzUM4iiWOQz5aN6PjOYooFDYSpXD1qsHtgwQZdaHCaI2pDN7E9L3EwyVCSZ2JQW6m6p0FzK684PItyUFuFcKNIRJeMwL27FX5vws+awJk6ZMusx+h0zCRVtTQtKPhJSahLQEbdgAhS1ocXmdvud2SOwrPOPWY7QRwm0ZbhJByyrJssIGmmshx2rnyepH7TweLsQDQKIOsaMOLf8NXsgJEW2HQlwevNgxEOn2WrK8aQzEvXnSaQ2lHBBKZRHZuSMiewPdyr8Dzj/WNwAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "CircleCI", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + }, + { + "id": 46, + "icon": "fa:file-archive", + "name": "n8n-nodes-base.compression", + "defaults": { + "name": "Compression", + "color": "#408000" + }, + "iconData": { + "icon": "file-archive", + "type": "icon" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Compression", + "typeVersion": 1 + }, + { + "id": 59, + "icon": "fa:clock", + "name": "n8n-nodes-base.dateTime", + "defaults": { + "name": "Date & Time", + "color": "#408000" + }, + "iconData": { + "icon": "clock", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Date & Time", + "typeVersion": 2 + }, + { + "id": 114, + "icon": "file:helpScout.svg", + "name": "n8n-nodes-base.helpScoutTrigger", + "defaults": { + "name": "HelpScout Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjMxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTE4LjQzMiAxODAuOTY5bDkwLjQ4NC05MC40ODVhNjMuNzE3IDYzLjcxNyAwIDAwMTguOTktNDUuNDI4QTY0LjI0NSA2NC4yNDUgMCAwMDEwOS40NzYgMEwxOC45OSA5MC40ODRBNjMuNzE3IDYzLjcxNyAwIDAwMCAxMzUuOTEzYzAgMTcuNjg3IDcuMDc1IDMzLjUxMiAxOC40MzIgNDUuMDU2em0yMTkuMTM2LTUyLjg3NmwtOTAuNDg0IDkwLjQ4NGE2My43MTcgNjMuNzE3IDAgMDAtMTguOTkgNDUuNDI5IDY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMSA0NS4wNTZsOTAuNDg0LTkwLjQ4NUE2My43MTcgNjMuNzE3IDAgMDAyNTYgMTczLjE1YzAtMTcuNjg3LTcuMDc1LTMzLjUxMy0xOC40MzItNDUuMDU2em0tLjU1OS0zNy40MjJBNjMuNzE3IDYzLjcxNyAwIDAwMjU2IDQ1LjI0MiA2NC4yNDUgNjQuMjQ1IDAgMDAyMzcuNTY4LjE4NkwxOC45OTEgMjE4LjU3N0M3LjI2IDIzMC4zMDcgMCAyNDYuMzIgMCAyNjQuMTkyYTY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMiA0NS4wNTZMMjM3LjAwOSA5MC42NzF6IiBmaWxsPSIjMTI5MkVFIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "HelpScout Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 226, + "name": "Receive Google Sheet data via REST API", + "totalViews": 2438, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-01-01T19:27:20.732Z", + "nodes": [ + { + "id": 18, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilot", + "defaults": { + "name": "Autopilot" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot", + "typeVersion": 1 + }, + { + "id": 47, + "icon": "file:coda.svg", + "name": "n8n-nodes-base.coda", + "defaults": { + "name": "Coda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA2MCA2MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNjAgNjA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRUU1QTI5O30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQ1LjIsMTYuMmMzLjMsMCw2LjUsMS4zLDguOCwzLjNjMS41LDEuMywzLjgsMC4yLDMuOC0xLjhWNC43YzAtMi41LTIuMS00LjctNC43LTQuN0g2LjMKCUMzLjcsMCwxLjYsMi4xLDEuNiw0Ljd2NTAuN2MwLDIuNSwyLjEsNC42LDQuNyw0LjZoNDYuOGMyLjUsMCw0LjctMi4xLDQuNy00LjdWNDIuM2MwLTItMi4zLTMuMS0zLjgtMS44Yy0yLjQsMi4xLTUuNCwzLjMtOC44LDMuMwoJYy03LjYsMC0xMy43LTYuMi0xMy43LTEzLjhDMzEuNiwyMi40LDM3LjcsMTYuMiw0NS4yLDE2LjJ6Ii8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Coda", + "typeVersion": 1 + } + ] + }, + { + "id": 156, + "name": "Get Execute Command Data and Transfer to JSON", + "totalViews": 1855, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2019-11-02T12:18:37.963Z", + "nodes": [ + { + "id": 13, + "icon": "file:asana.svg", + "name": "n8n-nodes-base.asanaTrigger", + "defaults": { + "name": "Asana Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGN4PSI1MCUiIGN5PSI1NSUiIGZ4PSI1MCUiIGZ5PSI1NSUiIHI9IjcyLjUwNyUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjkyNDA0IDAgMCAxIC4wMzggMCkiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjRkZCOTAwIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0Y5NUQ4RiIgb2Zmc2V0PSI2MCUiLz48c3RvcCBzdG9wLWNvbG9yPSIjRjk1MzUzIiBvZmZzZXQ9Ijk5LjkxJSIvPjwvcmFkaWFsR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik00NS41OTQgMjguNWMtNi45OTQuMDAzLTEyLjY2NCA1LjY3My0xMi42NjcgMTIuNjY3LjAwMyA2Ljk5NSA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2OCA2Ljk5NS0uMDA0IDEyLjY2NC01LjY3MyAxMi42NjctMTIuNjY4LS4wMDMtNi45OTQtNS42NzItMTIuNjY0LTEyLjY2Ny0xMi42Njd6bS0zMi45MjcuMDAxQzUuNjczIDI4LjUwNS4wMDMgMzQuMTc0IDAgNDEuMTdjLjAwMyA2Ljk5NCA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2NyA2Ljk5NS0uMDAzIDEyLjY2NC01LjY3MyAxMi42NjgtMTIuNjY3LS4wMDQtNi45OTUtNS42NzMtMTIuNjY0LTEyLjY2OC0xMi42Njh6TTQxLjc5IDEyLjY2N2MtLjAwMiA2Ljk5NS01LjY3MSAxMi42NjUtMTIuNjY2IDEyLjY3LTYuOTk1LS4wMDQtMTIuNjY0LTUuNjc0LTEyLjY2Ny0xMi42N0MxNi40NiA1LjY3MyAyMi4xMy4wMDMgMjkuMTIzIDBjNi45OTQuMDA0IDEyLjY2MyA1LjY3MyAxMi42NjYgMTIuNjY3eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjczMiAyLjczMikiIGZpbGw9InVybCgjYSkiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Asana Trigger", + "typeVersion": 1 + }, + { + "id": 15, + "icon": "file:affinity.png", + "name": "n8n-nodes-base.affinity", + "defaults": { + "name": "Affinity" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Affinity", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + } + ] + }, + { + "id": 1, + "name": "Excel to Postgres", + "totalViews": 1757, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2019-08-31T00:05:02.587Z", + "nodes": [ + { + "id": 30, + "icon": "file:bitly.svg", + "name": "n8n-nodes-base.bitly", + "defaults": { + "name": "Bitly" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMjh2MTI4SDB6Ii8+PHBhdGggZD0iTTYzLjcwOCAwQzI4LjQ0MyAwIDAgMjguMzA2IDAgNjUuMzIyYzAgMTkuMTkzIDEwLjI2NiAzNy43MzYgMjQuNzAzIDQ4Ljg3NyAyLjc4IDIuMTQ1IDYuMDkxIDEuOTQ5IDcuOTg5LjEwOSAxLjU5OS0xLjU1IDEuNDY3LTUuMjgyLTEuNTA3LTcuODk3LTExLjU0MS0xMC4xNDktMTkuNTItMjUuNTE0LTE5LjUyLTQwLjg2MSAwLTI3LjU2NCAyNC40NzQtNTAuNzU2IDUyLjA0NC01MC43NTYgMzMuNTU4IDAgNTEuNTAzIDI3LjI1MiA1MS41MDMgNTAuMzUxIDAgMTQuMTE0LTYuOTAyIDMxLjE1NS0xOS4zNjEgNDIuMDI1LjAxOS0uMDQ5IDIuNTg4LTUuMTAxIDIuNTg4LTE0Ljk0IDAtMTYuNzQ4LTEwLjYxMi0yNS44MjEtMjIuOTI2LTI1LjgyMS04LjkxNCAwLTE0LjI1MSAzLjE4Ny0xNy44ODMgNi4xNTggMC02LjgyMi4yMjgtMTkuNTYzLjIyOC0xOS41NjMgMC04LjQwOS0yLjk0Ni0xNS4xNC0xMy4yMTMtMTUuMjg3LTUuOTQzLS4wODQtMTAuMzUzIDIuNjQxLTEzLjEwMyA4LjgwMy0uOTkxIDIuMzExLS42MjYgNC44MjIgMS4zMzMgNS45NiAxLjYyMS45NDEgNC4yODcuMjQzIDUuNjA2LTEuNTE3Ljg4MS0xLjEgMS4zNzUtMS4zMzUgMi4xNDEtMS4yNTIgMS4yNjEuMTM2IDEuMzA5IDIuMTY4IDEuMzU5IDMuNDY4LjAzOC45OTggMS4wMDggMTUuNDk0LjQ3NyA1Mi43NDYgMCAxMC4yNzUgOC4wNTkgMjIuMDc3IDI3LjM1NSAyMi4wNzcgOC4zMDcgMCAxNC42ODYtMi4zMjIgMjMuOTUzLTcuNTg3QzEwNy44NzEgMTEyLjM5OCAxMjggOTQuNjIgMTI4IDY0LjU1IDEyOCAyNi45NzcgOTcuNTcyIDAgNjMuNzA4IDB6bTYuMDUyIDExMy42MDJjLTcuMTgzLjEyNi0xMi40MjItMi4xODMtMTIuNzQzLTguNDYxLS4xMi0yLjM1Ni0uMDgyLTQuODY1LjAzMy02LjM3OC42OTUtOS4xNzQgNy4xMS0xNS43NzQgMTMuNDQzLTE3LjA0MSA3Ljg3Ni0xLjU3NiAxMy4xMjMgMi4wMjYgMTMuMTIzIDEyLjMzNy0uMDAxIDYuOTY4LTEuOTM1IDE5LjMzNC0xMy44NTYgMTkuNTQzeiIgZmlsbD0iI0RENUEyQiIvPjwvZz48L3N2Zz4=" + }, + "categories": [ + { + "id": 7, + "name": "Utility" + } + ], + "displayName": "Bitly", + "typeVersion": 1 + }, + { + "id": 31, + "icon": "file:bitwarden.svg", + "name": "n8n-nodes-base.bitwarden", + "defaults": { + "name": "Bitwarden" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48cGF0aCBkPSJNNTMuMzMzIDIuNjY3djMyYzAgMi4zODgtLjQ2NSA0Ljc1Ni0xLjM5NiA3LjEwM3MtMi4wODQgNC40My0zLjQ1OCA2LjI1LTMuMDE1IDMuNTktNC45MTcgNS4zMTItMy42NiAzLjE1My01LjI3MiA0LjI5MmwtNS4wNCAzLjIzLTMuNzMgMi4wNjItMS43Ny44MzRjLS4zMzMuMTY2LS42OTUuMjUtMS4wODMuMjVhMi40IDIuNCAwIDAxLTEuMDgzLS4yNWwtMS43Ny0uODM0LTMuNzMtMi4wNjItNS4wNDItMy4yM2MtMS42MS0xLjE0LTMuMzY4LTIuNTctNS4yNy00LjI5MnMtMy41NC0zLjQ5Mi00LjkxNi01LjMxMi0yLjUyOC0zLjkwMy0zLjQ2LTYuMjVTMCAzNy4wNTUgMCAzNC42Njd2LTMyQTIuNTYgMi41NiAwIDAxLjc5MS43OTIgMi41NiAyLjU2IDAgMDEyLjY2NiAwaDQ4Yy43MiAwIDEuMzQ2LjI2NCAxLjg3NC43OTJhMi41NiAyLjU2IDAgMDEuNzkyIDEuODc1bS04IDMyVjhIMjYuNjY2djQ3LjM3NWMzLjMwNS0xLjc1IDYuMjY0LTMuNjUzIDguODc1LTUuNzA4IDYuNTI3LTUuMTEgOS43OS0xMC4xMSA5Ljc5LTE1IiBzdHJva2U9Im5vbmUiIGZpbGw9IiMzYzhkYmMiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Bitwarden", + "typeVersion": 1 + }, + { + "id": 41, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUp", + "defaults": { + "name": "ClickUp" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 980, + "name": "Loading Data Into Spreadsheet or Database", + "totalViews": 1553, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-03-11T11:20:25.787Z", + "nodes": [ + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 26, + "icon": "file:sns.svg", + "name": "n8n-nodes-base.awsSnsTrigger", + "defaults": { + "name": "AWS SNS Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTk4Ljg3NSAyMzIuMDMzbC0yNi40MzMtNy40MDgtMjUuMDAxLTI4LjUwOCAzMS4yNzItLjg2MyAyMC4xNjIgMzYuNzc5bS02MS4xMjUtMTguOGwtMTQuODc1LTQuMTY2LTE0LjA1OC0xNi4wMzQgMTcuMDgyLTIuODA5IDExLjg1MSAyMy4wMDkiIGZpbGw9IiM5OTVCODAiLz48cGF0aCBkPSJNMCAxOTEuMDE3bDE1LjIwNCAzLjA5MSAyLjIwNy0zLjg4MlY1OC41MDNsLTIuMjA3LTIuNTYxTDAgNjQuNnYxMjYuNDE3IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTczLjkzMyA2OS43MDhMMTUuMjA4IDU1Ljk0MnYxMzguMTY2bDguNzk4LS44MTggMTMuNzQ0IDE5Ljk0MyAxMC42LTIyLjIwNSAyNS41ODMtMi4zNzhWNjkuNzA4IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTMzLjk1OCAxOTguMTMzbDI2LjA2MyA1LjI1IDEuNzE2LTQuMDQ1VjM3LjQ0bC0xLjcxNi0zLjY2NS0yNi4wNjMgMTMuMjA4djE1MS4xNSIgZmlsbD0iIzdCM0Y2NSIvPjxwYXRoIGQ9Ik0yMDguNzM0IDgxLjUxNkw2MC4wMjEgMzMuNzc1djE2OS42MTJsMTcuMjIxLTIuMjE2IDIxLjYzMyAzMC44NjIgMTcuMTI2LTM1Ljg1IDkyLjczMy0xMS45MzNWODEuNTE2IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTE4MS44MzMgMjU2LjQ5MmwtMzcuNTY2LTEwLjUyNS0zNS41MDktNDAuNSA0Ni4wMzMtLjQ2OCAyNy4wNDIgNTEuNDkzIiBmaWxsPSIjOTk1QjgwIi8+PHBhdGggZD0iTTg5LjU5MSAyMDguOTVsMzguMzMgNy40MTcgMi45NzctMi41NjZWNC4xMTdMMTI3LjkyMSAwbC0zOC4zMyAxOS4xNThWMjA4Ljk1IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTI1NiA2NC4wMzNMMTI3LjkyNSAwdjIxNi4zNjdsMjIuNTk3LTQuNTI4IDMxLjMxMSA0NC42NTMgMjYuOTAxLTU2LjMwOS0uMDE3LS4wMDJMMjU2IDE5MC43MDhWNjQuMDMzIiBmaWxsPSIjQzE3QjlEIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AWS SNS Trigger", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 225, + "name": "Trending \"Show HN\" to email", + "totalViews": 1459, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-01-01T18:56:31.672Z", + "nodes": [ + { + "id": 7, + "icon": "file:airtable.svg", + "name": "n8n-nodes-base.airtable", + "defaults": { + "name": "Airtable" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcwIj48cGF0aCBkPSJNODkgNC44TDE2LjIgMzQuOWMtNC4xIDEuNy00IDcuNC4xIDkuMWw3My4yIDI5YzYuNCAyLjYgMTMuNiAyLjYgMjAgMGw3My4yLTI5YzQuMS0xLjYgNC4xLTcuNC4xLTkuMWwtNzMtMzAuMUMxMDMuMiAyIDk1LjcgMiA4OSA0LjgiIGZpbGw9IiNmY2I0MDAiLz48cGF0aCBkPSJNMTA1LjkgODguOXY3Mi41YzAgMy40IDMuNSA1LjggNi43IDQuNWw4MS42LTMxLjdjMS45LS43IDMuMS0yLjUgMy4xLTQuNVY1Ny4yYzAtMy40LTMuNS01LjgtNi43LTQuNUwxMDkgODQuM2MtMS45LjgtMy4xIDIuNi0zLjEgNC42IiBmaWxsPSIjMThiZmZmIi8+PHBhdGggZD0iTTg2LjkgOTIuNmwtMjQuMiAxMS43LTIuNSAxLjJMOS4xIDEzMGMtMy4yIDEuNi03LjQtLjgtNy40LTQuNFY1Ny41YzAtMS4zLjctMi40IDEuNi0zLjMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS41IDQuMyA3LjEuNSA4LjkiIGZpbGw9IiNmODJiNjAiLz48cGF0aCBkPSJNODYuOSA5Mi42bC0yNC4yIDExLjctNTkuNC01MGMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS40IDQuMyA3IC41IDguOCIgZmlsbD0iI2JhMWU0NSIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Airtable", + "typeVersion": 2 + }, + { + "id": 11, + "icon": "file:amqp.png", + "name": "n8n-nodes-base.amqpTrigger", + "defaults": { + "name": "AMQP Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AMQP Trigger", + "typeVersion": 1 + }, + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 19, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilotTrigger", + "defaults": { + "name": "Autopilot Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot Trigger", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 114, + "icon": "file:helpScout.svg", + "name": "n8n-nodes-base.helpScoutTrigger", + "defaults": { + "name": "HelpScout Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjMxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTE4LjQzMiAxODAuOTY5bDkwLjQ4NC05MC40ODVhNjMuNzE3IDYzLjcxNyAwIDAwMTguOTktNDUuNDI4QTY0LjI0NSA2NC4yNDUgMCAwMDEwOS40NzYgMEwxOC45OSA5MC40ODRBNjMuNzE3IDYzLjcxNyAwIDAwMCAxMzUuOTEzYzAgMTcuNjg3IDcuMDc1IDMzLjUxMiAxOC40MzIgNDUuMDU2em0yMTkuMTM2LTUyLjg3NmwtOTAuNDg0IDkwLjQ4NGE2My43MTcgNjMuNzE3IDAgMDAtMTguOTkgNDUuNDI5IDY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMSA0NS4wNTZsOTAuNDg0LTkwLjQ4NUE2My43MTcgNjMuNzE3IDAgMDAyNTYgMTczLjE1YzAtMTcuNjg3LTcuMDc1LTMzLjUxMy0xOC40MzItNDUuMDU2em0tLjU1OS0zNy40MjJBNjMuNzE3IDYzLjcxNyAwIDAwMjU2IDQ1LjI0MiA2NC4yNDUgNjQuMjQ1IDAgMDAyMzcuNTY4LjE4NkwxOC45OTEgMjE4LjU3N0M3LjI2IDIzMC4zMDcgMCAyNDYuMzIgMCAyNjQuMTkyYTY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMiA0NS4wNTZMMjM3LjAwOSA5MC42NzF6IiBmaWxsPSIjMTI5MkVFIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "HelpScout Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 524, + "name": "Get today's date and day using the Function node", + "totalViews": 1354, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-07-16T09:26:32.454Z", + "nodes": [ + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + } + ] + } + ], + "filters": [ + { + "counts": [ + { + "count": 66, + "highlighted": "Building Blocks", + "value": "Building Blocks" + }, + { + "count": 24, + "highlighted": "Sales", + "value": "Sales" + }, + { + "count": 24, + "highlighted": "Marketing & Growth", + "value": "Marketing & Growth" + }, + { + "count": 16, + "highlighted": "DevOps & IT", + "value": "DevOps & IT" + }, + { + "count": 12, + "highlighted": "HR & People Ops", + "value": "HR & People Ops" + }, + { + "count": 9, + "highlighted": "Development", + "value": "Development" + }, + { + "count": 9, + "highlighted": "Managed Service Providers", + "value": "Managed Service Providers" + }, + { + "count": 7, + "highlighted": "Finance & Accounting", + "value": "Finance & Accounting" + }, + { + "count": 6, + "highlighted": "Product & Project Management", + "value": "Product & Project Management" + }, + { + "count": 3, + "highlighted": "Customer Service", + "value": "Customer Service" + } + ], + "field_name": "categories", + "sampled": false, + "stats": { + "total_values": 11 + } + } + ] +} diff --git a/cypress/fixtures/templates_search/sales_templates_search_response.json b/cypress/fixtures/templates_search/sales_templates_search_response.json new file mode 100644 index 0000000000000..4efbb3585b504 --- /dev/null +++ b/cypress/fixtures/templates_search/sales_templates_search_response.json @@ -0,0 +1,1316 @@ +{ + "totalWorkflows": 24, + "workflows": [ + { + "id": 837, + "name": "Automating Products Price Changes Tracking", + "totalViews": 1343, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-12-19T10:26:27.570Z", + "nodes": [ + { + "id": 7, + "icon": "file:airtable.svg", + "name": "n8n-nodes-base.airtable", + "defaults": { + "name": "Airtable" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcwIj48cGF0aCBkPSJNODkgNC44TDE2LjIgMzQuOWMtNC4xIDEuNy00IDcuNC4xIDkuMWw3My4yIDI5YzYuNCAyLjYgMTMuNiAyLjYgMjAgMGw3My4yLTI5YzQuMS0xLjYgNC4xLTcuNC4xLTkuMWwtNzMtMzAuMUMxMDMuMiAyIDk1LjcgMiA4OSA0LjgiIGZpbGw9IiNmY2I0MDAiLz48cGF0aCBkPSJNMTA1LjkgODguOXY3Mi41YzAgMy40IDMuNSA1LjggNi43IDQuNWw4MS42LTMxLjdjMS45LS43IDMuMS0yLjUgMy4xLTQuNVY1Ny4yYzAtMy40LTMuNS01LjgtNi43LTQuNUwxMDkgODQuM2MtMS45LjgtMy4xIDIuNi0zLjEgNC42IiBmaWxsPSIjMThiZmZmIi8+PHBhdGggZD0iTTg2LjkgOTIuNmwtMjQuMiAxMS43LTIuNSAxLjJMOS4xIDEzMGMtMy4yIDEuNi03LjQtLjgtNy40LTQuNFY1Ny41YzAtMS4zLjctMi40IDEuNi0zLjMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS41IDQuMyA3LjEuNSA4LjkiIGZpbGw9IiNmODJiNjAiLz48cGF0aCBkPSJNODYuOSA5Mi42bC0yNC4yIDExLjctNTkuNC01MGMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS40IDQuMyA3IC41IDguOCIgZmlsbD0iI2JhMWU0NSIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Airtable", + "typeVersion": 2 + }, + { + "id": 11, + "icon": "file:amqp.png", + "name": "n8n-nodes-base.amqpTrigger", + "defaults": { + "name": "AMQP Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AMQP Trigger", + "typeVersion": 1 + }, + { + "id": 13, + "icon": "file:asana.svg", + "name": "n8n-nodes-base.asanaTrigger", + "defaults": { + "name": "Asana Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGN4PSI1MCUiIGN5PSI1NSUiIGZ4PSI1MCUiIGZ5PSI1NSUiIHI9IjcyLjUwNyUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjkyNDA0IDAgMCAxIC4wMzggMCkiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjRkZCOTAwIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0Y5NUQ4RiIgb2Zmc2V0PSI2MCUiLz48c3RvcCBzdG9wLWNvbG9yPSIjRjk1MzUzIiBvZmZzZXQ9Ijk5LjkxJSIvPjwvcmFkaWFsR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik00NS41OTQgMjguNWMtNi45OTQuMDAzLTEyLjY2NCA1LjY3My0xMi42NjcgMTIuNjY3LjAwMyA2Ljk5NSA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2OCA2Ljk5NS0uMDA0IDEyLjY2NC01LjY3MyAxMi42NjctMTIuNjY4LS4wMDMtNi45OTQtNS42NzItMTIuNjY0LTEyLjY2Ny0xMi42Njd6bS0zMi45MjcuMDAxQzUuNjczIDI4LjUwNS4wMDMgMzQuMTc0IDAgNDEuMTdjLjAwMyA2Ljk5NCA1LjY3MyAxMi42NjQgMTIuNjY3IDEyLjY2NyA2Ljk5NS0uMDAzIDEyLjY2NC01LjY3MyAxMi42NjgtMTIuNjY3LS4wMDQtNi45OTUtNS42NzMtMTIuNjY0LTEyLjY2OC0xMi42Njh6TTQxLjc5IDEyLjY2N2MtLjAwMiA2Ljk5NS01LjY3MSAxMi42NjUtMTIuNjY2IDEyLjY3LTYuOTk1LS4wMDQtMTIuNjY0LTUuNjc0LTEyLjY2Ny0xMi42N0MxNi40NiA1LjY3MyAyMi4xMy4wMDMgMjkuMTIzIDBjNi45OTQuMDA0IDEyLjY2MyA1LjY3MyAxMi42NjYgMTIuNjY3eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjczMiAyLjczMikiIGZpbGw9InVybCgjYSkiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Asana Trigger", + "typeVersion": 1 + }, + { + "id": 15, + "icon": "file:affinity.png", + "name": "n8n-nodes-base.affinity", + "defaults": { + "name": "Affinity" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Affinity", + "typeVersion": 1 + }, + { + "id": 19, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilotTrigger", + "defaults": { + "name": "Autopilot Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot Trigger", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 31, + "icon": "file:bitwarden.svg", + "name": "n8n-nodes-base.bitwarden", + "defaults": { + "name": "Bitwarden" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48cGF0aCBkPSJNNTMuMzMzIDIuNjY3djMyYzAgMi4zODgtLjQ2NSA0Ljc1Ni0xLjM5NiA3LjEwM3MtMi4wODQgNC40My0zLjQ1OCA2LjI1LTMuMDE1IDMuNTktNC45MTcgNS4zMTItMy42NiAzLjE1My01LjI3MiA0LjI5MmwtNS4wNCAzLjIzLTMuNzMgMi4wNjItMS43Ny44MzRjLS4zMzMuMTY2LS42OTUuMjUtMS4wODMuMjVhMi40IDIuNCAwIDAxLTEuMDgzLS4yNWwtMS43Ny0uODM0LTMuNzMtMi4wNjItNS4wNDItMy4yM2MtMS42MS0xLjE0LTMuMzY4LTIuNTctNS4yNy00LjI5MnMtMy41NC0zLjQ5Mi00LjkxNi01LjMxMi0yLjUyOC0zLjkwMy0zLjQ2LTYuMjVTMCAzNy4wNTUgMCAzNC42Njd2LTMyQTIuNTYgMi41NiAwIDAxLjc5MS43OTIgMi41NiAyLjU2IDAgMDEyLjY2NiAwaDQ4Yy43MiAwIDEuMzQ2LjI2NCAxLjg3NC43OTJhMi41NiAyLjU2IDAgMDEuNzkyIDEuODc1bS04IDMyVjhIMjYuNjY2djQ3LjM3NWMzLjMwNS0xLjc1IDYuMjY0LTMuNjUzIDguODc1LTUuNzA4IDYuNTI3LTUuMTEgOS43OS0xMC4xMSA5Ljc5LTE1IiBzdHJva2U9Im5vbmUiIGZpbGw9IiMzYzhkYmMiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Bitwarden", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + }, + { + "id": 46, + "icon": "fa:file-archive", + "name": "n8n-nodes-base.compression", + "defaults": { + "name": "Compression", + "color": "#408000" + }, + "iconData": { + "icon": "file-archive", + "type": "icon" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Compression", + "typeVersion": 1 + }, + { + "id": 62, + "icon": "file:discord.svg", + "name": "n8n-nodes-base.discord", + "defaults": { + "name": "Discord" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMTk5cHgiIHZpZXdCb3g9IjAgMCAyNTYgMTk5IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICAgIDxnPgogICAgICAgIDxwYXRoIGQ9Ik0yMTYuODU2MzM5LDE2LjU5NjYwMzEgQzIwMC4yODUwMDIsOC44NDMyODY2NSAxODIuNTY2MTQ0LDMuMjA4NDk4OCAxNjQuMDQxNTY0LDAgQzE2MS43NjY1MjMsNC4xMTMxODEwNiAxNTkuMTA4NjI0LDkuNjQ1NDk5MDggMTU3LjI3NjA5OSwxNC4wNDY0Mzc5IEMxMzcuNTgzOTk1LDExLjA4NDk4OTYgMTE4LjA3Mjk2NywxMS4wODQ5ODk2IDk4Ljc0MzAxNjMsMTQuMDQ2NDM3OSBDOTYuOTEwODQxNyw5LjY0NTQ5OTA4IDk0LjE5MjU4MzgsNC4xMTMxODEwNiA5MS44OTcxODk1LDAgQzczLjM1MjYwNjgsMy4yMDg0OTg4IDU1LjYxMzM5NDksOC44NjM5OTExNyAzOS4wNDIwNTgzLDE2LjYzNzY2MTIgQzUuNjE3NTIyOTMsNjcuMTQ2NTE0IC0zLjQ0MzMxOTEsMTE2LjQwMDgxMyAxLjA4NzExMDY5LDE2NC45NTU3MjEgQzIzLjI1NjAxOTYsMTgxLjUxMDkxNSA0NC43NDAzNjM0LDE5MS41Njc2OTcgNjUuODYyMTMyNSwxOTguMTQ4NTc2IEM3MS4wNzcyMTUxLDE5MC45NzExMjYgNzUuNzI4MzYyOCwxODMuMzQxMzM1IDc5LjczNTIxMzksMTc1LjMwMDI2MSBDNzIuMTA0MDE5LDE3Mi40MDA1NzUgNjQuNzk0OTcyNCwxNjguODIyMjAyIDU3Ljg4ODc4NjYsMTY0LjY2Nzk2MyBDNTkuNzIwOTYxMiwxNjMuMzEwNTg5IDYxLjUxMzEzMDQsMTYxLjg5MTQ1MiA2My4yNDQ1ODk4LDE2MC40MzEyNTcgQzEwNS4zNjc0MSwxODAuMTMzMTg3IDE1MS4xMzQ5MjgsMTgwLjEzMzE4NyAxOTIuNzU0NTIzLDE2MC40MzEyNTcgQzE5NC41MDYzMzYsMTYxLjg5MTQ1MiAxOTYuMjk4MTU0LDE2My4zMTA1ODkgMTk4LjExMDMyNiwxNjQuNjY3OTYzIEMxOTEuMTgzNzg3LDE2OC44NDI1NTYgMTgzLjg1NDczNywxNzIuNDIwOTI5IDE3Ni4yMjM1NDIsMTc1LjMyMDk2NSBDMTgwLjIzMDM5MywxODMuMzQxMzM1IDE4NC44NjE1MzgsMTkwLjk5MTgzMSAxOTAuMDk2NjI0LDE5OC4xNjg5MyBDMjExLjIzODc0NiwxOTEuNTg4MDUxIDIzMi43NDMwMjMsMTgxLjUzMTYxOSAyNTQuOTExOTQ5LDE2NC45NTU3MjEgQzI2MC4yMjc3NDcsMTA4LjY2ODIwMSAyNDUuODMxMDg3LDU5Ljg2NjI0MzIgMjE2Ljg1NjMzOSwxNi41OTY2MDMxIFogTTg1LjQ3Mzg3NTIsMTM1LjA5NDg5IEM3Mi44MjkwMjgxLDEzNS4wOTQ4OSA2Mi40NTkyMjE3LDEyMy4yOTAxNTUgNjIuNDU5MjIxNywxMDguOTE0OTAxIEM2Mi40NTkyMjE3LDk0LjUzOTY0NzIgNzIuNjA3NTk1LDgyLjcxNDU1ODcgODUuNDczODc1Miw4Mi43MTQ1NTg3IEM5OC4zNDA1MDY0LDgyLjcxNDU1ODcgMTA4LjcwOTk2Miw5NC41MTg5NDI3IDEwOC40ODg1MjksMTA4LjkxNDkwMSBDMTA4LjUwODUzMSwxMjMuMjkwMTU1IDk4LjM0MDUwNjQsMTM1LjA5NDg5IDg1LjQ3Mzg3NTIsMTM1LjA5NDg5IFogTTE3MC41MjUyMzcsMTM1LjA5NDg5IEMxNTcuODgwMzksMTM1LjA5NDg5IDE0Ny41MTA1ODQsMTIzLjI5MDE1NSAxNDcuNTEwNTg0LDEwOC45MTQ5MDEgQzE0Ny41MTA1ODQsOTQuNTM5NjQ3MiAxNTcuNjU4NjA2LDgyLjcxNDU1ODcgMTcwLjUyNTIzNyw4Mi43MTQ1NTg3IEMxODMuMzkxNTE4LDgyLjcxNDU1ODcgMTkzLjc2MTMyNCw5NC41MTg5NDI3IDE5My41Mzk4OTEsMTA4LjkxNDkwMSBDMTkzLjUzOTg5MSwxMjMuMjkwMTU1IDE4My4zOTE1MTgsMTM1LjA5NDg5IDE3MC41MjUyMzcsMTM1LjA5NDg5IFoiIGZpbGw9IiM1ODY1RjIiIGZpbGwtcnVsZT0ibm9uemVybyI+PC9wYXRoPgogICAgPC9nPgo8L3N2Zz4=" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Discord", + "typeVersion": 2 + }, + { + "id": 114, + "icon": "file:helpScout.svg", + "name": "n8n-nodes-base.helpScoutTrigger", + "defaults": { + "name": "HelpScout Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjMxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTE4LjQzMiAxODAuOTY5bDkwLjQ4NC05MC40ODVhNjMuNzE3IDYzLjcxNyAwIDAwMTguOTktNDUuNDI4QTY0LjI0NSA2NC4yNDUgMCAwMDEwOS40NzYgMEwxOC45OSA5MC40ODRBNjMuNzE3IDYzLjcxNyAwIDAwMCAxMzUuOTEzYzAgMTcuNjg3IDcuMDc1IDMzLjUxMiAxOC40MzIgNDUuMDU2em0yMTkuMTM2LTUyLjg3NmwtOTAuNDg0IDkwLjQ4NGE2My43MTcgNjMuNzE3IDAgMDAtMTguOTkgNDUuNDI5IDY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMSA0NS4wNTZsOTAuNDg0LTkwLjQ4NUE2My43MTcgNjMuNzE3IDAgMDAyNTYgMTczLjE1YzAtMTcuNjg3LTcuMDc1LTMzLjUxMy0xOC40MzItNDUuMDU2em0tLjU1OS0zNy40MjJBNjMuNzE3IDYzLjcxNyAwIDAwMjU2IDQ1LjI0MiA2NC4yNDUgNjQuMjQ1IDAgMDAyMzcuNTY4LjE4NkwxOC45OTEgMjE4LjU3N0M3LjI2IDIzMC4zMDcgMCAyNDYuMzIgMCAyNjQuMTkyYTY0LjI0NSA2NC4yNDUgMCAwMDE4LjQzMiA0NS4wNTZMMjM3LjAwOSA5MC42NzF6IiBmaWxsPSIjMTI5MkVFIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "HelpScout Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 1323, + "name": "Create Email Campaign From LinkedIn Post Interactions", + "totalViews": 942, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-11-15T14:48:51.258Z", + "nodes": [ + { + "id": 7, + "icon": "file:airtable.svg", + "name": "n8n-nodes-base.airtable", + "defaults": { + "name": "Airtable" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcwIj48cGF0aCBkPSJNODkgNC44TDE2LjIgMzQuOWMtNC4xIDEuNy00IDcuNC4xIDkuMWw3My4yIDI5YzYuNCAyLjYgMTMuNiAyLjYgMjAgMGw3My4yLTI5YzQuMS0xLjYgNC4xLTcuNC4xLTkuMWwtNzMtMzAuMUMxMDMuMiAyIDk1LjcgMiA4OSA0LjgiIGZpbGw9IiNmY2I0MDAiLz48cGF0aCBkPSJNMTA1LjkgODguOXY3Mi41YzAgMy40IDMuNSA1LjggNi43IDQuNWw4MS42LTMxLjdjMS45LS43IDMuMS0yLjUgMy4xLTQuNVY1Ny4yYzAtMy40LTMuNS01LjgtNi43LTQuNUwxMDkgODQuM2MtMS45LjgtMy4xIDIuNi0zLjEgNC42IiBmaWxsPSIjMThiZmZmIi8+PHBhdGggZD0iTTg2LjkgOTIuNmwtMjQuMiAxMS43LTIuNSAxLjJMOS4xIDEzMGMtMy4yIDEuNi03LjQtLjgtNy40LTQuNFY1Ny41YzAtMS4zLjctMi40IDEuNi0zLjMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS41IDQuMyA3LjEuNSA4LjkiIGZpbGw9IiNmODJiNjAiLz48cGF0aCBkPSJNODYuOSA5Mi42bC0yNC4yIDExLjctNTkuNC01MGMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS40IDQuMyA3IC41IDguOCIgZmlsbD0iI2JhMWU0NSIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Airtable", + "typeVersion": 2 + }, + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 76, + "icon": "fa:sign-in-alt", + "name": "n8n-nodes-base.executeWorkflow", + "defaults": { + "name": "Execute Workflow", + "color": "#ff6d5a" + }, + "iconData": { + "icon": "sign-in-alt", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Execute Workflow", + "typeVersion": 1 + } + ] + }, + { + "id": 467, + "name": "Funnel Users and Sales Data From Webhook to a Marketing Platform (We’d might like to generalize the workflow as it’s very specific to Teachable and Muatic integration)", + "totalViews": 658, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-07-09T20:34:27.156Z", + "nodes": [ + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 24, + "icon": "file:ses.svg", + "name": "n8n-nodes-base.awsSes", + "defaults": { + "name": "AWS SES" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzQuMzc1IDg1IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIyLjE4OCIgeT0iMi41Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZT0ibm9uZSI+PHBhdGggZD0iTTE2LjU1OCAxMi43NUwwIDM4LjU5MWwxNi41NTggMjUuODQxIDEzLjIyNy0zLjMyNC42NTQtNDQuODY5LTEzLjg4MS0zLjQ4OXoiIGZpbGw9IiM4NzY5MjkiLz48cGF0aCBkPSJNMzUuMDQ5IDU5Ljc4NmwtMTguNDkxIDQuNjQ1VjEyLjc1bDE4LjQ5MSA0LjY0NXY0Mi4zOTF6IiBmaWxsPSIjZDlhNzQxIi8+PGcgZmlsbD0iIzg3NjkyOSI+PHBhdGggZD0iTTMyLjg0OSAyMS42MTRMMzUuMDUgODAgNzAgNjIuODY3bC0uMDEtNDMuNjE1LTguOTE0IDEuNDQ4LTI4LjIyOC45MTN6Ii8+PHBhdGggZD0iTTQ2LjE4NCAzMy4xNDlsMTAuOTA2LS42MzIgMTAuNzc4LTE5LjE2NEw0MC42MTIgMCAzMC40MzkgNC4zNjRsMTUuNzQ1IDI4Ljc4NXoiLz48L2c+PHBhdGggZD0iTTQwLjYxMiAwbDI3LjI1NiAxMy4zNTNMNTcuMDkgMzIuNTE3IDQwLjYxMiAweiIgZmlsbD0iI2Q5YTc0MSIvPjxwYXRoIGQ9Ik0zNS4wNDkgNS41MzlMNTcuMDkgNDQuNzQybDMuNzg4IDIyLjU5NUwzNS4wNDkgODBsLTEwLjQ2LTUuMTMxVjkuNjRsMTAuNDYtNC4xMDF6IiBmaWxsPSIjODc2OTI5Ii8+PHBhdGggZD0iTTY5Ljk5MSAxOS4yNTFMNzAgNjIuODY3IDM1LjA1IDgwVjUuNTM5bDIyLjA0MSAzOS4yMDNMNjkuOTkgMTkuMjUxeiIgZmlsbD0iI2Q5YTc0MSIvPjwvZz48L3N5bWJvbD48L3N2Zz4=" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AWS SES", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "file:clickup.svg", + "name": "n8n-nodes-base.clickUpTrigger", + "defaults": { + "name": "ClickUp Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMTAgMCAxNTUgMTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSI2OC4wMSUiIHkyPSI2OC4wMSUiIGlkPSJhIj48c3RvcCBzdG9wLWNvbG9yPSIjODkzMEZEIiBvZmZzZXQ9IjAlIi8+PHN0b3Agc3RvcC1jb2xvcj0iIzQ5Q0NGOSIgb2Zmc2V0PSIxMDAlIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgeDE9IjAlIiB5MT0iNjguMDElIiB5Mj0iNjguMDElIiBpZD0iYiI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGMDJGMCIgb2Zmc2V0PSIwJSIvPjxzdG9wIHN0b3AtY29sb3I9IiNGRkM4MDAiIG9mZnNldD0iMTAwJSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0uNCAxMTkuMTJsMjMuODEtMTguMjRDMzYuODYgMTE3LjM5IDUwLjMgMTI1IDY1LjI2IDEyNWMxNC44OCAwIDI3Ljk0LTcuNTIgNDAuMDItMjMuOWwyNC4xNSAxNy44QzExMiAxNDIuNTIgOTAuMzQgMTU1IDY1LjI2IDE1NWMtMjUgMC00Ni44Ny0xMi40LTY0Ljg2LTM1Ljg4eiIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik02NS4xOCAzOS44NEwyMi44IDc2LjM2IDMuMjEgNTMuNjQgNjUuMjcuMTZsNjEuNTcgNTMuNTItMTkuNjggMjIuNjR6Ii8+PC9nPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "ClickUp Trigger", + "typeVersion": 1 + }, + { + "id": 47, + "icon": "file:coda.svg", + "name": "n8n-nodes-base.coda", + "defaults": { + "name": "Coda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA2MCA2MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNjAgNjA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRUU1QTI5O30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQ1LjIsMTYuMmMzLjMsMCw2LjUsMS4zLDguOCwzLjNjMS41LDEuMywzLjgsMC4yLDMuOC0xLjhWNC43YzAtMi41LTIuMS00LjctNC43LTQuN0g2LjMKCUMzLjcsMCwxLjYsMi4xLDEuNiw0Ljd2NTAuN2MwLDIuNSwyLjEsNC42LDQuNyw0LjZoNDYuOGMyLjUsMCw0LjctMi4xLDQuNy00LjdWNDIuM2MwLTItMi4zLTMuMS0zLjgtMS44Yy0yLjQsMi4xLTUuNCwzLjMtOC44LDMuMwoJYy03LjYsMC0xMy43LTYuMi0xMy43LTEzLjhDMzEuNiwyMi40LDM3LjcsMTYuMiw0NS4yLDE2LjJ6Ii8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Coda", + "typeVersion": 1 + }, + { + "id": 112, + "icon": "file:harvest.png", + "name": "n8n-nodes-base.harvest", + "defaults": { + "name": "Harvest" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Harvest", + "typeVersion": 1 + }, + { + "id": 126, + "icon": "file:invoiceNinja.svg", + "name": "n8n-nodes-base.invoiceNinjaTrigger", + "defaults": { + "name": "Invoice Ninja Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE2LjI0NyAxMC4zMjZhMS4xNjQgMS4xNjQgMCAxMS0yLjMyOCAwIDEuMTY0IDEuMTY0IDAgMDEyLjMyOCAwem0tNi4yODggMGExLjE2NCAxLjE2NCAwIDExLTIuMzI5IDAgMS4xNjQgMS4xNjQgMCAwMTIuMzI5IDB6bS0uMTQgMTMuNTJjLTQuNzEyLS45OC04LjIyNy00LjI1Ny05LjQ4Mi04Ljg0Mi0uNDIxLTEuNTM3LS40MjEtNC40OSAwLTYuMDI3QzEuNTA2IDQuNzA5IDQuNzMgMS40ODUgOC45OTcuMzE2YzEuNTM4LS40MjEgNC40OS0uNDIxIDYuMDI4IDAgNC4yNjcgMS4xNjkgNy40OTIgNC4zOTMgOC42NiA4LjY2LjI0Ljg3NC4yOTQgMS40My4yOTQgMy4wMTQgMCAxLjU4NC0uMDU0IDIuMTQtLjI5MyAzLjAxNC0xLjE3IDQuMjcxLTQuNDM5IDcuNTM2LTguNjYxIDguNjUtMS4zOTEuMzY3LTMuOTE2LjQ2LTUuMjA2LjE5MnptNi42NC05LjMxNWMtMy4wNDctMS4zNDgtNC4wNTQtMS43MzctNC41LTEuNzM3LS40NDYgMC0xLjQzMy4zOC00LjM4IDEuNjg0LTIuMDkxLjkyNi0zLjgyOCAxLjc2LTMuODYgMS43OWgxNi42NjN6bS05Ljg3My0uMzYxYzEuNjIxLS43MjkgMy4wNi0xLjM4NyAzLjE5Ni0xLjQ2NC4yNTgtLjE0NS4zMzctLjA5LTUuMjg1LTMuNjgyLS41Ni0uMzU4LTEuMDIzLS42OTgtMS4wMjUtLjY1djcuMTlhNzkwLjEgNzkwLjEgMCAwMDMuMTE0LTEuMzk0em0xNC4wNzgtMi4xOTRWOC40MTdjMC0uMTEtMS42NzYuOTkzLTMuNDk2IDIuMTItMyAxLjg1NC0zLjI4MSAyLjA2LTMuMDA0IDIuMTg1IDEuMzQ1LjYxMSA2LjQyIDIuODYyIDYuNSAyLjg3MnptLTguMTY5LjExYy41NDUuMTI1LjY0My4xMDQgMS4yMjYtLjI2My4zNDktLjIyLjY1NS0uNDE5LjY4MS0uNDQyLjAyNi0uMDI0LS4wNS0uMTgxLS4xNjctLjM1LS4xMTgtLjE2OC0uMjE1LS41LS4yMTUtLjczOVY5Ljg2bC0uNTY5LjIxYy0uNzI2LjI2Ny0yLjI4LjI3LTMgLjAwNWwtLjU1Ni0uMjA1LjAxMy40NTJjLjAwNy4yNi0uMDg4LjU2My0uMjI1LjcxNS0uMjMyLjI1Ni0uMjIuMjc2LjQ1LjcyNi42NC40MzIuNzI1LjQ1NSAxLjIzLjMyN2EyLjM0OSAyLjM0OSAwIDAxMS4xMzItLjAwMnptLTQuMjMtMi42NWMtLjEwNS0uMTEzLTIuOTctLjk1NC0zLjAzMy0uODkxLS4wMy4wMy41MDQuNDE0IDEuMTg2Ljg1NGwxLjI0LjguMzQtLjM0NGMuMTg2LS4xODguMzA3LS4zNzcuMjY4LS40MnptOS43Ni0uMzczYy40NzMtLjMwNi44LS41NTUuNzI4LS41NTUtLjE1NSAwLTIuODc3LjgwNC0zLjAyNy44OTQtLjA1Ny4wMzQuMDMzLjIyOS4yLjQzM2wuMzA0LjM3LjQ3LS4yOTNjLjI1Ny0uMTYyLjg1NC0uNTQ0IDEuMzI2LS44NXptLTEuNjM2LS41NTVjMi4xMS0uNTkgMy44NjctMS4xMDIgMy45MDQtMS4xMzlIMy41OWMuMTg3LjE4NyA3Ljc3OSAyLjE5NSA4LjMyMyAyLjIwMi40MS4wMDUgMi4wMTQtLjM3NiA0LjQ3Ni0xLjA2M3oiLz48L3N2Zz4=" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Invoice Ninja Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 1206, + "name": "Process Shopify New Orders with CRM and Marketing Platforms", + "totalViews": 471, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-08-24T11:23:23.518Z", + "nodes": [ + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 43, + "icon": "file:clockify.svg", + "name": "n8n-nodes-base.clockifyTrigger", + "defaults": { + "name": "Clockify Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSJub25lIiBkPSJNMTQgMGgyMjd2MjU2SDE0eiIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTY1LjEyNCA4Ny45OWw1OC43NzctNTguODE0IDE2LjgyNSAxNi44MjMtNTguNzc4IDU4LjgxNS0xNi44MjQtMTYuODIzem0tMjMuODE3IDYwLjEwNWMtMTEuNTAzIDAtMjAuODIyLTkuMzYtMjAuODIyLTIwLjkxOCAwLTExLjU0NiA5LjMxOS0yMC45MTggMjAuODIyLTIwLjkxOCAxMS41MDMgMCAyMC44MjIgOS4zNzIgMjAuODIyIDIwLjkxOCAwIDExLjU1OC05LjMxOSAyMC45MTgtMjAuODIyIDIwLjkxOHpNMjQxIDIwOC44NDVsLTE2LjgyNCAxNi44MzUtNTguNzc4LTU4LjgxNiAxNi44MjUtMTYuODM1TDI0MSAyMDguODQ1eiIgZmlsbD0iIzIyMiIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTQxLjkxMyAyMTcuNjM3YzExLjg0OCAwIDIzLjEzNi0yLjM2NCAzMy40ODItNi41NjdsMjguNzA4IDI4Ljc0QzE4NS42OTIgMjUwLjA4OSAxNjQuNSAyNTYgMTQxLjkxMyAyNTYgNzEuMjc0IDI1NiAxNCAxOTguNjg5IDE0IDEyOC4wMDYgMTQgNTcuMzExIDcxLjI3NSAwIDE0MS45MTMgMGMyMi4zNjEgMCA0My4zNjIgNS43NjcgNjEuNjQxIDE1Ljg1NmwtMjguMjMxIDI4LjI2MWMtMTAuMzMzLTQuMTc5LTIxLjU4NS02LjU0My0zMy40MS02LjU0My00OS40NyAwLTg5LjU3NSA0MC4zMDktODkuNTc1IDkwLjAzOCAwIDQ5LjcxNiA0MC4xMDQgOTAuMDI1IDg5LjU3NSA5MC4wMjV6IiBmaWxsPSIjMDNBOUY0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + }, + { + "id": 7, + "name": "Utility" + } + ], + "displayName": "Clockify Trigger", + "typeVersion": 1 + }, + { + "id": 71, + "icon": "file:emelia.svg", + "name": "n8n-nodes-base.emelia", + "defaults": { + "name": "Emelia" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSItMiAyIDQ1IDQ1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yOS43MTQgNi41MzlIOS4wNzhhMi4xMDkgMi4xMDkgMCAwMC0yLjAwNyAyLjc2Mmw1LjQ4NCAxNi44NjMgNC4yMTktMS44MTdhMi42OSAyLjY5IDAgMDEtLjAyNC0uMzQgMi42NDEgMi42NDEgMCAxMTEuMDIzIDIuMDgxIDI0MTYuNzEgMjQxNi43MSAwIDAxLTQuNDE1IDIuNTQ1bDUuMTU2IDE1Ljg1MmEuODY1Ljg2NSAwIDAwMS42NDUgMGwxMS40OC0zNS4yOTNhMi4wMjYgMi4wMjYgMCAwMC0xLjkyNS0yLjY1M3oiIG9wYWNpdHk9Ii45IiBmaWxsPSIjZjRiNDU0Ii8+PHBhdGggZD0iTTM3LjE1NCAyMS4yNjFMMS4wNzEgMTIuNTc3YS44NjUuODY1IDAgMDAtLjg2MSAxLjRsMTAuOTA5IDEyLjggNS42NTYtMi40MzZhMi42OTQgMi42OTQgMCAwMS0uMDI0LS4zNCAyLjY0MSAyLjY0MSAwIDExMS4wMjMgMi4wODEgMjE3OC4xNSAyMTc4LjE1IDAgMDEtNC44NiAyLjhsMTEuMjkxIDEzLjI1NWEyLjExIDIuMTEgMCAwMDMuNC0uMjY0bDEwLjgtMTcuNTg2YTIuMDI2IDIuMDI2IDAgMDAtMS4yNTEtMy4wMjZ6IiBmaWxsPSIjZWY2ZDRhIiBvcGFjaXR5PSIuOSIvPjxwYXRoIGQ9Ik0zNy42MzYgMTIuNTc3TDEuNTUzIDIxLjI2MUEyLjAyNSAyLjAyNSAwIDAwLjMwMSAyNC4yOWwzLjQ3MiA1LjY1NiAxMy01LjZhMi42ODkgMi42ODkgMCAwMS0uMDI0LS4zNCAyLjY0MSAyLjY0MSAwIDExMS4wMjMgMi4wODFjLTMuMSAxLjc5MS05LjA3MyA1LjIzNC0xMi4xMDggNi45NGw1LjQzMiA4Ljg0OGEyLjExIDIuMTEgMCAwMDMuNC4yNjRsMjMuOTk1LTI4LjE2MWEuODY1Ljg2NSAwIDAwLS44NTUtMS40MDF6IiBmaWxsPSIjMjAzNTRjIiBvcGFjaXR5PSIuOSIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Emelia", + "typeVersion": 1 + }, + { + "id": 107, + "icon": "file:gotify.png", + "name": "n8n-nodes-base.gotify", + "defaults": { + "name": "Gotify" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Gotify", + "typeVersion": 1 + }, + { + "id": 225, + "icon": "file:strapi.svg", + "name": "n8n-nodes-base.strapi", + "defaults": { + "name": "Strapi" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNSIgaGVpZ2h0PSIxMDMyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0zNDEuMzI4IDB2MzQ0LjA2MWgzNDEuMzMzdjM0NC4wNmgzNDEuMzI5VjBIMzQxLjMyOHoiIGZpbGw9InVybCgjYSkiLz48cGF0aCBkPSJNNjgzIDM0My43MjVIMzQzdjM0NC43MzJoMzQwVjM0My43MjV6IiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0iTTM0MS4zMzMgMzQ0LjA2MUgwTDM0MS4zMzMgMHYzNDQuMDYxeiIgZmlsbD0idXJsKCNjKSIvPjxwYXRoIGQ9Ik02ODIuMzY3IDEwMzEuMThWNjg3LjQ1N2gzNDIuMDAzTDY4Mi4zNjcgMTAzMS4xOHoiIGZpbGw9InVybCgjZCkiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzNjUuMTYyIDMxNC42NjMpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMzc0LjUwMiAzMTQuNjYzKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDM4My44NDMgMzE0LjY2MykiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzOTMuMTg0IDMxNC42NjMpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgNDAyLjUyNSAzMTQuNjYzKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDQxMS44NjYgMzE0LjY2MykiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSA0MjEuMjA2IDMxNC42NjMpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgNDMwLjU0NiAzMTQuNjYzKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDQzOS44ODcgMzE0LjY2MykiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSA0NDkuMjI4IDMxNC42NjMpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgNDU4LjU2OSAzMTQuNjYzKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDM2NS4xNjIgMzA2LjA0MSkiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzNzQuNTAyIDMwNi4wNDEpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMzgzLjg0MyAzMDYuMDQxKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDM5My4xODQgMzA2LjA0MSkiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSA0MDIuNTI1IDMwNi4wNDEpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgNDExLjg2NiAzMDYuMDQxKSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDQyMS4yMDYgMzA2LjA0MSkiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSA0MzAuNTQ2IDMwNi4wNDEpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMzY1LjE2MiAyOTcuNDE5KSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDM3NC41MDIgMjk3LjQxOSkiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzODMuODQzIDI5Ny40MTkpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMzkzLjE4NCAyOTcuNDE5KSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDQwMi41MjUgMjk3LjQxOSkiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIHI9IjEuNzk2IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzNjUuMTYyIDI4OC43OTYpIiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSByPSIxLjc5NiIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMzc0LjUwMiAyODguNzk3KSIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgcj0iMS43OTYiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDM4My44NDMgMjg4Ljc5NykiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MTYuMTYyIiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MjUuNTAyIiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MzQuODQzIiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NDQuMTg0IiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NTMuNTI1IiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NjIuODY2IiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NzIuMjA2IiBjeT0iNjM4Ljc5NiIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MTYuMTYyIiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MjUuNTAyIiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MzQuODQzIiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NDQuMTg0IiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NTMuNTI1IiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NjIuODY2IiBjeT0iNjQ3LjQxOSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MTYuMTYyIiBjeT0iNjU2LjA0MSIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3MjUuNTAyIiBjeT0iNjU2LjA0IiByPSIxLjc5NiIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgY3g9IjczNC44NDMiIGN5PSI2NTYuMDQiIHI9IjEuNzk2IiBmaWxsPSIjOTU2RkZGIi8+PGNpcmNsZSBjeD0iNzQ0LjE4NCIgY3k9IjY1Ni4wNCIgcj0iMS43OTYiIGZpbGw9IiM5NTZGRkYiLz48Y2lyY2xlIGN4PSI3NTMuNTI1IiBjeT0iNjU2LjA0IiByPSIxLjc5NiIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgY3g9IjcxNi4xNjIiIGN5PSI2NjQuNjY0IiByPSIxLjc5NiIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgY3g9IjcyNS41MDIiIGN5PSI2NjQuNjYzIiByPSIxLjc5NiIgZmlsbD0iIzk1NkZGRiIvPjxjaXJjbGUgY3g9IjczNC44NDMiIGN5PSI2NjQuNjYzIiByPSIxLjc5NiIgZmlsbD0iIzk1NkZGRiIvPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjEwNzIuMzYiIHkxPSItNTEuNDA3IiB4Mj0iNTUzLjAzMSIgeTI9IjQ0Ni45NDUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9Ii4wMjEiIHN0b3AtY29sb3I9IiM5NTZGRkYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMxQzFCN0UiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjYzNC4zNjUiIHkxPSIzOTYuNSIgeDI9IjI3Ny4xNzciIHkyPSI3MTIuOTAxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzk1NkZGRiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzFBMjY3MCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJjIiB4MT0iMTcwLjY2NyIgeTE9IjAiIHgyPSIxNzAuNjY3IiB5Mj0iMzQ0LjA2MSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iLjAyMSIgc3RvcC1jb2xvcj0iIzk1NkZGRiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzFDMUI3RSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJkIiB4MT0iMTA4NS4xOCIgeTE9IjU1Ni4yMiIgeDI9IjY5NS42NTQiIHkyPSI5NjcuMDQyIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzk1NkZGRiIvPjxzdG9wIG9mZnNldD0iLjgzOSIgc3RvcC1jb2xvcj0iIzFDMUI3RSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + }, + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Strapi", + "typeVersion": 1 + } + ] + }, + { + "id": 1207, + "name": "Run Weekly Inventories on Shopify Sales ", + "totalViews": 424, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-08-24T15:08:34.063Z", + "nodes": [ + { + "id": 7, + "icon": "file:airtable.svg", + "name": "n8n-nodes-base.airtable", + "defaults": { + "name": "Airtable" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcwIj48cGF0aCBkPSJNODkgNC44TDE2LjIgMzQuOWMtNC4xIDEuNy00IDcuNC4xIDkuMWw3My4yIDI5YzYuNCAyLjYgMTMuNiAyLjYgMjAgMGw3My4yLTI5YzQuMS0xLjYgNC4xLTcuNC4xLTkuMWwtNzMtMzAuMUMxMDMuMiAyIDk1LjcgMiA4OSA0LjgiIGZpbGw9IiNmY2I0MDAiLz48cGF0aCBkPSJNMTA1LjkgODguOXY3Mi41YzAgMy40IDMuNSA1LjggNi43IDQuNWw4MS42LTMxLjdjMS45LS43IDMuMS0yLjUgMy4xLTQuNVY1Ny4yYzAtMy40LTMuNS01LjgtNi43LTQuNUwxMDkgODQuM2MtMS45LjgtMy4xIDIuNi0zLjEgNC42IiBmaWxsPSIjMThiZmZmIi8+PHBhdGggZD0iTTg2LjkgOTIuNmwtMjQuMiAxMS43LTIuNSAxLjJMOS4xIDEzMGMtMy4yIDEuNi03LjQtLjgtNy40LTQuNFY1Ny41YzAtMS4zLjctMi40IDEuNi0zLjMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS41IDQuMyA3LjEuNSA4LjkiIGZpbGw9IiNmODJiNjAiLz48cGF0aCBkPSJNODYuOSA5Mi42bC0yNC4yIDExLjctNTkuNC01MGMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS40IDQuMyA3IC41IDguOCIgZmlsbD0iI2JhMWU0NSIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Airtable", + "typeVersion": 2 + }, + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 18, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilot", + "defaults": { + "name": "Autopilot" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 26, + "icon": "file:sns.svg", + "name": "n8n-nodes-base.awsSnsTrigger", + "defaults": { + "name": "AWS SNS Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTk4Ljg3NSAyMzIuMDMzbC0yNi40MzMtNy40MDgtMjUuMDAxLTI4LjUwOCAzMS4yNzItLjg2MyAyMC4xNjIgMzYuNzc5bS02MS4xMjUtMTguOGwtMTQuODc1LTQuMTY2LTE0LjA1OC0xNi4wMzQgMTcuMDgyLTIuODA5IDExLjg1MSAyMy4wMDkiIGZpbGw9IiM5OTVCODAiLz48cGF0aCBkPSJNMCAxOTEuMDE3bDE1LjIwNCAzLjA5MSAyLjIwNy0zLjg4MlY1OC41MDNsLTIuMjA3LTIuNTYxTDAgNjQuNnYxMjYuNDE3IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTczLjkzMyA2OS43MDhMMTUuMjA4IDU1Ljk0MnYxMzguMTY2bDguNzk4LS44MTggMTMuNzQ0IDE5Ljk0MyAxMC42LTIyLjIwNSAyNS41ODMtMi4zNzhWNjkuNzA4IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTMzLjk1OCAxOTguMTMzbDI2LjA2MyA1LjI1IDEuNzE2LTQuMDQ1VjM3LjQ0bC0xLjcxNi0zLjY2NS0yNi4wNjMgMTMuMjA4djE1MS4xNSIgZmlsbD0iIzdCM0Y2NSIvPjxwYXRoIGQ9Ik0yMDguNzM0IDgxLjUxNkw2MC4wMjEgMzMuNzc1djE2OS42MTJsMTcuMjIxLTIuMjE2IDIxLjYzMyAzMC44NjIgMTcuMTI2LTM1Ljg1IDkyLjczMy0xMS45MzNWODEuNTE2IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTE4MS44MzMgMjU2LjQ5MmwtMzcuNTY2LTEwLjUyNS0zNS41MDktNDAuNSA0Ni4wMzMtLjQ2OCAyNy4wNDIgNTEuNDkzIiBmaWxsPSIjOTk1QjgwIi8+PHBhdGggZD0iTTg5LjU5MSAyMDguOTVsMzguMzMgNy40MTcgMi45NzctMi41NjZWNC4xMTdMMTI3LjkyMSAwbC0zOC4zMyAxOS4xNThWMjA4Ljk1IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTI1NiA2NC4wMzNMMTI3LjkyNSAwdjIxNi4zNjdsMjIuNTk3LTQuNTI4IDMxLjMxMSA0NC42NTMgMjYuOTAxLTU2LjMwOS0uMDE3LS4wMDJMMjU2IDE5MC43MDhWNjQuMDMzIiBmaWxsPSIjQzE3QjlEIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AWS SNS Trigger", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:clearbit.svg", + "name": "n8n-nodes-base.clearbit", + "defaults": { + "name": "Clearbit" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Clearbit", + "typeVersion": 1 + }, + { + "id": 221, + "icon": "file:stackby.png", + "name": "n8n-nodes-base.stackby", + "defaults": { + "name": "Stackby" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAC91BMVEUAAAApJRwHBggfGxImIBjLeRIAjNFQqjnmQjZQqTgNKCUmIAQadHJOpTfvuhD6wxHlQjYAi9AyayNrHhlgMw8ANlEAd7K+NSsAbKL1vxDmQzfzvRAAh8ndQDT7wxFNojYAjNEAg8QAi9Dmsw9QqDjfrQ/kQjb4wRAAis5GlTHIOS9Ppzj3wBAAd7E/hiwAb6Y7fim3jgzdPzQAhshMozapgwvaPjMAhMWfewrQOzEAVH2JJx8AfLmKawiCJR7TPDFImjLMOi/hrg4Ae7hHlzEgRRZEki9RFhLIOC7ZqA1aRQVDkC8/iy3NoAu8NCngQTUAhcbaPzNLoDXVPTJKnTTVPTL5whEAgL7RPDH5wRFHlzIAfbvNOzAAerbiQTbEOC7Wpg7iQTUAic2+Ni3Row7hQTVCji8Acqv0vhDgQDVOpTe6NSzImw0/hiyyMyoAiMpNpTYAbKEAh8mvMikAZ5tNpDbdPzTxvBC/lAyoMCcAYpNMoTWiLiaxiQvaPzM6fCk2cyY2cyaXKyNLnzTrtw9KnjTptg8AgsIAgsKZdgrUPTIwZiJJnDPnsw7dqw5GlTEsXh8AT3YAf7/ksQ6FZwgASnB2Ihvhrw4ARWdHlzF6XgclThlxVwfIOS7bqg0dPhTTpAzrtxBLnjXptQ8AgsFKnDQAe7dPqDjdrA/bqg5PpzdFkjAAis1PpzdEkDD2vxBPpjf2vxD2vxAAicxPpze9Ny1OpjfMng29kwwAiMsAiMvfQDXzvRDeQDX0vhDEmA3eQDTyvBA9gisAhsgAaJvwuxDyvBA6fCkAZZemMCfvug/uuQ8AhcYAcakAhMbtuQ+7kQwAYI9LoDWwMymdLSXtuA8AXYsAT3YAW4gAg8MAWYWTKiLWPTIAV4KPKSKcegp2IhtJnDPmsw8ASW1JmjMsXh/ksQ7QPDErWx4AfrzPOzApWB0nVBsAfLoAQGAAQWEAeLQAd7JRqznoQzf9xBEAjdIAjtP7wxHmQjcAjNEAic1PqDjjQTarWRDCAAAA8nRSTlMAAwULDgX8+/jzEgkG/fn17+lfKRwbEg8J/vz8+/v7+fb08O/o5OHh3t7c2tfSu7eZlJKPj3Z0cWBcT09EQ0JBNCkmJSEdGBcWFhURCwoJ/vn39vXx8fDq6urm5OTa2dHR0MzJyMfHwL+7urq0rq6tqaWjn56enp6el4GBgYGAgH5zamlpXV1cUlJRUVBQSUdGQjk4ODc3Ly4rKycjHh0WDvXx8e/s5eHe2tPT0s7OzsfHxsTEwsG8vLu2trawrquqp6GalZWSjouLioSDgX19eXh3d3d3bWhnZWFhX1taWlVMSkRAPz86OjY1NS4tKycbGNIMkEAAAAMoSURBVEjHzdNVVBVRGIbh/wSIgIGA3Yog3SkpSIuEhS0WBiIigoKAiqBSiiISit3d3d3d3a0nKLnw1N7DrMXA7OMN3/27Zs0z/0CDXi9HpVPNJ1ob3ynZtrzeVSw2KVCq/R2jKhaLK810gXw/joilK6uK5ZNTbRfLV9bvESlVuhZqq8x+kbWtb3bFzz2vTtb+OaeK2vV3eGRt76OVYsWGvOASUo1GaaXJErKUk25AUf0kpLLDVO1jdQkv0qozarvM4qk4OJFQTVXFVE+5reaoGbqwbpfSqEri2gorIrRZUmVQVOMLwD2yQigUVkd5s6PSwlTTdMF1lFC2oATplXjc1mFNxVd53kOoWOhrgLy97Sx4rKgGPpZQNUdtcII3ZBmLRE1tGG/tyz5MNcwRfOLUUNv/Pk9l/mCRZN3smd43hqJaDu7HKlA79CWn9GozkWx6zkwfyYBGpVjFCBdYdaajSL7yw0w/Z4aWjOqyOscBUwVN0oYVE0So3bKQw/SR7TpLqB5q9LXFVB2me8NbY9yOWQaM07RSlVJZUlSJiEq6dqeK6/zQNyRUJ9piqmfc0mvNUNvUwq+e84RPY2lUMzuitpuNBtQTLxqO2moZVTl63c2IinE0Kh3IplF5JnvV1baiqEIT+SoL9FHbZnIxuO1uYs5nbktOY6pBc7mBtwbUpMo0Egg6pTDe9leKytAJfGlUmvO6CyQLa1E/1YF88Dhejlq9Vxx/68YC2cJza3/fSEwVpQMfd2Gqkc7gGd1IoJhp7bftqrjn4Ct0qkJwOyhA67mY4YM5NJdTaQQ+oKji/eCNEUr/jvvGeCC2anKqC/gi1yXxpFSKNZniVceJWFZH5ANk4VbPnus/uzFqQy6pQx3zuacNAIEXyzFVnxmYakOqBrCY70kZ1aFCWDmRRsVqHnskP2+8DuRspag+A9vl6a9J4nPSalAVAftl23MDKKpO5quBZDWpwlJ5RCmNqgWXrH2/A1Pt/ECWctI24da0iKwNuPsfVNaYam0KIRVk4ueGk1IBBKAnb8sF8vlHy6m+gzLz3C+5yLNeoNzcjEKS+aDsciRUDXj/AJL4Mhr51poGAAAAAElFTkSuQmCC" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Stackby", + "typeVersion": 1 + }, + { + "id": 312, + "icon": "file:perspective.svg", + "name": "n8n-nodes-base.googlePerspective", + "defaults": { + "name": "Google Perspective" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iNiAxMCAxMjUgMTI1IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmc0IgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4Ij4KICAgIDxjbGlwUGF0aAogICAgICAgY2xpcFBhdGhVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgICBpZD0iY2xpcFBhdGg5MzciPgogICAgICA8cmVjdAogICAgICAgICBzdHlsZT0iZmlsbDojMDAwMGZmO2ZpbGwtcnVsZTpldmVub2RkIgogICAgICAgICBpZD0icmVjdDkzOSIKICAgICAgICAgd2lkdGg9IjEyMy40OTQ3IgogICAgICAgICBoZWlnaHQ9IjExOS4wMjc3MyIKICAgICAgICAgeD0iMTIuMjUiCiAgICAgICAgIHk9IjEyLjQ4MjI3MSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgogIDxnCiAgICAgaWQ9Imc1MzIiCiAgICAgY2xpcC1wYXRoPSJ1cmwoI2NsaXBQYXRoOTM3KSI+CiAgICA8cGF0aAogICAgICAgZD0iTTEzMS41MyA3MmE1OS43OSA1OS43OSAwIDAgMS01My44MSA1OS40OXYtMTE5QTU5Ljc5IDU5Ljc5IDAgMCAxIDEzMS41MyA3MnpNMTIuMjUgNzh2NTMuNTFoNTMuNTJBNTkuODEgNTkuODEgMCAwIDAgMTIuMjUgNzh6bTAtMTJoNTMuNTJWMTIuNTFBNTkuODEgNTkuODEgMCAwIDAgMTIuMjUgNjZ6bTE3Ni44My0zNy4wNkgyMjNjMjEuNzcgMCAzNS44IDExLjMyIDM1LjggMjkuNjVzLTE0IDI5LjY0LTM1LjggMjkuNjRoLTE1LjIydjI2LjgyaC0xOC43em0zMy4zNCA0NGMxMS44MSAwIDE3LjM0LTQuNTUgMTcuMzQtMTQuMzlzLTUuNTMtMTQuNC0xNy4zNC0xNC40aC0xNC42NFY3M3ptMzcuMTQgMTEuNzJ2LS42MWMwLTE5LjkzIDEyLjkxLTMzIDMyLTMzIDE4IDAgMzEgMTIuNjcgMzEgMzMuNDZ2NC4xOGgtNDQuOWMxIDkuNzIgNiAxNC4yNyAxNC4xNCAxNC4yNyA1LjkxIDAgMTAuMDktMi41OCAxMS45NC03aDE4LjJjLTIuNyAxMi40Mi0xNC42NCAyMS0zMC4yNiAyMS0xOS44Mi4wNC0zMi4xMi0xMS45Ni0zMi4xMi0zMi4zem0xOC41Ny03Ljg3aDI2LjJjLTEuNzItOC4xMi02LjI3LTEyLjE4LTEyLjkxLTEyLjE4cy0xMS42OSAzLjk0LTEzLjI5IDEyLjE4em01NC43My0yMy43NGgxNy41OXY5LjQ3QzM1NC4yNiA1NS44OCAzNjEgNTIuMzEgMzcwIDUyLjMxaDMuNTZ2MTdoLTE1LjVjLTQuNjcgMC03LjEzIDIuNDYtNy4xMyA3djM4Ljc1aC0xOC4wN3pNMzc4LjEyIDk2SDM5NWMuNjIgNC45MiA0LjU1IDcuNSAxMy4yOSA3LjUgNy4zOCAwIDEwLjk0LTIuMzMgMTAuOTQtNS42NSAwLTIuNzEtMS43Mi0zLjk0LTYuMTUtNC44bC0xNC43OS0yLjg1Yy0xMy42NS0yLjQ2LTE5LjA2LTguMTItMTkuMDYtMTguMDggMC0xMi4zIDEwLTIxIDI3LjkyLTIxIDE2LjczIDAgMjguMTcgOC4zNyAyOSAyMC41NGgtMTYuODJjLS43NC00LjQyLTQuOC03LTEyLjMtNy03IDAtMTAuNDYgMi4yMi0xMC40NiA1LjU0IDAgMi41OCAxLjcyIDMuODEgNiA0LjY3bDE0Ljg4IDIuODNjMTMuNTMgMi40NiAxOC45NCA4LjEyIDE4Ljk0IDE4LjIxIDAgMTIuOTEtMTAuNDUgMjEuMTUtMjguMjkgMjEuMTUtMTguNzktLjA2LTI5LjM3LTguNzgtMjkuOTgtMjEuMDZ6bTY4LjEzLTQyLjk1aDE3LjU5djkuODRjMy43LTcuMzggMTAuNzEtMTEuNTYgMTkuNTYtMTEuNTYgMTQuODkgMCAyNi4zMyAxMS42OCAyNi4zMyAzMnYuNjJjMCAyMC4xNy0xMi4wNiAzMi44NC0yNi45NCAzMi44NC04LjEyIDAtMTQuNzYtMy4zMi0xOC40NS05LjQ3djMwLjYzaC0xOC4wOXpNNDkxIDg0LjQydi0xLjZjMC0xMS4zMi00LjkyLTE3LjM1LTEzLjE2LTE3LjM1LTguNjEgMC0xMy41MyA1LjkxLTEzLjUzIDE3LjIzdjIuN2MwIDEyLjE4IDUgMTcuMjIgMTMuMTYgMTcuMjIgNy45IDAgMTMuNTMtNi42MiAxMy41My0xOC4yem0yNi4xLjI0di0uNjFjMC0xOS45MyAxMi45Mi0zMyAzMi0zMyAxOCAwIDMxIDEyLjY3IDMxIDMzLjQ2djQuMThoLTQ0LjljMSA5LjcyIDYgMTQuMjcgMTQuMTUgMTQuMjcgNS45IDAgMTAuMDgtMi41OCAxMS45My03aDE4LjJjLTIuNyAxMi40Mi0xNC42MyAyMS0zMC4yNSAyMS0xOS44My4wNC0zMi4xMy0xMS45Ni0zMi4xMy0zMi4zem0xOC41Ny03Ljg3aDI2LjIxYy0xLjczLTguMTItNi4yOC0xMi4xOC0xMi45Mi0xMi4xOHMtMTEuNjkgMy45NC0xMy4yOSAxMi4xOHptNTEuNTMgNy43NXYtLjc0YzAtMTkuMzEgMTMuMTYtMzIuNzIgMzEuMzctMzIuNzIgMTYuMjQgMCAyNy44IDkuMzUgMjkuNCAyNC42aC0xNy43MWMtLjc0LTYuNjQtNS40Mi0xMC40NS0xMS40NC0xMC40NS03Ljg4IDAtMTMgNi41Mi0xMyAxOC4ydjEuNDhjMCAxMS44MSA1LjE2IDE4IDEyLjc5IDE4IDYuNTIgMCAxMC45NS00LjA2IDExLjgxLTExLjJoMTcuNzFjLTEuNiAxNS43NS0xMyAyNS4zNC0yOS43NyAyNS4zNEM2MDAgMTE3IDU4Ny4yIDEwNC4zNCA1ODcuMiA4NC41NHptNzYuMDEgOS4zNVY2Ny4zMmgtMTAuN1Y1My4wNWgxMC43di0xOGgxOHYxOGgxOC4yMXYxNC4yN2gtMTguMjV2MzAuNzVjMCAxLjcyLjc0IDIuNDYgMi40NiAyLjQ2aDE1Ljc1djE0LjUySDY4NS42Yy0xNC41MiAwLTIyLjM5LTcuNTEtMjIuMzktMjEuMTZ6bTQ2LTU4LjMxYTEwLjA5IDEwLjA5IDAgMCAxIDIwLjE3IDAgMTAuMDkgMTAuMDkgMCAxIDEtMjAuMTcgMHptMSAxNy40N2gxOC4wOHY2MmgtMTguMXptMjUuMTkgMEg3NTRsMTQuMzkgNDUuMjcgMTQuMjctNDUuMjdoMTcuNzFsLTIwLjkxIDYyaC0yMy4xNXptNjguODggMzEuNjF2LS42MWMwLTE5LjkzIDEyLjkyLTMzIDMyLTMzIDE4IDAgMzEgMTIuNjcgMzEgMzMuNDZ2NC4xOGgtNDQuOWMxIDkuNzIgNiAxNC4yNyAxNC4xNSAxNC4yNyA1LjkgMCAxMC4wOS0yLjU4IDExLjkzLTdoMTguMjFjLTIuNzEgMTIuNDItMTQuNjQgMjEtMzAuMjYgMjEtMTkuODMuMDQtMzIuMTMtMTEuOTYtMzIuMTMtMzIuM3ptMTguNTctNy44N2gyNi4yMWMtMS43My04LjEyLTYuMjgtMTIuMTgtMTIuOTItMTIuMThzLTExLjY5IDMuOTQtMTMuMjkgMTIuMTh6IgogICAgICAgZmlsbD0iIzQ1MTczNSIKICAgICAgIGlkPSJwYXRoMiIgLz4KICA8L2c+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 7, + "name": "Utility" + }, + { + "id": 10, + "name": "Analytics" + } + ], + "displayName": "Google Perspective", + "typeVersion": 1 + } + ] + }, + { + "id": 628, + "name": "Receive updates from HubSpot when a new contact is created", + "totalViews": 376, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2020-08-31T00:27:22.044Z", + "nodes": [ + { + "id": 303, + "icon": "file:notion.svg", + "name": "n8n-nodes-base.notionTrigger", + "defaults": { + "name": "Notion Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyNTAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjEyIDAuMTkgNDg3LjYxOSA1MTAuOTQxIj48cGF0aCBkPSJNOTYuMDg1IDkxLjExOGMxNS44MSAxMi44NDUgMjEuNzQxIDExLjg2NSA1MS40MyA5Ljg4NGwyNzkuODg4LTE2LjgwNmM1LjkzNiAwIDEtNS45MjItLjk4LTYuOTA2TDM3OS45NCA0My42ODZjLTguOTA3LTYuOTE1LTIwLjc3My0xNC44MzQtNDMuNTE2LTEyLjg1M0w2NS40MDggNTAuNmMtOS44ODQuOTgtMTEuODU4IDUuOTIyLTcuOTIyIDkuODgzem0xNi44MDQgNjUuMjI4djI5NC40OTFjMCAxNS44MjcgNy45MDkgMjEuNzQ4IDI1LjcxIDIwLjc2OWwzMDcuNTk3LTE3Ljc5OWMxNy44MS0uOTc5IDE5Ljc5NC0xMS44NjUgMTkuNzk0LTI0LjcyMlYxMzYuNTdjMC0xMi44MzYtNC45MzgtMTkuNzU4LTE1Ljg0LTE4Ljc3bC0zMjEuNDQyIDE4Ljc3Yy0xMS44NjMuOTk3LTE1LjgyIDYuOTMxLTE1LjgyIDE5Ljc3NnptMzAzLjY1OSAxNS43OTdjMS45NzIgOC45MDMgMCAxNy43OTgtOC45MiAxOC43OTlsLTE0LjgyIDIuOTUzdjIxNy40MTJjLTEyLjg2OCA2LjkxNi0yNC43MzQgMTAuODctMzQuNjIyIDEwLjg3LTE1LjgzMSAwLTE5Ljc5Ni00Ljk0NS0zMS42NTQtMTkuNzZsLTk2Ljk0NC0xNTIuMTl2MTQ3LjI0OGwzMC42NzcgNi45MjJzMCAxNy43OC0yNC43NSAxNy43OGwtNjguMjMgMy45NThjLTEuOTgyLTMuOTU4IDAtMTMuODMyIDYuOTIxLTE1LjgxbDE3LjgwNS00LjkzNVYyMTAuN2wtMjQuNzIxLTEuOTgxYy0xLjk4My04LjkwMyAyLjk1NS0yMS43NCAxNi44MTItMjIuNzM2bDczLjE5NS00LjkzNEwzNTguMTg2IDMzNS4yMlYxOTguODM2bC0yNS43MjMtMi45NTJjLTEuOTc0LTEwLjg4NCA1LjkyNy0xOC43ODcgMTUuODE5LTE5Ljc2N3pNNDIuNjUzIDIzLjkxOWwyODEuOS0yMC43NmMzNC42MTgtMi45NjkgNDMuNTI1LS45OCA2NS4yODMgMTQuODI1bDg5Ljk4NiA2My4yNDdjMTQuODQ4IDEwLjg3NiAxOS43OTcgMTMuODM3IDE5Ljc5NyAyNS42OTN2MzQ2Ljg4M2MwIDIxLjc0LTcuOTIgMzQuNTk3LTM1LjYwOCAzNi41NjRMMTM2LjY0IDUxMC4xNGMtMjAuNzg1Ljk5MS0zMC42NzctMS45NzEtNDEuNTYyLTE1LjgxNWwtNjYuMjY3LTg1Ljk3OEMxNi45MzggMzkyLjUyIDEyIDM4MC42OCAxMiAzNjYuODI4VjU4LjQ5NWMwLTE3Ljc3OCA3LjkyMi0zMi42MDggMzAuNjUzLTM0LjU3NnoiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Notion Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 1344, + "name": "Save Email Attachments to Cloud Storage (Nextcloud)", + "totalViews": 362, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-11-29T13:59:16.771Z", + "nodes": [ + { + "id": 10, + "icon": "file:amqp.png", + "name": "n8n-nodes-base.amqp", + "defaults": { + "name": "AMQP Sender" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AMQP Sender", + "typeVersion": 1 + }, + { + "id": 14, + "icon": "file:apiTemplateIo.svg", + "name": "n8n-nodes-base.apiTemplateIo", + "defaults": { + "name": "APITemplate.io" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgdmlld0JveD0iMCAwIDIzLjE2NyAyMS4xNjciPjxwYXRoIGZpbGw9IiM5MWJjZTUiIGQ9Ik0xOC42MTEuNTA0TDMuNDczIDExLjI0bDQuNTk3IDIuMjEzIDEuMDY2IDMuODgzIDMuMTcxLTIuMjI2IDIuODAyLjk1MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4xNzIgMTcuNTk3YS4yNjQuMjY0IDAgMDEtLjI5LS4xOTJsLTEuMDM1LTMuNzY3LTQuNDg4LTIuMTZhLjI2NS4yNjUgMCAwMS0uMDM5LS40NTRMMTguNDU4LjI4OGEuMjY0LjI2NCAwIDAxLjQxLjI3NGwtMy41IDE1LjU1NmEuMjY1LjI2NSAwIDAxLS4zNDQuMTkzbC0yLjY3NC0uOTA4LTMuMDYxIDIuMTVhLjI3My4yNzMgMCAwMS0uMTE3LjA0NHptLTUuMTgtNi40MDFsNC4xOTMgMi4wMTdjLjA2OS4wMzQuMTIuMDk1LjE0LjE2OWwuOTY3IDMuNTIgMi44NjMtMi4wMWEuMjY1LjI2NSAwIDAxLjIzNy0uMDMzbDIuNTI0Ljg1NkwxOC4yIDEuMTE5eiIvPjxwYXRoIGZpbGw9IiMzYTg0YzEiIGQ9Ik05LjMzOSAxNy4wNzlsLjk0Ni0yLjkxM0wxOC42NDIuNzI4IDguMjI2IDEzLjIzNXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNOS4zNTYgMTcuMjFhLjEzMi4xMzIgMCAwMS0uMTQzLS4xNzJsLjk0Ni0yLjkxM2EuMTYuMTYgMCAwMS4wMTQtLjAzbDcuMzkzLTExLjg4OC05LjQxOSAxMS4xNTZhLjEzMy4xMzMgMCAwMS0uMjAyLS4xNzFMMTguNTQuNjQyYS4xMzIuMTMyIDAgMDEuMjE0LjE1NmwtOC4zNDggMTMuNDI0LS45NDEgMi44OThhLjEzMi4xMzIgMCAwMS0uMTA5LjA5em0tMy4yMTUgMS4wNDVjLTEuMTY4LjAyOS0yLjMwMy0uMzczLTMuMDY3LTEuMjg3LS40ODUtLjU4LS42ODMtMS41NzQuMjc1LTEuNzA1Ljg1OS0uMTE5IDEuMjI5LjQ4NSAxLjQ4IDEuMTk2LjgwNCAyLjAyNi4xNiAyLjkzOC0xLjY4IDQuMjcyLS4xNDQuMDkuMDE1LjI5Ni4xNTkuMjA2IDEuMjE4LTEgMS44MjktMS40NjYgMi4xLTIuNDQ4LjEzNC0xLjM2LS40MDItMy4wOTEtMS4zODQtMy40MzYtMS4xMi0uMzgyLTIuMDYzLjUxNi0xLjQ5OSAxLjU4NS42OTkgMS4zMjQgMi4yMyAxLjkxNCAzLjY1MiAxLjg3OC4xNy0uMDA0LjEzMy0uMjY2LS4wMzYtLjI2MXoiLz48cGF0aCBmaWxsPSIjMWYyMTJiIiBkPSJNNy4yMzUgMTguMTRhMTQuOTUgMTQuOTUgMCAwMS0uNzU5LjA5Yy0uMTcuMDE3LS4xMzMuMjc5LjAzNi4yNjIuMjUzLS4wMjUuNTA3LS4wNTIuNzU4LS4wOS4xNjktLjAyNi4xMzMtLjI4OC0uMDM1LS4yNjJ6bTEuMTk0LS41MDRhOC40NiA4LjQ2IDAgMDEtLjg2NS4zNDRjLS4xNjIuMDU0LS4wNTcuMjk3LjEwNC4yNDNhOC4zNyA4LjM3IDAgMDAuODY1LS4zNDRjLjE1NC0uMDcxLjA1LS4zMTUtLjEwNC0uMjQzeiIvPjxwYXRoIGQ9Ik0xMi41OTkgMTUuMjJhLjEzMS4xMzEgMCAwMS0uMDctLjAxbC0yLjE2OC0uOTI0YS4xMzMuMTMzIDAgMDEuMTAzLS4yNDNsMi4xNjkuOTIzYS4xMzMuMTMzIDAgMDEtLjAzNC4yNTN6Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "APITemplate.io", + "typeVersion": 1 + }, + { + "id": 25, + "icon": "file:sns.svg", + "name": "n8n-nodes-base.awsSns", + "defaults": { + "name": "AWS SNS" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTk4Ljg3NSAyMzIuMDMzbC0yNi40MzMtNy40MDgtMjUuMDAxLTI4LjUwOCAzMS4yNzItLjg2MyAyMC4xNjIgMzYuNzc5bS02MS4xMjUtMTguOGwtMTQuODc1LTQuMTY2LTE0LjA1OC0xNi4wMzQgMTcuMDgyLTIuODA5IDExLjg1MSAyMy4wMDkiIGZpbGw9IiM5OTVCODAiLz48cGF0aCBkPSJNMCAxOTEuMDE3bDE1LjIwNCAzLjA5MSAyLjIwNy0zLjg4MlY1OC41MDNsLTIuMjA3LTIuNTYxTDAgNjQuNnYxMjYuNDE3IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTczLjkzMyA2OS43MDhMMTUuMjA4IDU1Ljk0MnYxMzguMTY2bDguNzk4LS44MTggMTMuNzQ0IDE5Ljk0MyAxMC42LTIyLjIwNSAyNS41ODMtMi4zNzhWNjkuNzA4IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTMzLjk1OCAxOTguMTMzbDI2LjA2MyA1LjI1IDEuNzE2LTQuMDQ1VjM3LjQ0bC0xLjcxNi0zLjY2NS0yNi4wNjMgMTMuMjA4djE1MS4xNSIgZmlsbD0iIzdCM0Y2NSIvPjxwYXRoIGQ9Ik0yMDguNzM0IDgxLjUxNkw2MC4wMjEgMzMuNzc1djE2OS42MTJsMTcuMjIxLTIuMjE2IDIxLjYzMyAzMC44NjIgMTcuMTI2LTM1Ljg1IDkyLjczMy0xMS45MzNWODEuNTE2IiBmaWxsPSIjQzE3QjlEIi8+PHBhdGggZD0iTTE4MS44MzMgMjU2LjQ5MmwtMzcuNTY2LTEwLjUyNS0zNS41MDktNDAuNSA0Ni4wMzMtLjQ2OCAyNy4wNDIgNTEuNDkzIiBmaWxsPSIjOTk1QjgwIi8+PHBhdGggZD0iTTg5LjU5MSAyMDguOTVsMzguMzMgNy40MTcgMi45NzctMi41NjZWNC4xMTdMMTI3LjkyMSAwbC0zOC4zMyAxOS4xNThWMjA4Ljk1IiBmaWxsPSIjN0IzRjY1Ii8+PHBhdGggZD0iTTI1NiA2NC4wMzNMMTI3LjkyNSAwdjIxNi4zNjdsMjIuNTk3LTQuNTI4IDMxLjMxMSA0NC42NTMgMjYuOTAxLTU2LjMwOS0uMDE3LS4wMDJMMjU2IDE5MC43MDhWNjQuMDMzIiBmaWxsPSIjQzE3QjlEIi8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AWS SNS", + "typeVersion": 1 + } + ] + }, + { + "id": 1225, + "name": "Export New Deals from CRM to Internal Messaging, Email and Database", + "totalViews": 309, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-09-10T14:28:58.405Z", + "nodes": [ + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 38, + "icon": "file:chargebee.png", + "name": "n8n-nodes-base.chargebeeTrigger", + "defaults": { + "name": "Chargebee Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAAk1BMVEUAAAD9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azX9azXS0qjqAAAAMHRSTlMAAta1CPr3HAyb8mNPLBLq5MmGZ1wi28+soEQX7pZ9dkk/MijDurCMims54b6lb1YyU8hoAAACDklEQVRIx+2V2bKiMBBAmyTs+w6CIi64O/n/rxtKJwIjJPA4Nfc8acOpTnfSAX7493G2QZN6Zeml+GwVS0xiNSXtYeBIm6lKEaJf3C5kjrvz6SioFruVTKcIJEG1D8oBa1wXUy6+w2lVxhFFuU+0j4KOjwdOjUHQnHJ/DVZ4iaXXatTo2OuhPuEWXm9fLOhxPQjldeeuyV/NqGT+su1ucZuRkt5PMRElDmCEbdK2MNcmKnaZi2EUrd7GMEHEXGMPi8mYfFruqjd2NLrE+/P9oF9nyCuW+P4JhcorcBbL4dc2WSxyEcoBe9Vi5yJlEc8RyTp7ldVo0w8rkZyzN3ddExjR4sw7TmZhzSRhEddZ3m2TRar5+3z8hDZ/xlMSnzCPnbDu9NcPhLItzKAZG0hJnHVYYhnDYtSS2RksJ+fcQi0qAbDtKXknU84oWPQJtizvxanpmcCQJ3VtiA1lUlbdzk7rfs/bzwFSVR/bsaDhjHukvvfJsbBXm8S+UboXTwfDQFjXG6S0/dde18oGrN20TI4DOzHaA3drzAJWPqVy2Fa+5qTWDn05AKJJbS+eafsHtUlRUgAHB/dkJT+ddJzI7U+3kopjVgiGUwroF7J/IWAac+7RFeo0D6X3daQCiRBNZs2XVrmDr4e2zQ1aVhrMwwl9+Z1Zzw42ptQPHZiPFJv5IXENWXGvdRhL8MN/y2+uRpQ1fWz5HAAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "displayName": "Chargebee Trigger", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:clearbit.svg", + "name": "n8n-nodes-base.clearbit", + "defaults": { + "name": "Clearbit" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Clearbit", + "typeVersion": 1 + }, + { + "id": 76, + "icon": "fa:sign-in-alt", + "name": "n8n-nodes-base.executeWorkflow", + "defaults": { + "name": "Execute Workflow", + "color": "#ff6d5a" + }, + "iconData": { + "icon": "sign-in-alt", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Execute Workflow", + "typeVersion": 1 + }, + { + "id": 112, + "icon": "file:harvest.png", + "name": "n8n-nodes-base.harvest", + "defaults": { + "name": "Harvest" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Harvest", + "typeVersion": 1 + }, + { + "id": 303, + "icon": "file:notion.svg", + "name": "n8n-nodes-base.notionTrigger", + "defaults": { + "name": "Notion Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyNTAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjEyIDAuMTkgNDg3LjYxOSA1MTAuOTQxIj48cGF0aCBkPSJNOTYuMDg1IDkxLjExOGMxNS44MSAxMi44NDUgMjEuNzQxIDExLjg2NSA1MS40MyA5Ljg4NGwyNzkuODg4LTE2LjgwNmM1LjkzNiAwIDEtNS45MjItLjk4LTYuOTA2TDM3OS45NCA0My42ODZjLTguOTA3LTYuOTE1LTIwLjc3My0xNC44MzQtNDMuNTE2LTEyLjg1M0w2NS40MDggNTAuNmMtOS44ODQuOTgtMTEuODU4IDUuOTIyLTcuOTIyIDkuODgzem0xNi44MDQgNjUuMjI4djI5NC40OTFjMCAxNS44MjcgNy45MDkgMjEuNzQ4IDI1LjcxIDIwLjc2OWwzMDcuNTk3LTE3Ljc5OWMxNy44MS0uOTc5IDE5Ljc5NC0xMS44NjUgMTkuNzk0LTI0LjcyMlYxMzYuNTdjMC0xMi44MzYtNC45MzgtMTkuNzU4LTE1Ljg0LTE4Ljc3bC0zMjEuNDQyIDE4Ljc3Yy0xMS44NjMuOTk3LTE1LjgyIDYuOTMxLTE1LjgyIDE5Ljc3NnptMzAzLjY1OSAxNS43OTdjMS45NzIgOC45MDMgMCAxNy43OTgtOC45MiAxOC43OTlsLTE0LjgyIDIuOTUzdjIxNy40MTJjLTEyLjg2OCA2LjkxNi0yNC43MzQgMTAuODctMzQuNjIyIDEwLjg3LTE1LjgzMSAwLTE5Ljc5Ni00Ljk0NS0zMS42NTQtMTkuNzZsLTk2Ljk0NC0xNTIuMTl2MTQ3LjI0OGwzMC42NzcgNi45MjJzMCAxNy43OC0yNC43NSAxNy43OGwtNjguMjMgMy45NThjLTEuOTgyLTMuOTU4IDAtMTMuODMyIDYuOTIxLTE1LjgxbDE3LjgwNS00LjkzNVYyMTAuN2wtMjQuNzIxLTEuOTgxYy0xLjk4My04LjkwMyAyLjk1NS0yMS43NCAxNi44MTItMjIuNzM2bDczLjE5NS00LjkzNEwzNTguMTg2IDMzNS4yMlYxOTguODM2bC0yNS43MjMtMi45NTJjLTEuOTc0LTEwLjg4NCA1LjkyNy0xOC43ODcgMTUuODE5LTE5Ljc2N3pNNDIuNjUzIDIzLjkxOWwyODEuOS0yMC43NmMzNC42MTgtMi45NjkgNDMuNTI1LS45OCA2NS4yODMgMTQuODI1bDg5Ljk4NiA2My4yNDdjMTQuODQ4IDEwLjg3NiAxOS43OTcgMTMuODM3IDE5Ljc5NyAyNS42OTN2MzQ2Ljg4M2MwIDIxLjc0LTcuOTIgMzQuNTk3LTM1LjYwOCAzNi41NjRMMTM2LjY0IDUxMC4xNGMtMjAuNzg1Ljk5MS0zMC42NzctMS45NzEtNDEuNTYyLTE1LjgxNWwtNjYuMjY3LTg1Ljk3OEMxNi45MzggMzkyLjUyIDEyIDM4MC42OCAxMiAzNjYuODI4VjU4LjQ5NWMwLTE3Ljc3OCA3LjkyMi0zMi42MDggMzAuNjUzLTM0LjU3NnoiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Notion Trigger", + "typeVersion": 1 + } + ] + }, + { + "id": 1221, + "name": "Send Reminders After Meetings", + "totalViews": 281, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-09-08T08:45:12.497Z", + "nodes": [ + { + "id": 28, + "icon": "file:beeminder.png", + "name": "n8n-nodes-base.beeminder", + "defaults": { + "name": "Beeminder" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAABelBMVEUAAAA6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw7Ozw7Oz46Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw6Ojw7Oz07Oz06Ojw6Ojw6Oj07Oz06Ojw6Ojw6Ojw6Ojw6Ojw7Ozw5OTw7Oz06Ojw6Oj06Ojw6Ojw6Ojw6Ojz/ywf/zgYyND03ODw1NjwvMj3/0wRCQUD2xxL/1Af/zwT/0QRPSz8vMj//0Qo9PUAyNUE0NTz/zwotMEAxM0BYUT5wZDp3ajn/0ggxND7vwxP/1QRGREA3OEBUTz5kWz2iiS6skCvtwBb4yQ7uvgw5OUI0NkFKRj9rYDx8bTiXgTG/oCPWsB3btBzhuBn0xBD6yw/9ywz/2QFSTT1cVDs6OjpsYDmCcTaZgzCTfTC6myfFoiTfthrovhlbUz1gWDxoXjqKdzSPejPCoSmylSm2mCjDoCHPqyD9zQz/0wv/1wR0ZjmpjizKpiPTrR/iuhjquw1UUEaPfD6tkjF8XVfuAAAAKXRSTlMA59K3oPqzMQkF9smWj0AsJ+7g2HpcGhGag2JSTPPMxq2piXRuNta/ZZKUy0EAAAOpSURBVEjH7ZVnW9pQGIbD0jqq1tm915NxEggBwpSNWFoHG7TuvbW7/e89SUA+kLbRr/X+ALmS685zzsl73sPc8D/zYhRwOUa0y2GHCxh9ad29DR3bSB8zbIPOc6vuEIBkkFDbPkBdEk0CeGVRHgSy66UYDx0+VloXgNsWZQdCO+fNskp4kSdErTQDsRB6LMrPILi3lPTa3G58eimSl5WKW4DTonwfEBfOuNoWGwjXNmve+gwPTFiUbw0AkdkE52VZllO4xOcI8OTWv72+KWPciM4qbIvAtAg80B72/S1x4qndZu+533sH/F6YbaN8yQKDI+MO++s390YYc3ofw6Af8G80L2VOpnPu74fOo3vm7h1AoB9HAIVMV6WW6w0cZYx7okgA3DUbsx3g1YXFPbcoABDdR2Gv5krb8ajmhKJL8XguKQD3u+W3Wi2Vw4nwt/1sSMvxx7dl1pvYmBdB8ec2PIFEIZ8FRrsX/gkEtZLWhil/yUX08PljObUaDGmxwdWUxNHpX+yLwMMu+RHERZnVkVL7ukKC8ZgeK/pKCc5YgC1VMClzwD8rXa7voU+3kgTarlp8J7EtGss8HCZy8ERh20jvVlUYCL7jgJdtE57m8dhEntvsyGy6PhOCztyvZsdlA4si7GbJJalTF1+XSTsZBx6lk7zCY8Bszvm2rNTXPvCa50/qvzunMtd6ayFHTLY2reeVQCu2PBMxCiUf9xMA/Idi3QhXvn4A7pm0D2QPLziOk87yqh4bmamkw5/don69W057OU45/8QDQ13yQ4C4j1KFVGnPr8WG3PmwxHmb1RW92nl1veIpbC0mgYE+xiyaROdz80E9Vvxx6Em9o9S2f7bmry7l1CS04G4m7aB6iECf5NpCxm2QyRRXIoKmE6L9mff/qbsuGJDYqfxp3tcic3JeVAUY2B8wf2B43Ol02sAveyS24LmkwV1szAEOp/P5w3+0sh4qN+gH60Cr9SQKDFk6qAS1qhVkx2XldT9cIxbkB639Vfv+Xuc7fU1jh8dYnwV5ktbaboPzVn3kIyV0EGalzaDVw8oJRI/TrHI6TVk4KHDc2Qxv9ZjsdYFkqhJtmxq0haTX/ECP9SOWj2132srsnABXr0V5aozavrJkuOFiUABeMFYZtlHbvSlrHa++GhG0Tm+d3lGAZOlBqaQWIgAGmaswOaB13qL8Te+948zVmOoBhGh8ibr9E8yVGQTAE8A2xFyDcWiMDTPXYmIM/U8nmRtuuD6/AQzW+dqtokXXAAAAAElFTkSuQmCC" + }, + "categories": [ + { + "id": 4, + "name": "Productivity" + } + ], + "displayName": "Beeminder", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:clearbit.svg", + "name": "n8n-nodes-base.clearbit", + "defaults": { + "name": "Clearbit" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Clearbit", + "typeVersion": 1 + }, + { + "id": 221, + "icon": "file:stackby.png", + "name": "n8n-nodes-base.stackby", + "defaults": { + "name": "Stackby" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 3, + "name": "Data & Storage" + } + ], + "displayName": "Stackby", + "typeVersion": 1 + } + ] + }, + { + "id": 1205, + "name": "Promote New Shopify Products on Social Media (Twitter and Telegram)", + "totalViews": 219, + "recentViews": 0, + "user": { + "username": "admin" + }, + "createdAt": "2021-08-24T10:40:50.007Z", + "nodes": [ + { + "id": 49, + "icon": "file:contentful.png", + "name": "n8n-nodes-base.contentful", + "defaults": { + "name": "Contentful" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAACzVBMVEUAAAD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////v3wV1FOteFQteH/11/8/f79/v///f3xWlX///4tsN//12BYuOPwXljORzn/2GXwYFry+Pz/1lrwVk/++/lTtuHvPzXvLyD//fdhueNbuOP/12L/1En/1EXvSkPq8/rO5fT/+e5LtOE9suD/9doAd7wIgbYAgbb/2nD/113MSjz+9PTU5/T98O+72++22e+GxedHs+AzseAArN73v74Ae7IAbq7/5KT0m5m6so//22H/2lnwTUb/1DzvRDvOTjrMPzfKRTbvOS7wNCnvKhjIAQD2+v3i7/jc6/fF4PL87Oz86umWy+mSyuh2wOX/9eH74eA/suA4sd8mr98crt763Nz/8tf61tb/8NL50tGiu8/6zs04ncz4yskbjcAAiMD/6r8AfLcohLYdgrYIgrYvhbU0hbT2s7L0pKLzlJHzjYvyf3v/23npzHjxcW3/3l7/2VXuVlDwUUvZT0TUSj7JLTjuJQ3+9fX//Oun0eqbzeqZzOl/wub/+eVtveSsyN8Prd4ArN2Ludnd2ss4ksL2uLcRgrb2t7Xy0LQAcbHorar+4qbczaPiqaDgpJ3nl5T0yY7Juou1rYbyhYLxeHX/33P/32/4yWTyZmHwZmH4yVvtsVPpp1PeWFD/2k/PVk3xUkvMVEnppEjILUTIIDvnoTj/1SvmmRDWLA7KHADUAAA0tfvvAAAAQ3RSTlMA/vz6AgQIBQOJf3UV+fjCbF1aVxEH9+7r5dzXyoRnUEpFKSUc8ejhtq6fm3FvMCwiCryxr6WWjnphQD42DdLNxZg46i9pNgAABRtJREFUSMelloV700AYxpNUJ8wZG3PB3V2vtq50LbSTlm0wxsYYDHfXbbi7u7u7u7u729/Ad+nCJaE0PPDuedbker973+/ukivlVgqlUk7u1EqlgvpLyZUq/BES3zwmODimRUIrfKeC0SQFNvAvMbBGZGU/xMovvG3jYF9olMYBLR9YtRISqULDGPZLT1KpqXpNqyAsmieEFRGgplQKj7bR4RzImZLbisEezMtTdf1dfZFIXGMD3z/QcjUVFIqIp1s8LBYs3LAU5YUQQ1B3OINQTUold8NWxyzyKEx7Uarf4eoYlRSmxclVkPk31pjkklFE+1BKIRsIrIjkXxsFdCyfVlPxDDSK0W59pw8Y0H/6NDtcJ/HpMF9B3ZFIxmeTgBxwSr/AjJWfOSs3CRmNvFmrSmAFVQtYga39uN6cn6fTg3S6QYPNc3KJOc4YRIL7VuKHpjug3KzBeXq+5i+cbf9Fw16oqKLkZcZeqBxhOyB0du2aVTqw5IRxc+ZUQsNe4axDKghna8XwjT2HrckDKMNisWS4+Ix8HdDEWgHW2DiAXzGNLg7feKXnpmGrLFkZ+eaFgwdZLC7aPBAZiTVXdSTPWIN6PCjYXAjwJd35+XNmnxyYucjMuusHZdmB5qzxhIN5HbgmBac/GVWwpbCw54a1lv7dcMwp02Yt0lnyAM+086wrJVIK+KsJqYnxui+71xdu2bxh+AU74tRXvyAzK2vICWBJ7kDIraYa8Evu3Ct1zP31BQXXVyCuK3x0OzPk3JCBdgFcAxetqoIYYrxsXCfDmN2jRt3qgDsgDp+S279vEmFxoZF4uuvKEK/kEV07GQxj9r05DQN5ECBhITDXzfmLnL5rX7YhJbvLCGAlYFkdgIOQjBh3T01JMRiyuyyRhlEcwAF8eG4XSG3ondJdCoa0wQD78OEeuOSU1F6d4VrKudn/wrX+NXYQwIH/OmGxAMfxl6rzrtRsyN31pgjWJCcna4QwUxvgeFq8SXqP3rv3mIBOdo0ggCv4AlwvXLQ9e49+8erzPUwQNn350KUa3EK2Z2v2qKgvejBGv5x4+PD328BoyhKjGY+KJzt3HCXekLURHBwKylv4SI57OnHihKKi0rt9WBL3X2obbzWZjmwDawJHs49kexybvAwef/g0oWPHjkXfng2dgcn0xSMdB0xarfbA8z6cNcChCa53d4TwNfT1I4YnHXxXnLZz5MjtexwOE2a1abZ+YM2V7A+ZKS43CXSt9C0Lm0xpOQ7H+LGAYpnGP0Qaza/UAewLUC54ovG2vFP6vmjSof0YAGEQbK1jc+aBMQdXDoGCWevqfGsY/MaPQ6+BJUoz2SZbCQupvbAxa53AwC2fXqktNtk4T63Waht7ZAcULNghCu6ga4LPG0LTqN+2HGeOyWqzWm02a5qjZM9qxGNpwfmu5F6CxHzm1Z05zhKn01ky2bp9ZTpuI0dsG8hLztg4JKMFtAacZi4funXr5dWL+wj3NQPPBF5jQkchP4TEOCcWJaHZZRKompgGhJPo11Q78BX9DPNHoUhatAxVw91FtMIf+dFSLMNgVi1k2YaGSMZ4ZmVwRBFWmDwKoXIezBnYDLVIZjEdFw5F0X+sFkW0hIVxL/iiXhOE3IZnAC3nLSfz7I6m4hsxuCvD0GRxZHi40MaJXLke8ISoiq6YZUJYEU0T8ZcSkquhU4so/zASuXJ975Z4XDxTkjhrEFI7JtrH29snOrZOK4qHSksNXQXDua/1J0BR0xDIlyn3AAAAAElFTkSuQmCC" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + }, + { + "id": 5, + "name": "Development" + } + ], + "displayName": "Contentful", + "typeVersion": 1 + }, + { + "id": 107, + "icon": "file:gotify.png", + "name": "n8n-nodes-base.gotify", + "defaults": { + "name": "Gotify" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,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" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Gotify", + "typeVersion": 1 + }, + { + "id": 325, + "icon": "file:dropcontact.svg", + "name": "n8n-nodes-base.dropcontact", + "defaults": { + "name": "Dropcontact" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjkuNTY5MiAxNi4yMTlDMjguMTQ2OSAxNS41MzcxIDI2LjU1MzUgMTUuMTU1MiAyNC44NzA5IDE1LjE1NTJDMTguODYxNSAxNS4xNTUyIDEzLjk4OTkgMjAuMDI2OCAxMy45ODk5IDI2LjAzNjJDMTMuOTg5OSAzMi4wNDU2IDE4Ljg2MTUgMzYuOTE3MiAyNC44NzA5IDM2LjkxNzJDMjguOTYyMSAzNi45MTcyIDMyLjUyNiAzNC42NTkyIDM0LjM4NDMgMzEuMzIxNUwzMS43NjE0IDI5Ljg2NDNDMzAuNDE1NCAzMi4yODE4IDI3LjgzNDEgMzMuOTE3MiAyNC44NzA5IDMzLjkxNzJDMjAuNTE4MyAzMy45MTcyIDE2Ljk4OTkgMzAuMzg4OCAxNi45ODk5IDI2LjAzNjJDMTYuOTg5OSAyMS42ODM2IDIwLjUxODMgMTguMTU1MiAyNC44NzA5IDE4LjE1NTJDMjYuMDg5NiAxOC4xNTUyIDI3LjI0MzcgMTguNDMxOCAyOC4yNzM4IDE4LjkyNTdMMjkuNTY5MiAxNi4yMTlaTTM0Ljc3NzkgMS45ODQ0NFYyNy43NDYxSDMxLjY2OVYwLjg5OTM2M0MyOS41NDU5IDAuMzEzMTcxIDI3LjMwOTUgMCAyNSAwQzExLjE5MjkgMCAwIDExLjE5MjkgMCAyNUMwIDM4LjgwNzEgMTEuMTkyOSA1MCAyNSA1MEMzOC44MDcxIDUwIDUwIDM4LjgwNzEgNTAgMjVDNTAgMTQuNjYyNiA0My43MjU5IDUuNzkwNyAzNC43Nzc5IDEuOTg0NDRaIiBmaWxsPSIjMEFCQTlGIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Dropcontact", + "typeVersion": 1 + } + ] + } + ], + "filters": [ + { + "counts": [ + { + "count": 24, + "highlighted": "Sales", + "value": "Sales" + }, + { + "count": 12, + "highlighted": "Marketing & Growth", + "value": "Marketing & Growth" + }, + { + "count": 6, + "highlighted": "Building Blocks", + "value": "Building Blocks" + }, + { + "count": 3, + "highlighted": "Finance & Accounting", + "value": "Finance & Accounting" + }, + { + "count": 2, + "highlighted": "HR & People Ops", + "value": "HR & People Ops" + } + ], + "field_name": "categories", + "sampled": false, + "stats": { + "total_values": 5 + } + } + ] +} diff --git a/cypress/fixtures/templates_search/test_template_import.json b/cypress/fixtures/templates_search/test_template_import.json new file mode 100644 index 0000000000000..c77be3db9c28d --- /dev/null +++ b/cypress/fixtures/templates_search/test_template_import.json @@ -0,0 +1,19 @@ +{ + "id": 60, + "name": "test1 test1", + "workflow": { + "nodes": [ + { + "name": "Start", + "type": "n8n-nodes-base.start", + "position": [ + 250, + 300 + ], + "parameters": {}, + "typeVersion": 1 + } + ], + "connections": {} + } +} diff --git a/cypress/fixtures/templates_search/test_template_preview.json b/cypress/fixtures/templates_search/test_template_preview.json new file mode 100644 index 0000000000000..4d3ca1e548f9a --- /dev/null +++ b/cypress/fixtures/templates_search/test_template_preview.json @@ -0,0 +1,150 @@ +{ + "workflow": { + "id": 60, + "name": "test1 test1", + "views": 120000000, + "recentViews": 0, + "totalViews": 120000000, + "createdAt": "2019-08-30T16:39:31.362Z", + "description": "here is a description. here is a description. here is a description. \n\n![Screenshot from 20190806 091433.png](fileId:88)", + "workflow": { + "nodes": [ + { + "name": "Start", + "type": "n8n-nodes-base.start", + "position": [ + 250, + 300 + ], + "parameters": {}, + "typeVersion": 1 + } + ], + "connections": {} + }, + "lastUpdatedBy": null, + "workflowInfo": { + "nodeCount": 1, + "nodeTypes": { + "n8n-nodes-base.start": { + "count": 1 + } + } + }, + "user": { + "username": "admin" + }, + "nodes": [ + { + "id": 11, + "icon": "file:amqp.png", + "name": "n8n-nodes-base.amqpTrigger", + "defaults": { + "name": "AMQP Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg==" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "AMQP Trigger", + "typeVersion": 1 + }, + { + "id": 18, + "icon": "file:autopilot.svg", + "name": "n8n-nodes-base.autopilot", + "defaults": { + "name": "Autopilot" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + } + ], + "displayName": "Autopilot", + "typeVersion": 1 + }, + { + "id": 20, + "icon": "file:lambda.svg", + "name": "n8n-nodes-base.awsLambda", + "defaults": { + "name": "AWS Lambda" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+" + }, + "categories": [ + { + "id": 5, + "name": "Development" + } + ], + "displayName": "AWS Lambda", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:clearbit.svg", + "name": "n8n-nodes-base.clearbit", + "defaults": { + "name": "Clearbit" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Clearbit", + "typeVersion": 1 + }, + { + "id": 51, + "icon": "file:convertKit.svg", + "name": "n8n-nodes-base.convertKitTrigger", + "defaults": { + "name": "ConvertKit Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTcyIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNODIuNzIgMTI2LjMxNmMyOS43NyAwIDUyLjc4LTIyLjYyMiA1Mi43OC01MC41MjYgMC0yNi4xNDMtMjEuNjE3LTQyLjEwNi0zNS45MzUtNDIuMTA2LTE5Ljk0NSAwLTM1LjkzIDE0LjA4NC0zOC4xOTggMzQuOTg4LS40MTggMy44NTYtMy40NzYgNy4wOS03LjM1NSA3LjA2MS02LjQyMy0uMDQ2LTE1Ljc0Ni0uMS0yMS42NTgtLjA4LTIuNTU1LjAwOC00LjY2OS0yLjA2NS00LjU0My00LjYxOC44OS0xOC4xMjMgNi45MTQtMzUuMDcgMTguNDAyLTQ4LjA4N0M1OC45NzYgOC40ODggNzcuNTYxIDAgOTkuNTY1IDBjMzYuOTY5IDAgNzEuODY5IDMzLjc4NiA3MS44NjkgNzUuNzkgMCA0Ni41MDgtMzguMzEyIDg0LjIxLTg3LjkyNyA4NC4yMS0zNS4zODQgMC03MS4wMjEtMjMuMjU4LTgzLjQ2NC01NS43NzVhLjcwMi43MDIgMCAwMS0uMDMtLjM3N2MuMTY1LS45NjIuNDk0LTEuODQxLjgxOC0yLjcwNy40NzEtMS4yNTguOTMxLTIuNDg4Ljg2NC0zLjkwNmwtLjIxNS00LjUyOWE1LjUyMyA1LjUyMyAwIDAxMy4xOC01LjI2M2wxLjc5OC0uODQyYTYuOTgyIDYuOTgyIDAgMDAzLjkxMi01LjA3NSA2Ljk5MyA2Ljk5MyAwIDAxNi44ODctNS43MzZjNS4yODIgMCA5Ljg3NSAzLjUxNSAxMS41OSA4LjUxMiA4LjMwNyAyNC4yMTIgMjEuNTExIDQyLjAxNCA1My44NzMgNDIuMDE0eiIgZmlsbD0iI0ZCNjk3MCIvPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 1, + "name": "Marketing" + }, + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "ConvertKit Trigger", + "typeVersion": 1 + } + ], + "categories": [], + "image": [] + } +} diff --git a/cypress/fixtures/workflow-with-unknown-credentials.json b/cypress/fixtures/workflow-with-unknown-credentials.json new file mode 100644 index 0000000000000..142422227c1ee --- /dev/null +++ b/cypress/fixtures/workflow-with-unknown-credentials.json @@ -0,0 +1,52 @@ +{ + "meta": { + "instanceId": "123" + }, + "nodes": [ + { + "parameters": { + "resource": "credential", + "name": "123", + "credentialTypeName": "123" + }, + "id": "a01f79f6-e8c3-44c5-be5e-4bc482e23172", + "name": "n8n", + "type": "n8n-nodes-base.n8n", + "typeVersion": 1, + "position": [ + 540, + 240 + ], + "credentials": { + "n8nApi": { + "id": "10", + "name": "n8n account" + } + } + }, + { + "parameters": {}, + "id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 300, + 240 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "n8n", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/cypress/fixtures/workflow-with-unknown-nodes.json b/cypress/fixtures/workflow-with-unknown-nodes.json new file mode 100644 index 0000000000000..5ea0189e50af4 --- /dev/null +++ b/cypress/fixtures/workflow-with-unknown-nodes.json @@ -0,0 +1,90 @@ +{ + "meta": { + "instanceId": "15bbf37b6a515ccc2f534cabcd8bd171ca33583ff7744b1e9420e5ce68e615bb" + }, + "nodes": [ + { + "parameters": {}, + "id": "40720511-19b6-4421-bdb0-3fb6efef4bc5", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 280, + 320 + ] + }, + { + "parameters": {}, + "id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa", + "name": "Unknown node 1", + "type": "n8n-nodes-base.thisNodeDoesntExist", + "typeVersion": 1, + "position": [ + 400, + 500 + ] + }, + { + "parameters": {}, + "id": "acdd1bdc-c642-4ea6-ad67-f4201b640ffa", + "name": "Unknown node 2", + "type": "n8n-nodes-base.thisNodeDoesntExistEither", + "typeVersion": 1, + "position": [ + 600, + 500 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "fbe5163b-7474-4741-980a-e4956789be0a", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 500, + 320 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "163313b9-64ff-4ffc-b00f-09b267d8132c", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 720, + 320 + ] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/cypress/pages/bannerStack.ts b/cypress/pages/bannerStack.ts new file mode 100644 index 0000000000000..dce3222126018 --- /dev/null +++ b/cypress/pages/bannerStack.ts @@ -0,0 +1,8 @@ +import { BasePage } from './base'; + +export class BannerStack extends BasePage { + getters = { + banner: () => cy.getByTestId('banner-stack'), + }; + actions = {}; +} diff --git a/cypress/pages/credentials.ts b/cypress/pages/credentials.ts index 7d3bf7ac9855e..24ec88565dd9e 100644 --- a/cypress/pages/credentials.ts +++ b/cypress/pages/credentials.ts @@ -5,7 +5,7 @@ export class CredentialsPage extends BasePage { getters = { emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'), createCredentialButton: () => cy.getByTestId('resources-list-add'), - searchInput: () => cy.getByTestId('resources-list-search').find('input'), + searchInput: () => cy.getByTestId('resources-list-search'), emptyList: () => cy.getByTestId('resources-list-empty'), credentialCards: () => cy.getByTestId('resources-list-item'), credentialCard: (credentialName: string) => @@ -17,8 +17,9 @@ export class CredentialsPage extends BasePage { this.getters.credentialCard(credentialName).findChildByTestId('credential-card-actions'), credentialDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'), - sort: () => cy.getByTestId('resources-list-sort'), - sortOption: (label: string) => this.getters.sort().contains(label).first(), + sort: () => cy.getByTestId('resources-list-sort').first(), + sortOption: (label: string) => + cy.getByTestId('resources-list-sort-item').contains(label).first(), filtersTrigger: () => cy.getByTestId('resources-list-filters-trigger'), filtersDropdown: () => cy.getByTestId('resources-list-filters-dropdown'), }; diff --git a/cypress/pages/demo.ts b/cypress/pages/demo.ts new file mode 100644 index 0000000000000..0590fb8def03e --- /dev/null +++ b/cypress/pages/demo.ts @@ -0,0 +1,21 @@ +/** + * Actions + */ + +export function vistDemoPage(theme?: 'dark' | 'light') { + const query = theme ? `?theme=${theme}` : ''; + cy.visit('/workflows/demo' + query); + cy.waitForLoad(); + cy.window().then((win) => { + // @ts-ignore + win.preventNodeViewBeforeUnload = true; + }); +} + +export function importWorkflow(workflow: object) { + const OPEN_WORKFLOW = {command: 'openWorkflow', workflow}; + cy.window().then($window => { + const message = JSON.stringify(OPEN_WORKFLOW); + $window.postMessage(message, '*') + }); +} diff --git a/cypress/pages/features/node-creator.ts b/cypress/pages/features/node-creator.ts index 8ebe6db702d6e..3e6a819443c8f 100644 --- a/cypress/pages/features/node-creator.ts +++ b/cypress/pages/features/node-creator.ts @@ -7,6 +7,7 @@ export class NodeCreator extends BasePage { plusButton: () => cy.getByTestId('node-creator-plus-button'), canvasAddButton: () => cy.getByTestId('canvas-add-button'), searchBar: () => cy.getByTestId('search-bar'), + getCategoryItem: (label: string) => cy.get(`[data-keyboard-nav-id="${label}"]`), getCreatorItem: (label: string) => this.getters.creatorItem().contains(label).parents('[data-test-id="item-iterator-item"]'), getNthCreatorItem: (n: number) => this.getters.creatorItem().eq(n), @@ -15,10 +16,12 @@ export class NodeCreator extends BasePage { selectedTab: () => this.getters.nodeCreatorTabs().find('.is-active'), categorizedItems: () => cy.getByTestId('categorized-items'), creatorItem: () => cy.getByTestId('item-iterator-item'), + categoryItem: () => cy.getByTestId('node-creator-category-item'), communityNodeTooltip: () => cy.getByTestId('node-item-community-tooltip'), - noResults: () => cy.getByTestId('categorized-no-results'), + noResults: () => cy.getByTestId('node-creator-no-results'), nodeItemName: () => cy.getByTestId('node-creator-item-name'), - activeSubcategory: () => cy.getByTestId('categorized-items-subcategory'), + nodeItemDescription: () => cy.getByTestId('node-creator-item-description'), + activeSubcategory: () => cy.getByTestId('nodes-list-header'), expandedCategories: () => this.getters.creatorItem().find('>div').filter('.active').invoke('text'), }; diff --git a/cypress/pages/index.ts b/cypress/pages/index.ts index 35ef30d5ec005..39c9be3b5648b 100644 --- a/cypress/pages/index.ts +++ b/cypress/pages/index.ts @@ -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'; @@ -9,3 +7,9 @@ export * from './settings-users'; export * from './settings-log-streaming'; export * from './sidebar'; export * from './ndv'; +export * from './bannerStack'; +export * from './workflow-executions-tab'; +export * from './signin'; +export * from './workflow-history'; +export * from './workerView'; +export * from './settings-public-api'; diff --git a/cypress/pages/mfa-login.ts b/cypress/pages/mfa-login.ts new file mode 100644 index 0000000000000..50ca5adab7f98 --- /dev/null +++ b/cypress/pages/mfa-login.ts @@ -0,0 +1,77 @@ +import { N8N_AUTH_COOKIE } from '../constants'; +import { BasePage } from './base'; +import { SigninPage } from './signin'; +import { WorkflowsPage } from './workflows'; + +export class MfaLoginPage extends BasePage { + url = '/mfa'; + getters = { + form: () => cy.getByTestId('mfa-login-form'), + token: () => cy.getByTestId('token'), + recoveryCode: () => cy.getByTestId('recoveryCode'), + enterRecoveryCodeButton: () => cy.getByTestId('mfa-enter-recovery-code-button'), + }; + + actions = { + loginWithMfaToken: (email: string, password: string, mfaToken: string) => { + const signinPage = new SigninPage(); + const workflowsPage = new WorkflowsPage(); + + cy.session( + [mfaToken], + () => { + cy.visit(signinPage.url); + + signinPage.getters.form().within(() => { + signinPage.getters.email().type(email); + signinPage.getters.password().type(password); + signinPage.getters.submit().click(); + }); + + this.getters.form().within(() => { + this.getters.token().type(mfaToken); + }); + + // we should be redirected to /workflows + cy.url().should('include', workflowsPage.url); + }, + { + validate() { + cy.getCookie(N8N_AUTH_COOKIE).should('exist'); + }, + }, + ); + }, + loginWithRecoveryCode: (email: string, password: string, recoveryCode: string) => { + const signinPage = new SigninPage(); + const workflowsPage = new WorkflowsPage(); + + cy.session( + [recoveryCode], + () => { + cy.visit(signinPage.url); + + signinPage.getters.form().within(() => { + signinPage.getters.email().type(email); + signinPage.getters.password().type(password); + signinPage.getters.submit().click(); + }); + + this.getters.enterRecoveryCodeButton().click(); + + this.getters.form().within(() => { + this.getters.recoveryCode().type(recoveryCode); + }); + + // we should be redirected to /workflows + cy.url().should('include', workflowsPage.url); + }, + { + validate() { + cy.getCookie(N8N_AUTH_COOKIE).should('exist'); + }, + }, + ); + }, + }; +} diff --git a/cypress/pages/modals/credentials-modal.ts b/cypress/pages/modals/credentials-modal.ts index 2f5fd298aa99d..08a258a05768e 100644 --- a/cypress/pages/modals/credentials-modal.ts +++ b/cypress/pages/modals/credentials-modal.ts @@ -1,4 +1,5 @@ import { BasePage } from '../base'; +import { getVisibleSelect } from '../../utils'; export class CredentialsModal extends BasePage { getters = { @@ -10,9 +11,7 @@ export class CredentialsModal extends BasePage { newCredentialTypeButton: () => cy.getByTestId('new-credential-type-button'), connectionParameters: () => cy.getByTestId('credential-connection-parameter'), connectionParameter: (fieldName: string) => - this.getters - .connectionParameters() - .find(`:contains('${fieldName}') .n8n-input input`), + this.getters.connectionParameters().find(`:contains('${fieldName}') .n8n-input input`), name: () => cy.getByTestId('credential-name'), nameInput: () => cy.getByTestId('credential-name').find('input'), // Saving of the credentials takes a while on the CI so we need to increase the timeout @@ -22,20 +21,17 @@ export class CredentialsModal extends BasePage { credentialsEditModal: () => cy.getByTestId('credential-edit-dialog'), credentialsAuthTypeSelector: () => cy.getByTestId('node-auth-type-selector'), credentialAuthTypeRadioButtons: () => - this.getters.credentialsAuthTypeSelector().find('label[role=radio]'), + this.getters.credentialsAuthTypeSelector().find('label.el-radio'), credentialInputs: () => cy.getByTestId('credential-connection-parameter'), menu: () => this.getters.editCredentialModal().get('.menu-container'), menuItem: (name: string) => this.getters.menu().get('.n8n-menu-item').contains(name), usersSelect: () => cy.getByTestId('credential-sharing-modal-users-select'), + testSuccessTag: () => cy.getByTestId('credentials-config-container-test-success'), }; actions = { addUser: (email: string) => { this.getters.usersSelect().click(); - this.getters - .usersSelect() - .get('.el-select-dropdown__item') - .contains(email.toLowerCase()) - .click(); + getVisibleSelect().contains(email.toLowerCase()).click(); }, setName: (name: string) => { this.getters.name().click(); @@ -43,12 +39,18 @@ export class CredentialsModal extends BasePage { }, save: (test = false) => { cy.intercept('POST', '/rest/credentials').as('saveCredential'); - this.getters.saveButton().click(); + this.getters.saveButton().click({ force: true }); cy.wait('@saveCredential'); if (test) cy.wait('@testCredential'); this.getters.saveButton().should('contain.text', 'Saved'); }, + saveSharing: (test = false) => { + cy.intercept('PUT', '/rest/credentials/*/share').as('shareCredential'); + this.getters.saveButton().click({ force: true }); + cy.wait('@shareCredential'); + this.getters.saveButton().should('contain.text', 'Saved'); + }, close: () => { this.getters.closeButton().click(); }, diff --git a/cypress/pages/modals/index.ts b/cypress/pages/modals/index.ts index 358ba0cf7800d..3d1981d027ad2 100644 --- a/cypress/pages/modals/index.ts +++ b/cypress/pages/modals/index.ts @@ -1,5 +1,3 @@ export * from './credentials-modal'; export * from './message-box'; export * from './workflow-sharing-modal'; -export * from './user-activation-survey-modal'; - diff --git a/cypress/pages/modals/message-box.ts b/cypress/pages/modals/message-box.ts index cfa38368b8d67..b54e375ef6c2a 100644 --- a/cypress/pages/modals/message-box.ts +++ b/cypress/pages/modals/message-box.ts @@ -5,15 +5,15 @@ export class MessageBox extends BasePage { modal: () => cy.get('.el-message-box', { withinSubject: null }), header: () => this.getters.modal().find('.el-message-box__title'), content: () => this.getters.modal().find('.el-message-box__content'), - confirm: () => this.getters.modal().find('.btn--confirm'), - cancel: () => this.getters.modal().find('.btn--cancel'), + confirm: () => this.getters.modal().find('.btn--confirm').first(), + cancel: () => this.getters.modal().find('.btn--cancel').first(), }; actions = { confirm: () => { - this.getters.confirm().click(); + this.getters.confirm().click({ force: true }); }, cancel: () => { - this.getters.cancel().click(); + this.getters.cancel().click({ force: true }); }, }; } diff --git a/cypress/pages/modals/mfa-setup-modal.ts b/cypress/pages/modals/mfa-setup-modal.ts new file mode 100644 index 0000000000000..d127731be278d --- /dev/null +++ b/cypress/pages/modals/mfa-setup-modal.ts @@ -0,0 +1,11 @@ +import { BasePage } from './../base'; + +export class MfaSetupModal extends BasePage { + getters = { + modalContainer: () => cy.getByTestId('changePassword-modal').last(), + tokenInput: () => cy.getByTestId('mfa-token-input'), + copySecretToClipboardButton: () => cy.getByTestId('mfa-secret-button'), + downloadRecoveryCodesButton: () => cy.getByTestId('mfa-recovery-codes-button'), + saveButton: () => cy.getByTestId('mfa-save-button'), + }; +} diff --git a/cypress/pages/modals/user-activation-survey-modal.ts b/cypress/pages/modals/user-activation-survey-modal.ts deleted file mode 100644 index d47f987887aeb..0000000000000 --- a/cypress/pages/modals/user-activation-survey-modal.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BasePage } from './../base'; - -export class UserActivationSurveyModal extends BasePage { - getters = { - modalContainer: () => cy.getByTestId('userActivationSurvey-modal').last(), - feedbackInput: () => cy.getByTestId('activation-feedback-input').find('textarea'), - sendFeedbackButton: () => cy.getByTestId('send-activation-feedback-button'), - }; -} diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 15f177240f793..f9cb5b130445b 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -1,4 +1,5 @@ import { BasePage } from './base'; +import { getVisiblePopper, getVisibleSelect } from '../utils'; export class NDV extends BasePage { getters = { @@ -7,22 +8,28 @@ export class NDV extends BasePage { copyInput: () => cy.getByTestId('copy-input'), credentialInput: (eq = 0) => cy.getByTestId('node-credentials-select').eq(eq), nodeExecuteButton: () => cy.getByTestId('node-execute-button'), + triggerPanelExecuteButton: () => cy.getByTestId('trigger-execute-button'), inputSelect: () => cy.getByTestId('ndv-input-select'), inputOption: () => cy.getByTestId('ndv-input-option'), inputPanel: () => cy.getByTestId('ndv-input-panel'), outputPanel: () => cy.getByTestId('output-panel'), executingLoader: () => cy.getByTestId('ndv-executing'), inputDataContainer: () => this.getters.inputPanel().findChildByTestId('ndv-data-container'), - inputDisplayMode: () => this.getters.inputPanel().findChildByTestId('ndv-run-data-display-mode').first(), + inputDisplayMode: () => + this.getters.inputPanel().findChildByTestId('ndv-run-data-display-mode').first(), outputDataContainer: () => this.getters.outputPanel().findChildByTestId('ndv-data-container'), - outputDisplayMode: () => this.getters.outputPanel().findChildByTestId('ndv-run-data-display-mode').first(), + outputDisplayMode: () => + this.getters.outputPanel().findChildByTestId('ndv-run-data-display-mode').first(), pinDataButton: () => cy.getByTestId('ndv-pin-data'), editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'), - pinnedDataEditor: () => this.getters.outputPanel().find('.monaco-editor[role=code]'), + pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller .cm-content'), runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), - savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'), + nodeOutputHint: () => cy.getByTestId('ndv-output-run-node-hint'), + savePinnedDataButton: () => + this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'), outputTableRows: () => this.getters.outputDataContainer().find('table tr'), outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'), + outputTableHeaderByText: (text: string) => this.getters.outputTableHeaders().contains(text), outputTableRow: (row: number) => this.getters.outputTableRows().eq(row), outputTbodyCell: (row: number, col: number) => this.getters.outputTableRow(row).find('td').eq(col), @@ -32,14 +39,18 @@ export class NDV extends BasePage { inputTbodyCell: (row: number, col: number) => this.getters.inputTableRow(row).find('td').eq(col), inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'), + inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'), nodeParameters: () => cy.getByTestId('node-parameters'), parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`), + parameterInputIssues: (parameterName: string) => + cy + .getByTestId(`parameter-input-${parameterName}`) + .should('have.length', 1) + .findChildByTestId('parameter-issues'), parameterExpressionPreview: (parameterName: string) => this.getters .nodeParameters() - .find( - `[data-test-id="parameter-input-${parameterName}"] + [data-test-id="parameter-expression-preview"]`, - ), + .find(`[data-test-id="parameter-expression-preview-${parameterName}"]`), nodeNameContainer: () => cy.getByTestId('node-title-container'), nodeRenameInput: () => cy.getByTestId('node-rename-input'), executePrevious: () => cy.getByTestId('execute-previous-node'), @@ -51,11 +62,67 @@ export class NDV extends BasePage { inputHoveringItem: () => this.getters.inputPanel().findChildByTestId('hovering-item'), outputBranches: () => this.getters.outputPanel().findChildByTestId('branches'), inputBranches: () => this.getters.inputPanel().findChildByTestId('branches'), + resourceLocator: (paramName: string) => cy.getByTestId(`resource-locator-${paramName}`), + resourceLocatorInput: (paramName: string) => + this.getters.resourceLocator(paramName).find('[data-test-id="rlc-input-container"]'), + resourceLocatorDropdown: (paramName: string) => + this.getters.resourceLocator(paramName).find('[data-test-id="resource-locator-dropdown"]'), + resourceLocatorErrorMessage: () => cy.getByTestId('rlc-error-container'), + resourceLocatorModeSelector: (paramName: string) => + this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'), + resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'), + resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'), + resourceMapperRemoveFieldButton: (fieldName: string) => + cy.getByTestId(`remove-field-button-${fieldName}`), + resourceMapperColumnsOptionsButton: () => + cy.getByTestId('columns-parameter-input-options-container'), + resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'), + sqlEditorContainer: () => cy.getByTestId('sql-editor-container'), + filterComponent: (paramName: string) => cy.getByTestId(`filter-${paramName}`), + filterCombinator: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-combinator-select').eq(index), + filterConditions: (paramName: string) => + this.getters.filterComponent(paramName).getByTestId('filter-condition'), + filterCondition: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-condition').eq(index), + filterConditionLeft: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-condition-left').eq(index), + filterConditionRight: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-condition-right').eq(index), + filterConditionOperator: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-operator-select').eq(index), + filterConditionRemove: (paramName: string, index = 0) => + this.getters.filterComponent(paramName).getByTestId('filter-remove-condition').eq(index), + filterConditionAdd: (paramName: string) => + this.getters.filterComponent(paramName).getByTestId('filter-add-condition'), + assignmentCollection: (paramName: string) => + cy.getByTestId(`assignment-collection-${paramName}`), + assignmentCollectionAdd: (paramName: string) => + this.getters.assignmentCollection(paramName).getByTestId('assignment-collection-drop-area'), + assignment: (paramName: string, index = 0) => + this.getters.assignmentCollection(paramName).getByTestId('assignment').eq(index), + assignmentRemove: (paramName: string, index = 0) => + this.getters.assignment(paramName, index).getByTestId('assignment-remove'), + assignmentName: (paramName: string, index = 0) => + this.getters.assignment(paramName, index).getByTestId('assignment-name'), + assignmentValue: (paramName: string, index = 0) => + this.getters.assignment(paramName, index).getByTestId('assignment-value'), + assignmentType: (paramName: string, index = 0) => + this.getters.assignment(paramName, index).getByTestId('assignment-type-select'), + searchInput: () => cy.getByTestId('ndv-search'), + pagination: () => cy.getByTestId('ndv-data-pagination'), + nodeVersion: () => cy.getByTestId('node-version'), + nodeSettingsTab: () => cy.getByTestId('tab-settings'), + codeEditorFullscreenButton: () => cy.getByTestId('code-editor-fullscreen-button'), + codeEditorDialog: () => cy.getByTestId('code-editor-fullscreen'), + codeEditorFullscreen: () => this.getters.codeEditorDialog().find('.cm-content'), + nodeRunSuccessIndicator: () => cy.getByTestId('node-run-info-success'), + nodeRunErrorIndicator: () => cy.getByTestId('node-run-info-danger'), }; actions = { pinData: () => { - this.getters.pinDataButton().click(); + this.getters.pinDataButton().click({ force: true }); }, editPinnedData: () => { this.getters.editPinnedDataButton().click(); @@ -77,22 +144,40 @@ export class NDV extends BasePage { this.getters.editPinnedDataButton().click(); this.getters.pinnedDataEditor().click(); - this.getters.pinnedDataEditor().type(`{selectall}{backspace}`); - this.getters.pinnedDataEditor().type(JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')); + this.getters + .pinnedDataEditor() + .type( + `{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`, + { + delay: 0, + }, + ); + + this.actions.savePinnedData(); + }, + pastePinnedData: (data: object) => { + this.getters.editPinnedDataButton().click(); + + this.getters.pinnedDataEditor().click(); + this.getters + .pinnedDataEditor() + .type('{selectall}{backspace}', { delay: 0 }) + .paste(JSON.stringify(data)); this.actions.savePinnedData(); }, clearParameterInput: (parameterName: string) => { this.getters.parameterInput(parameterName).type(`{selectall}{backspace}`); }, - typeIntoParameterInput: (parameterName: string, content: string) => { - this.getters.parameterInput(parameterName).type(content); + typeIntoParameterInput: ( + parameterName: string, + content: string, + opts?: { parseSpecialCharSequences: boolean; delay?: number }, + ) => { + this.getters.parameterInput(parameterName).type(content, opts); }, selectOptionInParameterDropdown: (parameterName: string, content: string) => { - this.getters.parameterInput(parameterName).find('.option-headline').contains(content).click(); - }, - dismissMappingTooltip: () => { - cy.getByTestId('dismiss-mapping-tooltip').click(); + getVisibleSelect().find('.option-headline').contains(content).click(); }, rename: (newName: string) => { this.getters.nodeNameContainer().click(); @@ -100,7 +185,7 @@ export class NDV extends BasePage { cy.get('body').type('{enter}'); }, executePrevious: () => { - this.getters.executePrevious().click(); + this.getters.executePrevious().click({ force: true }); }, mapDataFromHeader: (col: number, parameterName: string) => { const draggable = `[data-test-id="ndv-input-panel"] [data-test-id="ndv-data-container"] table th:nth-child(${col})`; @@ -127,15 +212,11 @@ export class NDV extends BasePage { }, changeInputRunSelector: (runName: string) => { this.getters.inputRunSelector().click(); - cy.get('.el-select-dropdown:visible .el-select-dropdown__item') - .contains(runName) - .click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); }, changeOutputRunSelector: (runName: string) => { this.getters.outputRunSelector().click(); - cy.get('.el-select-dropdown:visible .el-select-dropdown__item') - .contains(runName) - .click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); }, toggleOutputRunLinking: () => { this.getters.outputRunSelector().find('button').click(); @@ -146,8 +227,77 @@ 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) => { + this.getters.resourceLocatorModeSelector(paramName).click(); + getVisibleSelect().find('li').last().click(); + this.getters.resourceLocatorInput(paramName).type(value); + }, + validateExpressionPreview: (paramName: string, value: string) => { + this.getters + .parameterExpressionPreview(paramName) + .find('span') + .should('include.html', asEncodedHTML(value)); + }, + refreshResourceMapperColumns: () => { + this.getters.resourceMapperSelectColumn().realHover(); + this.getters + .resourceMapperSelectColumn() + .findChildByTestId('action-toggle') + .should('have.length', 1) + .click(); + + getVisiblePopper().find('li').last().click(); + }, + addFilterCondition: (paramName: string) => { + this.getters.filterConditionAdd(paramName).click({ force: true }); + }, + removeFilterCondition: (paramName: string, index: number) => { + this.getters.filterConditionRemove(paramName, index).click(); + }, + removeAssignment: (paramName: string, index: number) => { + this.getters.assignmentRemove(paramName, index).click(); + }, + setInvalidExpression: ({ + fieldName, + invalidExpression, + delay, + }: { + fieldName: string; + invalidExpression?: string; + delay?: number; + }) => { + this.actions.typeIntoParameterInput(fieldName, '='); + this.actions.typeIntoParameterInput(fieldName, invalidExpression ?? "{{ $('unknown')", { + parseSpecialCharSequences: false, + delay, + }); + this.actions.validateExpressionPreview(fieldName, `node doesn't exist`); + }, + openSettings: () => { + this.getters.nodeSettingsTab().click(); + }, + + openCodeEditorFullscreen: () => { + this.getters.codeEditorFullscreenButton().click({ force: true }); + }, + changeNodeOperation: (operation: string) => { + this.getters.parameterInput('operation').click(); + cy.get('.el-select-dropdown__item') + .contains(new RegExp(`^${operation}$`)) + .click({ force: true }); + this.getters.parameterInput('operation').find('input').should('have.value', operation); + }, }; } + +function asEncodedHTML(str: string): string { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/ /g, ' '); +} diff --git a/cypress/pages/settings-log-streaming.ts b/cypress/pages/settings-log-streaming.ts index b95fcb38bfa63..2d056a4444c04 100644 --- a/cypress/pages/settings-log-streaming.ts +++ b/cypress/pages/settings-log-streaming.ts @@ -1,4 +1,5 @@ import { BasePage } from './base'; +import { getVisibleSelect } from '../utils'; export class SettingsLogStreamingPage extends BasePage { url = '/settings/log-streaming'; @@ -6,11 +7,9 @@ export class SettingsLogStreamingPage extends BasePage { getActionBoxUnlicensed: () => cy.getByTestId('action-box-unlicensed'), getActionBoxLicensed: () => cy.getByTestId('action-box-licensed'), getDestinationModal: () => cy.getByTestId('destination-modal'), - getDestinationModalDialog: () => this.getters.getDestinationModal().find('.el-dialog'), getSelectDestinationType: () => cy.getByTestId('select-destination-type'), getDestinationNameInput: () => cy.getByTestId('subtitle-showing-type'), - getSelectDestinationTypeItems: () => - this.getters.getSelectDestinationType().find('.el-select-dropdown__item'), + getSelectDestinationTypeItems: () => getVisibleSelect().find('.el-select-dropdown__item'), getSelectDestinationButton: () => cy.getByTestId('select-destination-button'), getContactUsButton: () => this.getters.getActionBoxUnlicensed().find('button'), getAddFirstDestinationButton: () => this.getters.getActionBoxLicensed().find('button'), diff --git a/cypress/pages/settings-personal.ts b/cypress/pages/settings-personal.ts index a454837011a19..716625beb5ab6 100644 --- a/cypress/pages/settings-personal.ts +++ b/cypress/pages/settings-personal.ts @@ -1,20 +1,34 @@ import { ChangePasswordModal } from './modals/change-password-modal'; +import { MfaSetupModal } from './modals/mfa-setup-modal'; import { BasePage } from './base'; +import generateOTPToken from 'cypress-otp'; const changePasswordModal = new ChangePasswordModal(); +const mfaSetupModal = new MfaSetupModal(); export class PersonalSettingsPage extends BasePage { url = '/settings/personal'; + secret = ''; + getters = { currentUserName: () => cy.getByTestId('current-user-name'), firstNameInput: () => cy.getByTestId('firstName').find('input').first(), lastNameInput: () => cy.getByTestId('lastName').find('input').first(), emailInputContainer: () => cy.getByTestId('email'), emailInput: () => cy.getByTestId('email').find('input').first(), - changePasswordLink: () => cy.getByTestId('change-password-link').find('a').first(), + changePasswordLink: () => cy.getByTestId('change-password-link').first(), saveSettingsButton: () => cy.getByTestId('save-settings-button'), + enableMfaButton: () => cy.getByTestId('enable-mfa-button'), + disableMfaButton: () => cy.getByTestId('disable-mfa-button'), + themeSelector: () => cy.getByTestId('theme-select'), + selectOptionsVisible: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'), }; actions = { + changeTheme: (theme: 'System default' | 'Dark' | 'Light') => { + this.getters.themeSelector().click(); + this.getters.selectOptionsVisible().should('have.length', 3); + this.getters.selectOptionsVisible().contains(theme).click(); + }, loginAndVisit: (email: string, password: string) => { cy.signin({ email, password }); cy.visit(this.url); @@ -25,7 +39,6 @@ export class PersonalSettingsPage extends BasePage { this.getters.saveSettingsButton().realClick(); }, updatePassword: (oldPassword: string, newPassword: string) => { - this.getters.changePasswordLink().realClick(); changePasswordModal.getters.modalContainer().should('be.visible'); changePasswordModal.getters.currentPasswordInput().type('{selectall}').type(oldPassword); changePasswordModal.getters.newPasswordInput().type('{selectall}').type(newPassword); @@ -34,7 +47,10 @@ export class PersonalSettingsPage extends BasePage { }, tryToSetWeakPassword: (oldPassword: string, newPassword: string) => { this.actions.updatePassword(oldPassword, newPassword); - changePasswordModal.getters.newPasswordInputContainer().find('div[class^="_errorInput"]').should('exist'); + changePasswordModal.getters + .newPasswordInputContainer() + .find('div[class^="_errorInput"]') + .should('exist'); }, updateEmail: (newEmail: string) => { this.getters.emailInput().type('{selectall}').type(newEmail).type('{enter}'); @@ -48,5 +64,24 @@ export class PersonalSettingsPage extends BasePage { this.actions.loginAndVisit(email, password); cy.url().should('match', new RegExp(this.url)); }, + enableMfa: () => { + cy.visit(this.url); + this.getters.enableMfaButton().click(); + cy.wait('@getMfaQrCode'); + mfaSetupModal.getters.copySecretToClipboardButton().should('be.visible'); + mfaSetupModal.getters.copySecretToClipboardButton().realClick(); + cy.readClipboard().then((secret) => { + const token = generateOTPToken(secret); + + mfaSetupModal.getters.tokenInput().type(token); + mfaSetupModal.getters.downloadRecoveryCodesButton().should('be.visible'); + mfaSetupModal.getters.downloadRecoveryCodesButton().click(); + mfaSetupModal.getters.saveButton().click(); + }); + }, + disableMfa: () => { + cy.visit(this.url); + this.getters.disableMfaButton().click(); + }, }; } diff --git a/cypress/pages/settings-public-api.ts b/cypress/pages/settings-public-api.ts new file mode 100644 index 0000000000000..1a7d668136775 --- /dev/null +++ b/cypress/pages/settings-public-api.ts @@ -0,0 +1,5 @@ +export const getPublicApiUpgradeCTA = () => cy.getByTestId('public-api-upgrade-cta'); + +export const visitPublicApiPage = () => { + cy.visit('/settings/api'); +}; diff --git a/cypress/pages/settings-users.ts b/cypress/pages/settings-users.ts index 41ae18711468f..e3c80e5bcc9c6 100644 --- a/cypress/pages/settings-users.ts +++ b/cypress/pages/settings-users.ts @@ -4,8 +4,8 @@ import { WorkflowPage } from './workflow'; import { WorkflowsPage } from './workflows'; import { BasePage } from './base'; -const workflowPage = new WorkflowPage(); -const workflowsPage = new WorkflowsPage(); +const workflowPage = new WorkflowPage(); +const workflowsPage = new WorkflowsPage(); const mainSidebar = new MainSidebar(); const settingsSidebar = new SettingsSidebar(); @@ -18,11 +18,17 @@ export class SettingsUsersPage extends BasePage { inviteUsersModalEmailsInput: () => cy.getByTestId('emails').find('input').first(), userListItems: () => cy.get('[data-test-id^="user-list-item"]'), userItem: (email: string) => cy.getByTestId(`user-list-item-${email.toLowerCase()}`), - userActionsToggle: (email: string) => this.getters.userItem(email).find('[data-test-id="action-toggle"]'), - deleteUserAction: () => cy.getByTestId('action-toggle-dropdown').find('li:contains("Delete"):visible'), + userActionsToggle: (email: string) => + this.getters.userItem(email).find('[data-test-id="action-toggle"]'), + userRoleSelect: (email: string) => + this.getters.userItem(email).find('[data-test-id="user-role-select"]'), + deleteUserAction: () => + cy.getByTestId('action-toggle-dropdown').find('li:contains("Delete"):visible'), confirmDeleteModal: () => cy.getByTestId('deleteUser-modal').last(), - transferDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').first(), - deleteDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').last(), + transferDataRadioButton: () => + this.getters.confirmDeleteModal().find('.el-radio .el-radio__input').first(), + deleteDataRadioButton: () => + this.getters.confirmDeleteModal().find('.el-radio .el-radio__input').last(), userSelectDropDown: () => this.getters.confirmDeleteModal().find('.n8n-select'), userSelectOptions: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'), deleteUserButton: () => this.getters.confirmDeleteModal().find('button:contains("Delete")'), diff --git a/cypress/pages/settings.ts b/cypress/pages/settings.ts new file mode 100644 index 0000000000000..264b525dee717 --- /dev/null +++ b/cypress/pages/settings.ts @@ -0,0 +1,9 @@ +import { BasePage } from './base'; + +export class SettingsPage extends BasePage { + url = '/settings'; + getters = { + menuItems: () => cy.getByTestId('menu-item'), + }; + actions = {}; +} diff --git a/cypress/pages/sidebar/main-sidebar.ts b/cypress/pages/sidebar/main-sidebar.ts index b86ed326f19bc..5379b1f889285 100644 --- a/cypress/pages/sidebar/main-sidebar.ts +++ b/cypress/pages/sidebar/main-sidebar.ts @@ -1,4 +1,7 @@ import { BasePage } from '../base'; +import { WorkflowsPage } from '../workflows'; + +const workflowsPage = new WorkflowsPage(); export class MainSidebar extends BasePage { getters = { @@ -9,7 +12,9 @@ export class MainSidebar extends BasePage { workflows: () => this.getters.menuItem('Workflows'), credentials: () => this.getters.menuItem('Credentials'), executions: () => this.getters.menuItem('Executions'), - userMenu: () => cy.getByTestId('main-sidebar-user-menu'), + adminPanel: () => this.getters.menuItem('Admin Panel'), + userMenu: () => cy.get('div[class="action-dropdown-container"]'), + logo: () => cy.getByTestId('n8n-logo'), }; actions = { goToSettings: () => { @@ -24,11 +29,17 @@ export class MainSidebar extends BasePage { this.getters.credentials().click(); }, openUserMenu: () => { - this.getters.userMenu().find('[role="button"]').last().click(); + this.getters.userMenu().click(); + }, + openUserMenu: () => { + this.getters.userMenu().click(); }, signout: () => { + const workflowsPage = new WorkflowsPage(); + cy.visit(workflowsPage.url); this.actions.openUserMenu(); - cy.getByTestId('workflow-menu-item-logout').click(); + cy.getByTestId('user-menu-item-logout').click(); + cy.wrap(Cypress.session.clearAllSavedSessions()); }, }; } diff --git a/cypress/pages/signin.ts b/cypress/pages/signin.ts index b54a30173f6a5..1b2b35c22fbb4 100644 --- a/cypress/pages/signin.ts +++ b/cypress/pages/signin.ts @@ -1,4 +1,6 @@ +import { N8N_AUTH_COOKIE } from '../constants'; import { BasePage } from './base'; +import { WorkflowsPage } from './workflows'; export class SigninPage extends BasePage { url = '/signin'; @@ -8,4 +10,32 @@ export class SigninPage extends BasePage { password: () => cy.getByTestId('password'), submit: () => cy.get('button'), }; + + actions = { + loginWithEmailAndPassword: (email: string, password: string) => { + const signinPage = new SigninPage(); + const workflowsPage = new WorkflowsPage(); + + cy.session( + [email, password], + () => { + cy.visit(signinPage.url); + + this.getters.form().within(() => { + this.getters.email().type(email); + this.getters.password().type(password); + this.getters.submit().click(); + }); + + // we should be redirected to /workflows + cy.url().should('include', workflowsPage.url); + }, + { + validate() { + cy.getCookie(N8N_AUTH_COOKIE).should('exist'); + }, + }, + ); + }, + }; } diff --git a/cypress/pages/signup.ts b/cypress/pages/signup.ts deleted file mode 100644 index f647720ce4ef9..0000000000000 --- a/cypress/pages/signup.ts +++ /dev/null @@ -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'), - }; -} diff --git a/cypress/pages/template-collection.ts b/cypress/pages/template-collection.ts new file mode 100644 index 0000000000000..9f3457eae97d2 --- /dev/null +++ b/cypress/pages/template-collection.ts @@ -0,0 +1,33 @@ +export function visitTemplateCollectionPage(withFixture: Fixture) { + cy.intercept( + 'GET', + `https://api.n8n.io/api/templates/collections/${testData.ecommerceStarterPack.id}`, + { + fixture: withFixture.fixture, + }, + ).as('getTemplateCollection'); + + cy.visit(`/collections/${withFixture.id}`); + + cy.wait('@getTemplateCollection'); +} + +export function clickUseWorkflowButtonByTitle(workflowTitle: string) { + cy.getByTestId('template-card') + .contains('[data-test-id=template-card]', workflowTitle) + .realHover({ position: 'center' }) + .findChildByTestId('use-workflow-button') + .click({ force: true }); +} + +export type Fixture = { + id: number; + fixture: string; +}; + +export const testData = { + ecommerceStarterPack: { + id: 1, + fixture: 'Ecommerce_starter_pack_template_collection.json', + }, +}; diff --git a/cypress/pages/template-credential-setup.ts b/cypress/pages/template-credential-setup.ts new file mode 100644 index 0000000000000..d673261fdf77b --- /dev/null +++ b/cypress/pages/template-credential-setup.ts @@ -0,0 +1,70 @@ +import { CredentialsModal, MessageBox } from './modals'; +import * as formStep from '../composables/setup-template-form-step'; +import { overrideFeatureFlag } from '../composables/featureFlags'; + +export type TemplateTestData = { + id: number; + fixture: string; +}; + +export const testData = { + simpleTemplate: { + id: 1205, + fixture: 'Test_Template_1.json', + }, + templateWithoutCredentials: { + id: 1344, + fixture: 'Test_Template_2.json', + }, +}; + +const credentialsModal = new CredentialsModal(); +const messageBox = new MessageBox(); + +export const getters = { + continueButton: () => cy.getByTestId('continue-button'), + skipLink: () => cy.get('a:contains("Skip")'), + title: (title: string) => cy.get(`h1:contains(${title})`), + infoCallout: () => cy.getByTestId('info-callout'), +}; + +export const enableTemplateCredentialSetupFeatureFlag = () => { + overrideFeatureFlag('017_template_credential_setup_v2', true); +}; + +export const visitTemplateCredentialSetupPage = (templateId: number) => { + cy.visit(`templates/${templateId}/setup`); + enableTemplateCredentialSetupFeatureFlag(); + + formStep.getFormStep().eq(0).should('be.visible'); +}; + +/** + * Fills in dummy credentials for the given app name. + */ +export const fillInDummyCredentialsForApp = (appName: string) => { + formStep.getCreateAppCredentialsButton(appName).click(); + credentialsModal.getters.editCredentialModal().find('input:first()').type('test'); + credentialsModal.actions.save(false); + credentialsModal.actions.close(); +}; + +/** + * Fills in dummy credentials for the given app name. Assumes + * that a confirmation message box will be shown, which will be + * handled. + */ +export const fillInDummyCredentialsForAppWithConfirm = (appName: string) => { + fillInDummyCredentialsForApp(appName); + messageBox.actions.cancel(); +}; + +/** + * Finishes the credential setup by clicking the continue button. + */ +export const finishCredentialSetup = () => { + cy.intercept('POST', '/rest/workflows').as('createWorkflow'); + getters.continueButton().should('be.enabled'); + getters.continueButton().click(); + cy.wait('@createWorkflow'); +}; diff --git a/cypress/pages/template-workflow.ts b/cypress/pages/template-workflow.ts new file mode 100644 index 0000000000000..d1e8630a12428 --- /dev/null +++ b/cypress/pages/template-workflow.ts @@ -0,0 +1,38 @@ +import { BasePage } from './base'; + +export class TemplateWorkflowPage extends BasePage { + url = '/templates'; + + getters = { + useTemplateButton: () => cy.get('[data-test-id="use-template-button"]'), + description: () => cy.get('[data-test-id="template-description"]'), + }; + + actions = { + visit: (templateId: number) => { + cy.visit(`${this.url}/${templateId}`); + }, + + clickUseThisWorkflowButton: () => { + this.getters.useTemplateButton().click(); + }, + + openTemplate: (template: { + workflow: { + id: number; + name: string; + description: string; + user: { username: string }; + image: { id: number; url: string }[]; + }; + }, templateHost: string) => { + cy.intercept('GET', `${templateHost}/api/templates/workflows/${template.workflow.id}`, { + statusCode: 200, + body: template, + }).as('getTemplate'); + + this.actions.visit(template.workflow.id); + cy.wait('@getTemplate'); + }, + }; +} diff --git a/cypress/pages/templates.ts b/cypress/pages/templates.ts new file mode 100644 index 0000000000000..4c0225be48bbd --- /dev/null +++ b/cypress/pages/templates.ts @@ -0,0 +1,63 @@ +import { BasePage } from './base'; + +export class TemplatesPage extends BasePage { + url = '/templates'; + + getters = { + useTemplateButton: () => cy.getByTestId('use-template-button'), + templateCards: () => cy.getByTestId('template-card'), + firstTemplateCard: () => this.getters.templateCards().first(), + allCategoriesFilter: () => cy.getByTestId('template-filter-all-categories'), + searchInput: () => cy.getByTestId('template-search-input'), + categoryFilters: () => cy.get('[data-test-id^=template-filter]'), + categoryFilter: (category: string) => cy.getByTestId(`template-filter-${category}`), + collectionCountLabel: () => cy.getByTestId('collection-count-label'), + templateCountLabel: () => cy.getByTestId('template-count-label'), + templatesLoadingContainer: () => cy.getByTestId('templates-loading-container'), + expandCategoriesButton: () => cy.getByTestId('expand-categories-button'), + }; + + actions = { + openSingleTemplateView: (templateId: number) => { + cy.visit(`${this.url}/${templateId}`); + cy.waitForLoad(); + }, + + openOnboardingFlow: (id: number, name: string, workflow: object, templatesHost: string) => { + const apiResponse = { + id, + name, + workflow, + }; + cy.intercept('POST', '/rest/workflows').as('createWorkflow'); + cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, { + statusCode: 200, + body: apiResponse, + }).as('getTemplate'); + cy.intercept('GET', 'rest/workflows/**').as('getWorkflow'); + + cy.visit(`/workflows/onboarding/${id}`); + + cy.wait('@getTemplate'); + cy.wait(['@createWorkflow', '@getWorkflow']); + }, + + importTemplate: (id: number, name: string, workflow: object, templatesHost: string) => { + const apiResponse = { + id, + name, + workflow, + }; + cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, { + statusCode: 200, + body: apiResponse, + }).as('getTemplate'); + cy.intercept('GET', 'rest/workflows/**').as('getWorkflow'); + + cy.visit(`/workflows/templates/${id}`); + + cy.wait('@getTemplate'); + cy.wait('@getWorkflow'); + }, + }; +} diff --git a/cypress/pages/variables.ts b/cypress/pages/variables.ts index 721d874351cfe..6091e5cf1b54a 100644 --- a/cypress/pages/variables.ts +++ b/cypress/pages/variables.ts @@ -10,7 +10,7 @@ export class VariablesPage extends BasePage { goToUpgrade: () => cy.getByTestId('go-to-upgrade'), actionBox: () => cy.getByTestId('action-box'), emptyResourcesListNewVariableButton: () => this.getters.emptyResourcesList().find('button'), - searchBar: () => cy.getByTestId('resources-list-search').find('input'), + searchBar: () => cy.getByTestId('resources-list-search'), createVariableButton: () => cy.getByTestId('resources-list-add'), variablesRows: () => cy.getByTestId('variables-row'), variablesEditableRows: () => diff --git a/cypress/pages/workerView.ts b/cypress/pages/workerView.ts new file mode 100644 index 0000000000000..e14bfd36a253b --- /dev/null +++ b/cypress/pages/workerView.ts @@ -0,0 +1,15 @@ +import { BasePage } from './base'; + +export class WorkerViewPage extends BasePage { + url = '/settings/workers'; + getters = { + workerCards: () => cy.getByTestId('worker-card'), + workerCard: (workerId: string) => this.getters.workerCards().contains(workerId), + workerViewLicensed: () => cy.getByTestId('worker-view-licensed'), + workerViewUnlicensed: () => cy.getByTestId('worker-view-unlicensed'), + menuItems: () => cy.get('.el-menu-item'), + menuItem: () => this.getters.menuItems().get('#settings-workersview'), + }; + + actions = {}; +} diff --git a/cypress/pages/workflow-executions-tab.ts b/cypress/pages/workflow-executions-tab.ts index 4e641ae69a2e7..eb855f026f50a 100644 --- a/cypress/pages/workflow-executions-tab.ts +++ b/cypress/pages/workflow-executions-tab.ts @@ -1,5 +1,5 @@ -import { BasePage } from "./base"; -import { WorkflowPage } from "./workflow"; +import { BasePage } from './base'; +import { WorkflowPage } from './workflow'; const workflowPage = new WorkflowPage(); @@ -14,10 +14,16 @@ export class WorkflowExecutionsTab extends BasePage { failedExecutionListItems: () => cy.get('[data-test-execution-status="error"]'), executionCard: (executionId: string) => cy.getByTestId(`execution-details-${executionId}`), executionPreviewDetails: () => cy.get('[data-test-id^="execution-preview-details-"]'), - executionPreviewDetailsById: (executionId: string) => cy.getByTestId(`execution-preview-details-${executionId}`), - executionPreviewTime: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-time"]'), - executionPreviewStatus: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-label"]'), - executionPreviewId: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'), + executionPreviewDeleteButton: () => cy.get('[data-test-id="execution-preview-delete-button"]'), + executionPreviewDetailsById: (executionId: string) => + cy.getByTestId(`execution-preview-details-${executionId}`), + executionPreviewTime: () => + this.getters.executionPreviewDetails().find('[data-test-id="execution-time"]'), + executionPreviewStatus: () => + this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-label"]'), + executionPreviewId: () => + this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'), + executionDebugButton: () => cy.getByTestId('execution-debug-button'), }; actions = { toggleNodeEnabled: (nodeName: string) => { @@ -25,7 +31,7 @@ export class WorkflowExecutionsTab extends BasePage { cy.get('body').type('d', { force: true }); }, createManualExecutions: (count: number) => { - for (let i=0; i { this.getters.executionsTabButton().click(); + cy.url().should('include', '/executions'); }, switchToEditorTab: () => { workflowPage.getters.editorTabButton().click(); - } + cy.url().should('match', /\/workflow\/[^\/]+$/); + }, + deleteExecutionInPreview: () => { + this.getters.executionPreviewDeleteButton().click(); + cy.get('button.btn--confirm').click(); + }, }; -}; +} diff --git a/cypress/pages/workflow-history.ts b/cypress/pages/workflow-history.ts new file mode 100644 index 0000000000000..18cd6ed999235 --- /dev/null +++ b/cypress/pages/workflow-history.ts @@ -0,0 +1,7 @@ +import { BasePage } from "./base"; + +export class WorkflowHistoryPage extends BasePage { + getters = { + workflowHistoryCloseButton: () => cy.getByTestId('workflow-history-close-button'), + } +} diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index d42b2850c4e93..a3443c81b5941 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -1,6 +1,11 @@ import { META_KEY } from '../constants'; import { BasePage } from './base'; +import { getVisibleSelect } from '../utils'; +import { NodeCreator } from './features/node-creator'; +type CyGetOptions = Parameters<(typeof cy)['get']>[1]; + +const nodeCreator = new NodeCreator(); export class WorkflowPage extends BasePage { url = '/workflow/new'; getters = { @@ -12,11 +17,11 @@ export class WorkflowPage extends BasePage { workflowTagsContainer: () => cy.getByTestId('workflow-tags-container'), workflowTagsInput: () => this.getters.workflowTagsContainer().then(($el) => cy.wrap($el.find('input').first())), - tagPills: () => cy.get('[data-test-id="workflow-tags-container"] span.tags > span'), + tagPills: () => cy.get('[data-test-id="workflow-tags-container"] span.el-tag'), nthTagPill: (n: number) => - cy.get(`[data-test-id="workflow-tags-container"] span.tags > span:nth-child(${n})`), + cy.get(`[data-test-id="workflow-tags-container"] span.el-tag:nth-child(${n})`), tagsDropdown: () => cy.getByTestId('workflow-tags-dropdown'), - tagsInDropdown: () => cy.getByTestId('workflow-tags-dropdown').find('li').filter('.tag'), + tagsInDropdown: () => getVisibleSelect().find('li').filter('.tag'), createTagButton: () => cy.getByTestId('new-tag-link'), saveButton: () => cy.getByTestId('workflow-save-button'), nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'), @@ -25,6 +30,12 @@ export class WorkflowPage extends BasePage { canvasNodes: () => cy.getByTestId('canvas-node'), canvasNodeByName: (nodeName: string) => this.getters.canvasNodes().filter(`:contains(${nodeName})`), + nodeIssuesByName: (nodeName: string) => + this.getters + .canvasNodes() + .filter(`:contains(${nodeName})`) + .should('have.length.greaterThan', 0) + .findChildByTestId('node-issues'), getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => { return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`; }, @@ -37,8 +48,11 @@ export class WorkflowPage extends BasePage { canvasNodePlusEndpointByName: (nodeName: string, index = 0) => { return cy.get(this.getters.getEndpointSelector('plus', nodeName, index)); }, - successToast: () => cy.get('.el-notification .el-icon-success').parent(), - errorToast: () => cy.get('.el-notification .el-icon-error'), + successToast: () => cy.get('.el-notification:has(.el-notification--success)'), + warningToast: () => cy.get('.el-notification:has(.el-notification--warning)'), + errorToast: (options?: CyGetOptions) => + cy.get('.el-notification:has(.el-notification--error)', options), + infoToast: () => cy.get('.el-notification:has(.el-notification--info)'), activatorSwitch: () => cy.getByTestId('workflow-activate-switch'), workflowMenu: () => cy.getByTestId('workflow-menu'), firstStepButton: () => cy.getByTestId('canvas-add-button'), @@ -61,6 +75,7 @@ export class WorkflowPage extends BasePage { workflowMenuItemImportFromFile: () => cy.getByTestId('workflow-menu-item-import-from-file'), workflowMenuItemSettings: () => cy.getByTestId('workflow-menu-item-settings'), workflowMenuItemDelete: () => cy.getByTestId('workflow-menu-item-delete'), + workflowMenuItemGitPush: () => cy.getByTestId('workflow-menu-item-push'), // Workflow settings dialog elements workflowSettingsModal: () => cy.getByTestId('workflow-settings-dialog'), workflowSettingsErrorWorkflowSelect: () => cy.getByTestId('workflow-settings-error-workflow'), @@ -84,7 +99,8 @@ export class WorkflowPage extends BasePage { duplicateWorkflowModal: () => cy.getByTestId('duplicate-modal'), nodeViewBackground: () => cy.getByTestId('node-view-background'), nodeView: () => cy.getByTestId('node-view'), - inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input').find('[role=textbox]'), + inlineExpressionEditorInput: () => + cy.getByTestId('inline-expression-editor-input').find('[role=textbox]'), inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'), zoomInButton: () => cy.getByTestId('zoom-in-button'), zoomOutButton: () => cy.getByTestId('zoom-out-button'), @@ -92,10 +108,15 @@ export class WorkflowPage extends BasePage { executeWorkflowButton: () => cy.getByTestId('execute-workflow-button'), clearExecutionDataButton: () => cy.getByTestId('clear-execution-data-button'), stopExecutionButton: () => cy.getByTestId('stop-execution-button'), - stopExecutionWaitingForWebhookButton: () => cy.getByTestId('stop-execution-waiting-for-webhook-button'), + stopExecutionWaitingForWebhookButton: () => + cy.getByTestId('stop-execution-waiting-for-webhook-button'), nodeCredentialsSelect: () => cy.getByTestId('node-credentials-select'), + nodeCredentialsCreateOption: () => cy.getByTestId('node-credentials-select-item-new'), nodeCredentialsEditButton: () => cy.getByTestId('credential-edit-button'), nodeCreatorItems: () => cy.getByTestId('item-iterator-item'), + nodeCreatorNodeItems: () => cy.getByTestId('node-creator-node-item'), + nodeCreatorActionItems: () => cy.getByTestId('node-creator-action-item'), + nodeCreatorCategoryItems: () => cy.getByTestId('node-creator-category-item'), ndvParameters: () => cy.getByTestId('parameter-item'), nodeCredentialsLabel: () => cy.getByTestId('credentials-label'), getConnectionBetweenNodes: (sourceNodeName: string, targetNodeName: string) => @@ -109,18 +130,41 @@ export class WorkflowPage extends BasePage { addStickyButton: () => cy.getByTestId('add-sticky-button'), stickies: () => cy.getByTestId('sticky'), editorTabButton: () => cy.getByTestId('radio-button-workflow'), + workflowHistoryButton: () => cy.getByTestId('workflow-history-button'), + colors: () => cy.getByTestId('color'), + contextMenuAction: (action: string) => cy.getByTestId(`context-menu-item-${action}`), }; actions = { - visit: () => { + visit: (preventNodeViewUnload = true) => { cy.visit(this.url); cy.waitForLoad(); + cy.window().then((win) => { + // @ts-ignore + win.preventNodeViewBeforeUnload = preventNodeViewUnload; + }); }, - addInitialNodeToCanvas: (nodeDisplayName: string, { keepNdvOpen } = { keepNdvOpen: false }) => { + addInitialNodeToCanvas: ( + nodeDisplayName: string, + opts?: { keepNdvOpen?: boolean; action?: string, isTrigger?: boolean}, + ) => { this.getters.canvasPlusButton().click(); this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); - if (keepNdvOpen) return; - cy.get('body').type('{esc}'); + if (opts?.action) { + const itemId = opts.isTrigger ? 'Triggers' : 'Actions'; + // Expand actions category if it's collapsed + nodeCreator.getters + .getCategoryItem(itemId) + .parent() + .then(($el) => { + if ($el.attr('data-category-collapsed') === 'true') { + nodeCreator.getters.getCategoryItem(itemId).click(); + } + }); + nodeCreator.getters.getCreatorItem(opts.action).click(); + } else if (!opts?.keepNdvOpen) { + cy.get('body').type('{esc}'); + } }, addNodeToCanvas: ( nodeDisplayName: string, @@ -134,29 +178,94 @@ export class WorkflowPage extends BasePage { this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); - cy.wait(500) cy.get('body').then((body) => { - if(body.find('[data-test-id=node-creator]').length > 0) { - if(action) { - cy.contains(action).click() + if (body.find('[data-test-id=node-creator]').length > 0) { + if (action) { + cy.contains(action).click(); } else { - cy.getByTestId('item-iterator-item').eq(1).click() + // Select the first action + if (body.find('[data-keyboard-nav-type="action"]').length > 0) { + cy.get('[data-keyboard-nav-type="action"]').eq(0).click(); + } } } - }) + }); if (!preventNdvClose) cy.get('body').type('{esc}'); }, + openContextMenu: ( + nodeTypeName?: string, + method: 'right-click' | 'overflow-button' = 'right-click', + ) => { + const target = nodeTypeName + ? this.getters.canvasNodeByName(nodeTypeName) + : this.getters.nodeViewBackground(); + + if (method === 'right-click') { + target.rightclick(nodeTypeName ? 'center' : 'topLeft', { force: true }); + } else { + target.realHover(); + target.find('[data-test-id="overflow-node-button"]').click({ force: true }); + } + }, openNode: (nodeTypeName: string) => { this.getters.canvasNodeByName(nodeTypeName).first().dblclick(); }, + duplicateNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('duplicate'); + }, + deleteNodeFromContextMenu: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('delete'); + }, + executeNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('execute'); + }, + addStickyFromContextMenu: () => { + this.actions.openContextMenu(); + this.actions.contextMenuAction('add_sticky'); + }, + renameNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('rename'); + }, + copyNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('copy'); + }, + contextMenuAction: (action: string) => { + this.getters.contextMenuAction(action).click(); + }, + disableNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('toggle_activation'); + }, + pinNode: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName); + this.actions.contextMenuAction('toggle_pin'); + }, + openNodeFromContextMenu: (nodeTypeName: string) => { + this.actions.openContextMenu(nodeTypeName, 'overflow-button'); + this.actions.contextMenuAction('open'); + }, + selectAllFromContextMenu: () => { + this.actions.openContextMenu(); + this.actions.contextMenuAction('select_all'); + }, + deselectAll: () => { + this.actions.openContextMenu(); + this.actions.contextMenuAction('deselect_all'); + }, openExpressionEditorModal: () => { cy.contains('Expression').invoke('show').click(); cy.getByTestId('expander').invoke('show').click(); }, openTagManagerModal: () => { this.getters.createTagButton().click(); - this.getters.tagsDropdown().find('li.manage-tags').first().click(); + this.getters.tagsDropdown().click(); + getVisibleSelect().find('li.manage-tags').first().click(); }, openInlineExpressionEditor: () => { cy.contains('Expression').invoke('show').click(); @@ -173,10 +282,11 @@ export class WorkflowPage extends BasePage { this.getters.saveButton().should('contain', 'Save'); this.getters.saveButton().click(); this.getters.saveButton().should('contain', 'Saved'); + cy.url().should('not.have.string', '/new'); }, 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(); @@ -208,7 +318,7 @@ export class WorkflowPage extends BasePage { this.getters.workflowTagsInput().type(tag); this.getters.workflowTagsInput().type('{enter}'); }); - cy.get('body').type('{enter}'); + cy.realPress('Tab'); // For a brief moment the Element UI tag component shows the tags as(+X) string // so we need to wait for it to disappear this.getters.workflowTagsContainer().should('not.contain', `+${tags.length}`); @@ -216,6 +326,18 @@ export class WorkflowPage extends BasePage { zoomToFit: () => { cy.getByTestId('zoom-to-fit').click(); }, + pinchToZoom: (steps: number, mode: 'zoomIn' | 'zoomOut' = 'zoomIn') => { + // Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling) + this.getters.nodeViewBackground().trigger('wheel', { + force: true, + bubbles: true, + ctrlKey: true, + pageX: cy.window().innerWidth / 2, + pageY: cy.window().innerHeight / 2, + deltaMode: 1, + deltaY: mode === 'zoomOut' ? steps : -steps, + }); + }, hitUndo: () => { cy.get('body').type(META_KEY, { delay: 500, release: false }).type('z'); }, @@ -229,25 +351,40 @@ export class WorkflowPage extends BasePage { cy.get('body').type(META_KEY, { delay: 500, release: false }).type('a'); }, hitDisableNodeShortcut: () => { - cy.get('body').type(META_KEY, { delay: 500, release: false }).type('d'); + cy.get('body').type('d'); }, hitCopy: () => { cy.get('body').type(META_KEY, { delay: 500, release: false }).type('c'); }, - hitPaste: () => { - cy.get('body').type(META_KEY, { delay: 500, release: false }).type('P'); + hitPinNodeShortcut: () => { + cy.get('body').type('p'); + }, + hitExecuteWorkflowShortcut: () => { + cy.get('body').type(META_KEY, { delay: 500, release: false }).type('{enter}'); + }, + hitDuplicateNodeShortcut: () => { + cy.get('body').type(META_KEY, { delay: 500, release: false }).type('d'); + }, + hitAddStickyShortcut: () => { + cy.get('body').type('{shift}', { delay: 500, release: false }).type('S'); }, 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, @@ -266,36 +403,40 @@ export class WorkflowPage extends BasePage { this.getters.addStickyButton().click(); }, deleteSticky: () => { - this.getters.stickies().eq(0) - .realHover() - .find('[data-test-id="delete-sticky"]') - .click(); + this.getters.stickies().eq(0).realHover().find('[data-test-id="delete-sticky"]').click(); }, - editSticky: (content: string) => { - this.getters.stickies() - .dblclick() - .find('textarea') - .clear() - .type(content) - .type('{esc}'); - }, - turnOnManualExecutionSaving: () => { - this.getters.workflowMenu().click(); - this.getters.workflowMenuItemSettings().click(); - cy.get('.el-loading-mask').should('not.be.visible'); + toggleColorPalette: () => { this.getters - .workflowSettingsSaveManualExecutionsSelect() - .find('li:contains("Yes")') + .stickies() + .eq(0) + .realHover() + .find('[data-test-id="change-sticky-color"]') .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(); + }, + pickColor: (index: number) => { + this.getters.colors().eq(1).click(); + }, + editSticky: (content: string) => { + this.getters.stickies().dblclick().find('textarea').clear().type(content).type('{esc}'); + }, + clearSticky: () => { + this.getters.stickies().dblclick().find('textarea').clear().type('{esc}'); + }, + shouldHaveWorkflowName: (name: string) => { + this.getters.workflowNameInputContainer().invoke('attr', 'title').should('include', name); + }, + testLassoSelection: (from: [number, number], to: [number, number]) => { + cy.getByTestId('node-view-wrapper').trigger('mousedown', from[0], from[1], { force: true }); + cy.getByTestId('node-view-wrapper').trigger('mousemove', to[0], to[1], { force: true }); + cy.get('#select-box').should('be.visible'); + cy.getByTestId('node-view-wrapper').trigger('mouseup', to[0], to[1], { force: true }); + cy.get('#select-box').should('not.be.visible'); + }, + getNodePosition: (node: Cypress.Chainable>) => { + return node.then(($el) => ({ + left: +$el[0].style.left.replace('px', ''), + top: +$el[0].style.top.replace('px', ''), + })); }, }; } diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index 4a30b92ecbdc2..56a3c44923170 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -5,7 +5,7 @@ export class WorkflowsPage extends BasePage { getters = { newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'), newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'), - searchBar: () => cy.getByTestId('resources-list-search').find('input'), + searchBar: () => cy.getByTestId('resources-list-search'), createWorkflowButton: () => cy.getByTestId('resources-list-add'), workflowCards: () => cy.getByTestId('resources-list-item'), workflowCard: (workflowName: string) => @@ -23,9 +23,24 @@ export class WorkflowsPage extends BasePage { this.getters.workflowCard(workflowName).findChildByTestId('workflow-card-actions'), workflowDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'), + workflowFilterButton: () => cy.getByTestId('resources-list-filters-trigger').filter(':visible'), + workflowTagsDropdown: () => cy.getByTestId('tags-dropdown'), + workflowTagItem: (tag: string) => cy.getByTestId('tag').contains(tag), + workflowStatusDropdown: () => cy.getByTestId('status-dropdown'), + workflowStatusItem: (status: string) => cy.getByTestId('status').contains(status), + workflowOwnershipDropdown: () => cy.getByTestId('user-select-trigger'), + workflowOwner: (email: string) => cy.getByTestId('user-email').contains(email), + workflowResetFilters: () => cy.getByTestId('workflows-filter-reset'), // Not yet implemented // myWorkflows: () => cy.getByTestId('my-workflows'), // allWorkflows: () => cy.getByTestId('all-workflows'), + suggestedTemplatesPageContainer: () => cy.getByTestId('suggested-templates-page-container'), + suggestedTemplatesCards: () => cy.get('.agile__slides--regular [data-test-id=templates-info-card]'), + suggestedTemplatesNewWorkflowButton: () => cy.getByTestId('suggested-templates-new-workflow-button'), + suggestedTemplatesSectionContainer: () => cy.getByTestId('suggested-templates-section-container'), + suggestedTemplatesPreviewModal: () => cy.getByTestId('suggested-templates-preview-modal'), + suggestedTemplatesUseTemplateButton: () => cy.getByTestId('use-template-button'), + suggestedTemplatesSectionDescription: () => cy.getByTestId('suggested-template-section-description'), }; actions = { @@ -36,8 +51,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'); }, }; } diff --git a/cypress/support/binaryTestFiles.ts b/cypress/support/binaryTestFiles.ts index bb42cd2101ca8..4dc9919549627 100644 --- a/cypress/support/binaryTestFiles.ts +++ b/cypress/support/binaryTestFiles.ts @@ -1 +1,2 @@ -export const cowBase64 = "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" +export const cowBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAAHAAAABwCAMAAADxPgR5AAAACGFjVEwAAAAYAAAAANndHFMAAABmUExURf//////7PXszYl3WeXUqT03GmxYPEdEMbOafEc5J1lHMHZkR/Olptl7fP+9vc9dX5SJe4BqT//X1+vZrCsnEk9AK//99q2bfvrv1P/OzllHL8K6pX5sT+vdw/r04puJbPvpzOPOqsQreYIAAAABdFJOUwBA5thmAAAAGmZjVEwAAAAAAAAAcAAAAHAAAAAAAAAAAAAKAGQBAGZ29oAAAAGhSURBVGje7dhdT8JAEIVhCwti+UZDMPz//+mcpCfZTDqsGjsl5rw3pc3OPDcQ0Bf1PzpZ/tmba/Mgv0tgHsgDN+s0dLcA9K5rUGcB5Tx3CcwF363bELF11dbqHgS4WJjHLoHzgAxgDaGz1TXivMBckAFhvVVDxFrdLUCIuwTmgHyTMICEPoa4lPfRh3/twm6BOWBfVSwO/BTsXQJzQKIIWPfLiHIXMYE5INpYNbhvFKGLKuwUmA8S+xy6uPg8QgF5VGAueLSw8DCExfWV4QzOekzgc4CHb0awxiJUYC64s1oYzhDEHObH4GIJnBYkdrUcGkasIGIu7kK9JTAHJIqOjQgRi8D6D12B04FL6+oiHhdjBM/W1hKYAxbLg6VRBBZL4Dzga9XKaoE442eI4SowDyT6FyAxgfOAK1Zh+6Aa9TMCc0GP8jCxSxDROoHPBRI7BBFduDwKTGAeSJQg/7mwYyP3Hi0jCcwFiRLE1QMRWIKICUwCPeqKMH6B+x9ZeI59AjPB+M3jXy+HuHTshzGeC5wP5BIs5zBet84z3AucHPwCGDJBQOajSuoAAAAaZmNUTAAAAAEAAABsAAAAZAAAAAAAAAAMAAoAZAEABzCnYQAAAYFmZEFUAAAAAmje7djbbsIwEIRhAoGGcAq0qqh4//fsjuSRrJUdt2o2vZn/JgTs/W5COGzQZG1cR9cw04ZxViVhy2B48WlNqZeF4aPrs1JnAeR+zhIWi71bzxShQ9bJ6mYC2lvYj1nC1sEYsBxBd6trxP3CYjEGgI1WjhBq9bKmFGcJi8F4QTBgRD5SHMjz2hv74MJsYTHYmNVbXPxbbHQJi8EIIkDdHwJ4zNpagyVsWYwNVo5dGtXAbaHREhaHEfpKPVx83oFNDAmLwW4Whl1TGJofGdZgbQs6pITFYtcfRqwG3a2TJSweO1stCGuIlT50ha2DObAaIawlJGx9bGdhI9FbI67FYGH/iLmGZnUM+4Wtg71l7a2+EdaU9gyWsFiM4BIYb+j8kiosFtuzDLpUykHWFxIWh/kbMqFHJYJz5X+WCYvHCF0rEdy6hK2LESTGH4ZnVjgnWMIICYvFCBLjEdXO5yBhAVgLdHlolyp9+REWjbUvFP/Yr9+nCAlbHPsG9holMHgtc1IAAAAaZmNUTAAAAAMAAABwAAAAYAAAAAAAAAAQAAoAZAEAMy/MPAAAAYNmZEFUAAAABGje7dbbbsIwEIRh0nJKyiHQqqLi/d+zO5JHWq3suEUy5mL+G0jw7sdFkFih2VqFxtB+oRXjroUEPgRWsZs1p+4WgCn0XWiwgHKeuwQ+F/y0biliW9eHNSwEeG1hHruwc0oJfB7IAHoIXayhEucJchZ7AAtsA7LRNVkeIlbrbs0p7oqowDagf0j4AaGvFJfyuvTj34aI+t0C24CTa23xwH/BKYTdFwuowHYgGlPAhgcjOrpwT2B7kO0tDx4rldA3F/YBJCrwOSCxn9Q1xPsl1GMxGALbg2cLC08pLPavDGdwNmICXwM8/TGCxAS+DniwahjOEMQckMElsA8Y0GLE/CIP4lpgH5A30bkSzxET2B98t3IH1pUIRUxgP3Dn2lh+4JgJZ/x5tIQJbAcSjaDHrpkIMoH9wQ3LYKdCcWYfIiawKVj8AgT55/jAMtc4h5ncgySwD8hBgnxFpWs/i4gJ7ACGQS7PRqyQwH5gzD88/j0xga8PEs29F9gN/AXtWh/wZnKszAAAABpmY1RMAAAABQAAAHAAAABYAAAAAAAAABQACgBkAAAwn8ZCAAABZmZkQVQAAAAGaN7t09tuwjAQhGFCAsThlJaKUvX937M70o60WuE4UjHJxf43ELDnu4FNruTqC21mFmAdcJQ8NrjumRoNaJKwlUwB1gWJPaRRI+ZrMhHjfW7tpaNEOMC64E16aAT3Gi9OgXepk3AfW9jEHbuB3QDrgCi5LIQ+pKbQzcQf39E0SEkK8PUgAYYvLESs1K80atzxaID1wMHUScS/NI7yOffntzsE7W4vBfh6ECWtk3B5DphDk6kzBVgPZHgg+J+2GrYCXBY8FwpwvSCxH+3Txc+JeizA5cGrhMGLhmH7ynAGZy0W4HrAy8wIcizA9YAnqYThDM5aLMBlQaKI6FQe81CA7wWJ8YtWuhbqNT9qNwJ8P7g1uQvZ0e2TvqWddDDhOcD6IAbsoVbLgpofzoFwAqwDMh7yB9tMZ2lqeGfCc4B1QX/IH8T4KdOc4QBrguU8ilf/nmcCXDdI9Nn7ABcD/wCp0/cxvYy6+wAAABpmY1RMAAAABwAAAGgAAABgAAAACAAAAAwACgBkAAAqNm2eAAABY2ZkQVQAAAAIaN7t1l1Pg0AQhWERhJZqix/BGv///3ROsifZbBiHwJJoct4LK+3MPDe96IPXyWkM8u4JqgcRaZz6IIKCjoPORaegixMwQXUhImPR7NSkiE3WEiZoPzSliJQ1TkS4f7fw+prVW5gRtA+6pwj1WRfrN2i2Ogv77xYhIo+WoP0QAJYDCAtNEADGLxURREzQdij/UZEDRKK+rSnFW8AE1YPGLAJcWAPN1lgkqC6EN7sUgY8Uj2XPLnZmBYK7gvZB+IMHLMSQX35c0FFQnKC/Ab0EeYig4yEiX6m3Ir5PbC0iqB50s3DomsLB/JVhBrNAeGQJaS1B9aHryjC7BhFUB0L4B8vPVoRgBrMRIqgehA84RMyLCA8sIU/WUCSoDsTBW5CHoE+rhPiNE1QHItYFYcaDcBMYw7OgfRCxIatNuVBqKOLhPEF1IAwsLbVO3BFUHQoxxoUtO4K2Qepf9wPZWfDxej6GtgAAABpmY1RMAAAACQAAAHAAAABgAAAAAAAAAAwACgBkAABnZZsIAAABZGZkQVQAAAAKaN7t0tFKw0AQheHGpGkTtU1Vkooo+P4v6RyYA8uQ7AZhN1HmvylJd8930R68/995pn5FDm4PEqtmaldE2MHtwM50TvS4EGEH84KDZLHeNC5UaUSxFYI3DRsOZgAVu0uDRsxWRQLG+9wKsVZ6kHDOwTzgq3TXCLZBuBwDR6mRcB9b2LSYg/lA1JlCiD9+lQgQ45+PmIN5QX7BeimEiKX6kAaNW9h2sAzYmwjx4hpwlOyOg2VA1GmNRuhN4yifYyi3iDmYH2R4AIaLSTCSRVCjOVgKTOfg3wCfEy1hDm4PEnvXXkx8TzTEHNwHeJUweNEwHH4ynMFZYBxzcD/gZWU4G8NqaZJO2lFyMB9IFCNPUgrDGZyNYYQcLAdO0qdEdCli39IShj0AjJiDZcAv6Zpo0uZAvD/M5GBZENVSE8nB/YFH6WRKoXN38M7BfYC8XEdycEcgUdNv7zhYDPwBnsUNoDMUs3kAAAAaZmNUTAAAAAsAAABwAAAAZAAAAAAAAAAMAAoAZAAAAI8tggAAAX5mZEFUAAAADGje7dPJbsJAEIRhHLM5JICSSBCykfd/yVRJU9KoZbutoBlz6P9iPEt/F7MYa9vT44TGZgZYBxTW9MSB65FyOMCaoI8pDd4NZPEOBXhfoDAPtGiA9UBhnWnrJKAP5LNDAZYDjygHuXkxNU5COSvAuuAPOqNjSpjNw3Rfs/pA/g6wDPiKzimB66wdapyWiPc5y4Id4n6AZUDWmXKIfSIPJKQE6oMJsCwoQF1QDgnz+kCEhjC2RQGWAS8mQetUMzEiStgDCrAsyLqUNgS9pTTQvPemWcICLA8qvnCxQbeAVyQowJnAGyMQ4H2Az05TsBZtslYowPKgsPfUi0nrFiVgIxJgffCAOHCf4uD8qXiGZ4npT51jV0RACQuwDrifGM8OYSe0MAVYDlQ8xCFPyMN4hmctxloU4HzgFxI6lLBfJMSCnBfgfOA3OjidUn0g1wOcH2QtWjpZjPcCnA9coY3JQ/vucC3A+wG51o4UYHXQR03/vRNgNfAPiAUqMD2IFBoAAAAaZmNUTAAAAA0AAABwAAAAYAAAAAAAAAAIAAoAZAEAim0vyQAAAWlmZEFUAAAADmje7dbLbsJADIXhJkBKoAVaWkHT+/u/ZH0kH8kakXEQzSQL/5soxPa35W5IiyvL3QqwLEisMrXSqxPhAKcAfWwzIKC1RDjA0qCPNZnsDHdxK8B5gBbb9oRvAZYEfazWCGYwFw2wPEiMILEA5w0Sa5PWPT1pjSnA8uBBsiCALqlyAg4Aty6BSwm3A/x/8Fs6SweNWJqHcZ+3AiwLHqWzRrAxbaUcyIPYxy0L8luA44GoTbIQ+pA88KgRJBbg+CAB1kkWIub1LgGyWAqupADHAbskQo1WDWyjVUkL6V4KcBwQtdpSI/SicdG8ZwuwLMjWEjAM3ArWUooFWBB087EUBEQswHLgo1MOQ8SsEWAZkNib9pzE34laLMB5gHsJB3caDtsnwwxm+UcX+xYkFuA04G5gmL2E/UgBlgXZScKRB8nDMINZYjZiAU4DfkpE+yL2KwEIcD7gSgL4Je2dTlqAk4N/NLUPYOlyoTgAAAAaZmNUTAAAAA8AAABwAAAAbAAAAAAAAAAEAAoAZAABi7I+ZwAAAXpmZEFUAAAAEGje7dddS8NAEIVhE/qRtlpTq1ijoP7/P+kc2APDwmZDanZbOO9NSJmd52ZL6cNY7zMb2ylwVrOhZmY8L7AOmIN21lsmzLUWYYElwOnYfkJAAfpLJHAJcD62GcnP8CxRgfVBjx0SEcWztQSWAYlNAz2WRwXeFkhM4P2CWHYaSeB9g0xgfbCzCPi6TFhOVGB5sLc8CGCIajIRTYFrS+Ay4Ld1sfoQsbgcxvPY5TG2tYAKXAY8W5cQwY3rYDWZVhbOYxcvocAyINpFeQh9WTnwHBJYHiTABstDxHJ9Wn0Ie/ijS0zgsuAQRWgTaia2d+GdKCDeUIH/DxJFqxCh1xAPu/dk3OVBOgKXAVlnAcPQNeCP1VoCK4JX1oY8JrAO+JQphQmsDxL7CL1E8fMYbaOICSwPPltYeAxhsX8yzGCWf0ZxXuDtgMeJYXYME1gOZGsLSx6tHIYZzALbRmGPwDogI5qK2K+V2iGwJujhdPxyC6wO/gEmzFlgUTP51wAAABpmY1RMAAAAEQAAAHAAAABwAAAAAAAAAAAACgBkAADlbKtZAAABfGZkQVQAAAASaN7t2l1rwkAQhWFXk5qkH1ZspdD//z87BzLMELLZRZpZL857I8rmPAQUcuEh1+mBDhUR3Bs0LD2QwgTbgmWsHia4AxiAaUcJewSfDxyl70IKKkowAqzHpoqAAvMowfagx1428mcIxoH1XxjDXjMpqmAnEYwHjysZaFgNSjASNCgHKkbwOcE8ZSDGrhvlQNRLBGPBcdFQSNEceJaAEowB1x54k3RfKc15lGBbMPfAa+P5gBJsA366AK497G5Bd8lveIzg/iAO3KSfOYAAfFcpFcIorscWNj14kvTuCO4Djq5JMkgrgzeXbnoQFsF9QP2hawAVMazcrwQI6Ra2FSO4Lzi5OkkhvTBVNi0iGAEaioAlSaGvOR209/mw4/cIxoD4cJD+Azy6FCMYD5YrY9gi2BZ8L1SLEWwDXqSPQh7tFmGLYCzoUWBvkh+2VwtncHZY5DGCkaChCpZSMPfHVoLxYD1q2NnlNwi2BVEvXQr1iwg2A/8A0+MHMIo5vtgAAAAaZmNUTAAAABMAAABwAAAAcAAAAAAAAAAAAAoAZAAACPp4sAAAAWVmZEFUAAAAFGje7dTbasMwEITh2HFSH9qmJS0U+v7v2R3wwCJQpNBqlcL8N8YgzXdlH251rOxwRwIDQAcNlXlcYH+wAFXjAv8v6GGBDcAgzKMCHw9crM9CODfuYVNgBFiPrRUBFdgTLGPnG/kzAPkDENgHHF0APbZlIurBJ+tkCWwHEkuhDOiwMkqQqMC2ILEcSEzgY4LE/hrEXYECBdaBI2oATpbHBMaBS9JcUQ6kIzAGJHZOGioi6kFsC2wPvrkAEtlcNSA3BMaBxK7W195ibUnvVgmbLNzHFjbXPYEx4JLkIFYEry5uCowB0x81DgGoxdi35T98hG2BMeDqmixCvDhUtiZhG4bAtiBRBAwXCX3scdC9Z+MW9wTGgEdrtn4Dji7s8IMXGAMSJXhvRNhsCewD4uDFerZeC+EMzqaQxwTGgkQJshfLPxlBAoyYwEiwjLIUI3ByYUNgX5AXL4U8IrAr+APL+PChFMmrngAAABpmY1RMAAAAFQAAAHAAAABwAAAAAAAAAAAACgBkAgDXBmhIAAABUmZkQVQAAAAWaN7t2tFqwzAMheF5TdM43UhLNxjs/d9zEshImLrWLiJ34/w3IZDou7IxIS/of3dwBjAa9EPJmcUBjgc7kBsHGA/6MT8M8M+CikoAdwADsBoG+Bzg6y9KppMEMAB0AJn67NQCjxTAfUDFFGphq6OCcgAjQT82P8g+AzAaVKwNKnZuVNACcnbzBjgetJgXLYcogPuCbUpBxfzgRAEECHAsWDCAY8BsWjoBfCZQsdmUOhUY4BjwUnVvkSdHFwlgFKjYjfqSGDtXXakeNlH8Ps/imasEMAbMVQpJDvBmKjMBxoB2k7YL1pYcfVN24XOZAhgDrqZEXSUFfa1VmWID4H6gopoFP6Qk2Xtb6/A8UQAjQD0ML1SiZskDTqZF4lnlgzrAKFDR5KzG7v0gCTAaVHSj3qR3Sq/aRi2mUxXAceBR2jodqmqQZwAMAX8AyIHYEYSizPYAAAAaZmNUTAAAABcAAABwAAAAcAAAAAAAAAAAAAoAZAEAEb3oYgAAAU5mZEFUAAAAGGje7drRaoNAEIXhpjG1mpakpIFC3/89uwO7nMOA3WxgVhPOf6PC7nxeCIL4op67fUMCtwQC2jXEuMB1QUD3x/CBEhgABmAeHilDBT4SCFRgKBiKMSrwkUGg5giMAIEJfH7wtSG/T2AvsA5MqWslXi+wP+ghj8035FGBfcAW7O2feI3AvuASxCBjx4UKyuCQErglkLE6KnBbYMFaQMMErgdO1HulZRCYwH4gHhK0q1RggeuDjLWADAvsDQL12JG6FyyYwFjwTBkIpg0sMwT2AoFdUj85W8jQV66GDSnbb7Ns5pwT2AecXIzdCl6oMtNAMwTGgv5la9dgUO3j+2/qnONZh5TAeHCm+Aa4JWzI2fnsEhgPAkUe/M4VgK8HqnyEnagxJbAPiHADp9RH7jOFI7I1hvgf58acwLVAdKq0tM//ACkwFPwDOBHMwXcQBKYAAAAaZmNUTAAAABkAAABsAAAAaAAAAAAAAAAIAAoAZAEAt0Hv/AAAASJmZEFUAAAAGmje7drBCoMwEEVRra1trKDFFoT+/392Agk8gmmMHa3Cu7uBYc4uqxRTXaAio9QNYroYLl2hKqPYDTsT08LiULkghBHzIDFdzC+VCiFMbB0sgqihxI6DIUhMD/MQMWLEjo2dJOsQ2z+GEdsGO/0Qsf1gRnolCtGzREwXSyEINTMKQWK6mATgPKj+Eu4QWxeLIYghdI9US8T2hiGUBon9F/NQDmYhYupYFDTQLVEMQ4iYPhZCNVQm8iixbbFRQigHQxQxDxHTxYzUQ+GD+5BysF5CkJgOhtAwEUJzsdE1SBZsXMTWwUyQR3KwAbKYkYjpYgjW0NKH+C31LrxFTBdDEOukVnq6WhvOUCdVLhNETBNL180s9ZGSmCr2ATZwwPFLCoQfAAAAGmZjVEwAAAAbAAAAbAAAAGQAAAAAAAAACAAKAGQBAB8ilfEAAADpZmRBVAAAABxo3u3NwQrCMBCEYRuxGnuxFN//Ve1gC8NSm0Z3ycH5b2HDfCfuYjpVVd4Q5ovhcDWdv8huYFdYLNb9GMPC4rHOOYDC4jAcPCEGhfljDEWBwoT9K8aQMGFboDBhwoQJe5eWhPlikYiwGCwtHR3Ic89CwuIwRGAxQPcDARTWHmOo34lBYe0xhoYP4SYsFrNgGWOoDApri62QsPaYBfE5U7eKGBUWh2HwQQ3UONdVBJC3sC3MF8NjMjFUg6W5aSMYwvyxTI2moxDKJmH+2Ar2O6WK7NYKCXPCGKToXd3WljA37AUnJqSBEfMALQAAABpmY1RMAAAAHQAAAHAAAABcAAAAAAAAAAwACgBkAQD2AoWjAAAA6WZkQVQAAAAeaN7t1tEKwjAQRFFtsRJFQcT//1Wz0DBLaeJG2aSVuW8hZM5b6aGmWyHrBsEeILB7IbknuC1w/CJBCfYDTyp5fDSk38g5wQRbgsDOqjRQKv8GMMH9ghomuGcQKEFfEB/q3yP4jyBQggQJ9gSBEtwzCJQgQYLr4BDzhIY5gtsBQ+z1IQ0R9AWlhFrhiyFBCTYGDYVYGpwKaZRgExCoCQR2zSR3BFuDQG0gMBsKjKAfKGPP2DiXA4HZQEn20jZBfzCoHotqfnrDIoLeINCp0FDRckvOYhD0A4EinOtb2yLoAr4BN6eOce7b6XgAAAAaZmNUTAAAAB8AAABwAAAAVAAAAAAAAAAMAAoAZAAAzQarjAAAANVmZEFUAAAAIGje7dRbC4MwDIZhdczRnWCI//+vroGFhKAhBVMmfO9VD6TPXYdIL6ehMYA9QcHeTnQPMAFMwJ47MQrwf8DLQQHsC15NYzBvjmGAmaBgN1UU5Dl/VmCA5wQtDBAgwG2QFv7H3f55eyjAHHA8KIAAAQIECBAgwDOBJdComn4BzANp04Lda7MT3ZcaQwA7gMEs9tiJ0bWmZwHmgBQt6GAxOaDCfHRR0TzAHJBisKg+JjpjLAIWFcBsUNDZaWrIvsUYwDxQUEn27W29BTAF/AIO+4pR0fYgEwAAABpmY1RMAAAAIQAAAHAAAABkAAAAAAAAAAQACgBkAADcqv+yAAABA2ZkQVQAAAAiaN7t2N0KwyAMhuHZ0Y2O/cBg93+rNQchImpmi1Hhe09aW+Jz1EJ7OdKnkDYL0AZcg/jaVSmcAdgLFOweRGva0BXKzQAcB3RKuRlCAc4NMgrQDkw94C9fdF6RoABbgTr29NExPgc4NChYJoYB9gZ1TFBBzqAA7cG3LwRoDXAyUAngGGD8Q4DBry8H0T3GAHYFCx8vgsYxtkYBHA9kNBXPxHMABwKVAPYG9Zc3wPlAd6Cal/fiA9gMFLRBjAG0AZeg84gEsAcoHdm4hAFsBgpaGcB5wc33UwJoDwqqY48/YpQxgBagoFQOY/BWKERpT4CmoMCJtop4H4DNwR0796YRRhi74QAAABpmY1RMAAAAIwAAAGwAAABsAAAAAAAAAAAACgBkAAD5ce+iAAAA7mZkQVQAAAAkaN7t2cEKgzAQRVHbErrRFvr/H1sDwguhjgnO1Czu3QhO9Oyi4tTa22hqDMzE3KHZqITBrsHS1qOjVAT2X6xEbh3VcD6CxWOpqAVIRXswWAQm6Fl0BB6vFwrmj+UT3lgNgoGB6UEZEZgbZkKLUTe2BRaBCZqN8hxsDKyGPEGweEyobg42NvY6CGws7LNmQXkONgwmcDdBYNdhqaj1JRXsOqz9w12lKrCxMa1vu+a+BTY6JgTMFTM24n6sBsBisV9DE6wyDEFrYC6YwBOBgYGZWDAoCMwbE+iB1j9WwcIwoacTAuaOfQER+52x4k1fNgAAABpmY1RMAAAAJQAAAGwAAABwAAAAAAAAAAAACgBkAQAMN5oyAAAA6GZkQVQAAAAmaN7t2EEKgzAQQFGNSDdqofc/bBtQJoR2hpiMpPL/0ui8TVDiYPU0GgoC88WmvcUoglMSmBdmQ+PJUhjMC7OhGhjMB5uTRqM5CexqTKBHkjZEuVcFwcDAwP4Lq/9Ygt0NExDMF1uNwPrEFqW4DtYHlkMWCNYXJqgMr90kYL7YZgTWF/b6pEFxHawbTECluD5ngV2LfXtoVZKDvBSvgV2DnfmRAnZvLOyB+WMmmPULAWuKKS/isoLRcRgEa4MdYMgqGQzWAaYEBgZWjDmBghwQWGtMwFhrBMwNE7QqQcBcsDe74ZwhV/x3xgAAABpmY1RMAAAAJwAAAGgAAABwAAAABAAAAAAACgBkAAChCwb6AAAA7mZkQVQAAAAoaN7t2MsKwyAQQNFoKd2kKfT/P7axICPSUXB8pHDvJogjZydJNq1Xoa0hoG9dkb1QBIFmQ4LcGgKaA8VDrqEUBRoDaYAFBbJDdcQOAlkhQYCuDd2zXKV8HmgsFAYfWaXD+ryOAQEBWSDBgJqhbpcq0FjIZT0LuR8BzYIE2QuFfaBVkCAppq2BFkJKQNeCwvNIytdAa6H32VEo7AMtgwRTEwSoCeJN9f+gpR9iPgnIAHX+oeGTgOZD8ZA2H/aA5kI+C2gFpF+qvpIyryNnQO1QxLw5HQGyQoKFegERAeoGCWhOAKAu0Af14aCh2gE5VAAAABpmY1RMAAAAKQAAAGgAAABwAAAAAAAAAAAACgBkAAD8oUQkAAABNGZkQVQAAAAqaN7t2dGKgzAQhWFrTDdCa2Hv+v4vumfBYcLUcatTtUvPfxOMDN+NBCTNbz06jxV0Q3c0jF2Rrtod3VAaO0/UowYRei10Wlmq6pEghKKQYoJ0CyuoNSXUIEIxyCZQWZBFLEYoCFlkZRaQfULbQvEUIxSHEiL03pCHdBMROh6yyBqsIIsR2hcqiND/hbx3hGJQNj0DZROh7aCMvkwZzUHejCCE3hc6IUKEPhGyh6og15laNHWoEtoOKqgbE+R7JsHqCO0FKTKgS1X9LBih46ALklWyz4QOhpwIBaCdP4YBLYFaROhVkGKDm0V8TPYJxaGMHjAnD8mmFhHaBvIH/Lwfs/pynlAE0stEb4DQ8ZBg3kD3Rz6kCKEIpHkD6YmyKSFBCMUgmzOwMAUIhaEffvXDgc1gj3oAAAAaZmNUTAAAACsAAABsAAAAcAAAAAAAAAAAAAoAZAAA+F+aLwAAAV5mZEFUAAAALGje7dfBbsIwEEVREpI2lYiLYNP+/492nmRrRpYHZ2EnoXp3g8DjOQsiIS7obnpKy8auWfdCFxOxBliEfqRnbJV+Y4+s9DlmhkIAv7KItcdQOviWsCzEsFRfNcxgdnD6MGEvsVaYQukAC8LGPGyM2QeEWEtMwUXCgptUgzBTwsYsYv0wBGySIuhWgnAP93NskYj1xRR8nUUsRGx/DAsiWEmREjSYcE6sL4aWjQ1OxPpj3sXJKZ0T+z9Yvu8qETsWwzyx82G1H80aROydMQWJtcU+s2bJw3BWmk8QsffBsJTYsdjqROwc2GxK0ONF+fwoEdsXm0wJCtLNZN/jHHNjjNi+mIKKYWF6RaX3xE6GORHrhfV/QIJE7CyYgsFNIVsNItYEc8E5azWVIO8esT0w/8/eWGjLn0RivTAFvUuTUw3DXmL9MOR+0ZWIHYUpaPMn6/eINcX+ACCf/BEcVDlJAAAAGmZjVEwAAAAtAAAAbAAAAHAAAAAAAAAAAAAKAGQAABWV6FUAAAF/ZmRBVAAAAC5o3u3Xy26DMBCF4YSQlNCGVmkX9P1ftHMkj8ay8UWqDVmcf4MInvk2WcBJ+5JODcrtItYOw8NVwlUP3jeaEukOTXcR64t9S6sLi+eNzokArl7YRawvdg+6ef24dLHep9BwF7H2mL98lkbp3asOs7Dj5oX9xNpjs2uSgJ3/2exFrDVmoA89CtWAg6QQsfYYUgzLfl3PIP29BA4u7CTWF/uUsGxxYaldLZzB2TRkXSRifbGlMsVCiNj+2IdUgnCmBsNvxPpio6RguhjCnAFxAIn1wQzM5yMIc1sfhsT6Y3poLGaIzhHbFwsHpkIhQuxoLC7156iBfJBYb8wgYq+JYcFb0FUaM+F5eF4hYsdgj0wpDIuJ7YfpAoSFz0x4PkhXL9wT2wWLQIX0Y13z7w20iB2LYaFetfCe2IthxWIMs8SOwsp/kEXyMZ3NQcSaYFlwSRZDxPbGYrD0knrZaOulVSFivTADw6FTRYqFc8R6YzaI8gPlOWLNsT/srwtAAftErwAAABh0RVh0U29mdHdhcmUAZ2lmMmFwbmcuc2YubmV0lv8TyAAAAABJRU5ErkJggg=='; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 132745510cbfa..a92dc2ce06443 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,32 +1,12 @@ -// *********************************************** -// 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 { + BACKEND_BASE_URL, + INSTANCE_ADMIN, + INSTANCE_MEMBERS, + INSTANCE_OWNER, + N8N_AUTH_COOKIE, +} from '../constants'; Cypress.Commands.add('getByTestId', (selector, ...args) => { return cy.get(`[data-test-id="${selector}"]`, ...args); @@ -42,8 +22,8 @@ Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => { cy.waitForLoad(false); workflowPage.actions.setWorkflowName(workflowName); - workflowPage.getters.saveButton().should('contain', 'Saved'); + workflowPage.actions.zoomToFit(); }); Cypress.Commands.add( @@ -58,149 +38,57 @@ Cypress.Commands.add('waitForLoad', (waitForIntercepts = true) => { // These aliases are set-up before each test in cypress/support/e2e.ts // 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']) + if (waitForIntercepts) { + cy.wait(['@loadSettings', '@loadNodeTypes']); } 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); - }, - { - validate() { - cy.getCookie(N8N_AUTH_COOKIE).should('exist'); - }, - }, + Cypress.session.clearAllSavedSessions(); + cy.session([email, password], () => + cy.request({ + method: 'POST', + url: `${BACKEND_BASE_URL}/rest/login`, + body: { email, password }, + failOnStatusCode: false, + }), ); }); -Cypress.Commands.add('signout', () => { - cy.visit('/signout'); - cy.waitForLoad(); - cy.url().should('include', '/signin'); - 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) => { - signupPage.getters.firstName().type(firstName); - signupPage.getters.lastName().type(lastName); - signupPage.getters.password().type(password); - signupPage.getters.submit().click(); - }); - }); +Cypress.Commands.add('signinAsOwner', () => { + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); }); -Cypress.Commands.add('setup', ({ email, firstName, lastName, password }) => { - const signupPage = new SignupPage(); - - cy.visit(signupPage.url); - - 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); - signupPage.getters.submit().click(); - } else { - cy.log('User already signed up'); - } - }); +Cypress.Commands.add('signout', () => { + cy.request({ + method: 'POST', + url: `${BACKEND_BASE_URL}/rest/logout`, + headers: { 'browser-id': localStorage.getItem('n8n-browserId') } }); + cy.getCookie(N8N_AUTH_COOKIE).should('not.exist'); }); Cypress.Commands.add('interceptREST', (method, url) => { - cy.intercept(method, `http://localhost:5678/rest${url}`); + cy.intercept(method, `${BACKEND_BASE_URL}/rest${url}`); }); -Cypress.Commands.add('inviteUsers', ({ instanceOwner, users }) => { - const settingsUsersPage = new SettingsUsersPage(); - - 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 }); - }); +const setFeature = (feature: string, enabled: boolean) => + cy.request('PATCH', `${BACKEND_BASE_URL}/rest/e2e/feature`, { + feature: `feat:${feature}`, + enabled, }); -}); - -Cypress.Commands.add('skipSetup', () => { - const signupPage = new SignupPage(); - const workflowPage = new WorkflowPage(); - const Confirmation = new MessageBox(); - - cy.visit(signupPage.url); - 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'); - } - }); +const setQueueMode = (enabled: boolean) => + cy.request('PATCH', `${BACKEND_BASE_URL}/rest/e2e/queue-mode`, { + enabled, }); -}); -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('enable-feature', feature); -}); +Cypress.Commands.add('enableFeature', (feature: string) => setFeature(feature, true)); +Cypress.Commands.add('disableFeature', (feature: string) => setFeature(feature, false)); +Cypress.Commands.add('enableQueueMode', () => setQueueMode(true)); +Cypress.Commands.add('disableQueueMode', () => setQueueMode(false)); Cypress.Commands.add('grantBrowserPermissions', (...permissions: string[]) => { if (Cypress.isBrowser('chrome')) { @@ -235,19 +123,36 @@ Cypress.Commands.add('paste', { prevSubject: true }, (selector, pastePayload) => Cypress.Commands.add('drag', (selector, pos, options) => { const index = options?.index || 0; const [xDiff, yDiff] = pos; - const element = cy.get(selector).eq(index); + const element = typeof selector === 'string' ? cy.get(selector).eq(index) : selector; element.should('exist'); - const originalLocation = Cypress.$(selector)[index].getBoundingClientRect(); - - element.trigger('mousedown'); - element.trigger('mousemove', { - which: 1, - pageX: options?.abs? xDiff: originalLocation.right + xDiff, - pageY: options?.abs? yDiff: originalLocation.top + yDiff, - force: true, + element.then(([$el]) => { + const originalLocation = $el.getBoundingClientRect(); + const newPosition = { + x: options?.abs ? xDiff : originalLocation.right + xDiff, + y: options?.abs ? yDiff : originalLocation.top + yDiff, + }; + if (options?.realMouse) { + element.realMouseDown(); + element.realMouseMove(newPosition.x, newPosition.y); + element.realMouseUp(); + } else { + element.trigger('mousedown', { force: true }); + element.trigger('mousemove', { + which: 1, + pageX: newPosition.x, + pageY: newPosition.y, + force: true, + }); + if (options?.clickToFinish) { + // Click to finish the drag + // For some reason, mouseup isn't working when moving nodes + cy.get('body').click(newPosition.x, newPosition.y); + } else { + element.trigger('mouseup', { force: true }); + } + } }); - element.trigger('mouseup', { force: true }); }); Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => { @@ -269,6 +174,7 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => { cy.get(draggableSelector).trigger('mousedown'); } // We don't chain these commands to make sure cy.get is re-trying correctly + cy.get(droppableSelector).realMouseMove(0, 0); cy.get(droppableSelector).realMouseMove(pageX, pageY); cy.get(droppableSelector).realHover(); cy.get(droppableSelector).realMouseUp(); @@ -277,3 +183,25 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => { } }); }); + +Cypress.Commands.add('push', (type, data) => { + cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/push`, { + type, + data, + }); +}); + +Cypress.Commands.add('shouldNotHaveConsoleErrors', () => { + cy.window().then((win) => { + const spy = cy.spy(win.console, 'error'); + cy.wrap(spy).should('not.have.been.called'); + }); +}); + +Cypress.Commands.add('resetDatabase', () => { + cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, { + owner: INSTANCE_OWNER, + members: INSTANCE_MEMBERS, + admin: INSTANCE_ADMIN, + }); +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index ee534b4dc59fd..69bb74ec88b0c 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -1,30 +1,31 @@ -// *********************************************************** -// 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 { INSTANCE_OWNER } from '../constants'; import './commands'; -// Load custom nodes and credentials fixtures +before(() => { + cy.resetDatabase(); + + Cypress.on('uncaught:exception', (err) => { + return !err.message.includes('ResizeObserver'); + }); +}); + beforeEach(() => { + if (!cy.config('disableAutoLogin')) { + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + } + + cy.window().then((win): void => { + win.localStorage.setItem('N8N_THEME', 'light'); + }); + cy.intercept('GET', '/rest/settings').as('loadSettings'); - cy.intercept('GET', '/rest/login').as('loadLogin'); + cy.intercept('GET', '/types/nodes.json').as('loadNodeTypes'); // Always intercept the request to test credentials and return a success cy.intercept('POST', '/rest/credentials/test', { statusCode: 200, body: { data: { status: 'success', message: 'Tested successfully' }, - } + }, }); -}) +}); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 7665602dafcb4..f31e50c57899c 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -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 | undefined)[] @@ -34,21 +23,33 @@ declare global { findChildByTestId(childTestId: string): Chainable>; createFixtureWorkflow(fixtureKey: string, workflowName: string): void; signin(payload: SigninPayload): void; + signinAsOwner(): void; signout(): void; - signup(payload: SignupPayload): void; - setup(payload: SetupPayload): void; - setupOwner(payload: SetupPayload): void; - inviteUsers(payload: InviteUsersPayload): void; interceptREST(method: string, url: string): Chainable; - skipSetup(): void; - resetAll(): void; enableFeature(feature: string): void; + disableFeature(feature: string): void; + enableQueueMode(): void; + disableQueueMode(): void; waitForLoad(waitForIntercepts?: boolean): void; grantBrowserPermissions(...permissions: string[]): void; readClipboard(): Chainable; paste(pastePayload: string): void; - drag(selector: string, target: [number, number], options?: {abs?: true, index?: number}): void; + drag( + selector: string | Cypress.Chainable>, + target: [number, number], + options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean }, + ): void; draganddrop(draggableSelector: string, droppableSelector: string): void; + push(type: string, data: unknown): void; + shouldNotHaveConsoleErrors(): void; + window(): Chainable< + AUTWindow & { + featureFlags: { + override: (feature: string, value: any) => void; + }; + } + >; + resetDatabase(): void; } } } diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 61b0e504a304f..26a5da716b47e 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "sourceMap": false, "declaration": false, - "sourceMap": false - } + "lib": ["esnext", "dom"], + "types": ["cypress", "node"] + }, + "include": ["**/*.ts"], + "exclude": ["**/dist/**/*", "**/node_modules/**/*"] } diff --git a/cypress/utils/executions.ts b/cypress/utils/executions.ts new file mode 100644 index 0000000000000..81748af50566e --- /dev/null +++ b/cypress/utils/executions.ts @@ -0,0 +1,135 @@ +import { ITaskData } from '../../packages/workflow/src'; +import { IPinData } from '../../packages/workflow'; +import { clickExecuteWorkflowButton } from '../composables/workflow'; + +export function createMockNodeExecutionData( + name: string, + { + data, + inputOverride, + executionStatus = 'success', + jsonData, + ...rest + }: Partial & { jsonData?: Record }, +): Record { + return { + [name]: { + startTime: new Date().getTime(), + executionTime: 0, + executionStatus, + data: jsonData + ? Object.keys(jsonData).reduce((acc, key) => { + acc[key] = [ + [ + { + json: jsonData[key], + pairedItem: { item: 0 }, + }, + ], + ]; + + return acc; + }, {}) + : data, + source: [null], + ...rest, + }, + }; +} + +export function createMockWorkflowExecutionData({ + executionId, + runData, + pinData = {}, + lastNodeExecuted, +}: { + executionId: string; + runData: Record; + pinData?: IPinData; + lastNodeExecuted: string; +}) { + return { + executionId, + data: { + data: { + startData: {}, + resultData: { + runData, + pinData, + lastNodeExecuted, + }, + executionData: { + contextData: {}, + nodeExecutionStack: [], + metadata: {}, + waitingExecution: {}, + waitingExecutionSource: {}, + }, + }, + mode: 'manual', + startedAt: new Date().toISOString(), + stoppedAt: new Date().toISOString(), + status: 'success', + finished: true, + }, + }; +} + +export function runMockWorkflowExcution({ + trigger, + lastNodeExecuted, + runData, + workflowExecutionData, +}: { + trigger?: () => void; + lastNodeExecuted: string; + runData: Array>; + workflowExecutionData?: ReturnType; +}) { + const executionId = Math.random().toString(36).substring(4); + + cy.intercept('POST', '/rest/workflows/run', { + statusCode: 201, + body: { + data: { + executionId, + }, + }, + }).as('runWorkflow'); + + if (trigger) { + trigger(); + } else { + clickExecuteWorkflowButton(); + } + + cy.wait('@runWorkflow'); + + const resolvedRunData = {}; + runData.forEach((nodeExecution) => { + const nodeName = Object.keys(nodeExecution)[0]; + const nodeRunData = nodeExecution[nodeName]; + + cy.push('nodeExecuteBefore', { + executionId, + nodeName, + }); + cy.push('nodeExecuteAfter', { + executionId, + nodeName, + data: nodeRunData, + }); + + resolvedRunData[nodeName] = nodeExecution[nodeName]; + }); + + cy.push( + 'executionFinished', + createMockWorkflowExecutionData({ + executionId, + lastNodeExecuted, + runData: resolvedRunData, + ...workflowExecutionData, + }), + ); +} diff --git a/cypress/utils/index.ts b/cypress/utils/index.ts new file mode 100644 index 0000000000000..3cfa5a7449186 --- /dev/null +++ b/cypress/utils/index.ts @@ -0,0 +1,3 @@ +export * from './executions'; +export * from './modal'; +export * from './popper'; diff --git a/cypress/utils/modal.ts b/cypress/utils/modal.ts new file mode 100644 index 0000000000000..4b260ca9e858a --- /dev/null +++ b/cypress/utils/modal.ts @@ -0,0 +1,3 @@ +export function getVisibleModalOverlay() { + return cy.get('.el-overlay .el-overlay-dialog').filter(':visible'); +} diff --git a/cypress/utils/popper.ts b/cypress/utils/popper.ts new file mode 100644 index 0000000000000..5743c70f3e6fa --- /dev/null +++ b/cypress/utils/popper.ts @@ -0,0 +1,15 @@ +export function getPopper() { + return cy.get('.el-popper'); +} + +export function getVisiblePopper() { + return getPopper().filter(':visible'); +} + +export function getVisibleSelect() { + return getVisiblePopper().filter('.el-select__popper'); +} + +export function getVisibleDropdown() { + return getVisiblePopper().filter('.el-dropdown__popper'); +} diff --git a/docker/compose/subfolderWithSSL/.env b/docker/compose/subfolderWithSSL/.env deleted file mode 100644 index 7008bd631a05e..0000000000000 --- a/docker/compose/subfolderWithSSL/.env +++ /dev/null @@ -1,25 +0,0 @@ -# Folder where data should be saved -DATA_FOLDER=/root/n8n/ - -# The top level domain to serve from -DOMAIN_NAME=example.com - -# The subfolder to serve from -SUBFOLDER=app1 -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 - -# The email address to use for the SSL certificate creation -SSL_EMAIL=user@example.com diff --git a/docker/compose/subfolderWithSSL/README.md b/docker/compose/subfolderWithSSL/README.md deleted file mode 100644 index 3970938719caa..0000000000000 --- a/docker/compose/subfolderWithSSL/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# n8n on Subfolder with SSL - -Starts n8n and deploys it on a subfolder - -## Start - -To start n8n in a subfolder 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` file! - -``` -docker-compose up -d -``` - -To stop it execute: - -``` -docker-compose stop -``` diff --git a/docker/compose/subfolderWithSSL/docker-compose.yml b/docker/compose/subfolderWithSSL/docker-compose.yml deleted file mode 100644 index 37a4b25f182f2..0000000000000 --- a/docker/compose/subfolderWithSSL/docker-compose.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: '3' - -services: - traefik: - image: 'traefik' - command: - - '--api=true' - - '--api.insecure=true' - - '--api.dashboard=true' - - '--providers.docker=true' - - '--providers.docker.exposedbydefault=false' - - '--entrypoints.websecure.address=:443' - - '--certificatesresolvers.mytlschallenge.acme.tlschallenge=true' - - '--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}' - - '--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json' - ports: - - '443:443' - - '80:80' - volumes: - - ${DATA_FOLDER}/letsencrypt:/letsencrypt - - /var/run/docker.sock:/var/run/docker.sock:ro - n8n: - image: docker.n8n.io/n8nio/n8n - ports: - - '127.0.0.1:5678:5678' - labels: - - traefik.enable=true - - traefik.http.routers.n8n.rule=Host(`${DOMAIN_NAME}`) - - traefik.http.routers.n8n.tls=true - - traefik.http.routers.n8n.entrypoints=websecure - - 'traefik.http.routers.n8n.rule=PathPrefix(`/${SUBFOLDER}{regex:$$|/.*}`)' - - 'traefik.http.middlewares.n8n-stripprefix.stripprefix.prefixes=/${SUBFOLDER}' - - 'traefik.http.routers.n8n.middlewares=n8n-stripprefix' - - traefik.http.routers.n8n.tls.certresolver=mytlschallenge - - traefik.http.middlewares.n8n.headers.SSLRedirect=true - - traefik.http.middlewares.n8n.headers.STSSeconds=315360000 - - traefik.http.middlewares.n8n.headers.browserXSSFilter=true - - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true - - traefik.http.middlewares.n8n.headers.forceSTSHeader=true - - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME} - - 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 - - NODE_ENV=production - - N8N_PATH - - WEBHOOK_URL=https://${DOMAIN_NAME}${N8N_PATH} - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ${DATA_FOLDER}/.n8n:/home/node/.n8n diff --git a/docker/compose/withMariaDB/.env b/docker/compose/withMariaDB/.env deleted file mode 100644 index 48f9efcd642a0..0000000000000 --- a/docker/compose/withMariaDB/.env +++ /dev/null @@ -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 diff --git a/docker/compose/withMariaDB/README.md b/docker/compose/withMariaDB/README.md deleted file mode 100644 index 6af2fa8c8ac16..0000000000000 --- a/docker/compose/withMariaDB/README.md +++ /dev/null @@ -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. diff --git a/docker/compose/withMariaDB/docker-compose.yml b/docker/compose/withMariaDB/docker-compose.yml deleted file mode 100644 index e7a1db74e0ff9..0000000000000 --- a/docker/compose/withMariaDB/docker-compose.yml +++ /dev/null @@ -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 diff --git a/docker/compose/withPostgres/.env b/docker/compose/withPostgres/.env deleted file mode 100644 index c074f42c9edae..0000000000000 --- a/docker/compose/withPostgres/.env +++ /dev/null @@ -1,9 +0,0 @@ -POSTGRES_USER=changeUser -POSTGRES_PASSWORD=changePassword -POSTGRES_DB=n8n - -POSTGRES_NON_ROOT_USER=changeUser -POSTGRES_NON_ROOT_PASSWORD=changePassword - -N8N_BASIC_AUTH_USER=changeUser -N8N_BASIC_AUTH_PASSWORD=changePassword diff --git a/docker/compose/withPostgres/README.md b/docker/compose/withPostgres/README.md deleted file mode 100644 index f47d5e51c86a5..0000000000000 --- a/docker/compose/withPostgres/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# n8n with PostgreSQL - -Starts n8n with PostgreSQL as database. - -## Start - -To start n8n with PostgreSQL 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 PostgreSQL can be changed in the [`.env`](.env) file in the current directory. diff --git a/docker/compose/withPostgres/docker-compose.yml b/docker/compose/withPostgres/docker-compose.yml deleted file mode 100644 index 9b3ab5b83e23f..0000000000000 --- a/docker/compose/withPostgres/docker-compose.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: '3.8' - -volumes: - db_storage: - n8n_storage: - -services: - postgres: - image: postgres:11 - restart: always - environment: - - POSTGRES_USER - - POSTGRES_PASSWORD - - POSTGRES_DB - - POSTGRES_NON_ROOT_USER - - POSTGRES_NON_ROOT_PASSWORD - volumes: - - db_storage:/var/lib/postgresql/data - - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh - healthcheck: - test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] - interval: 5s - timeout: 5s - retries: 10 - - n8n: - image: docker.n8n.io/n8nio/n8n - restart: always - environment: - - DB_TYPE=postgresdb - - DB_POSTGRESDB_HOST=postgres - - DB_POSTGRESDB_PORT=5432 - - 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 diff --git a/docker/compose/withPostgres/init-data.sh b/docker/compose/withPostgres/init-data.sh deleted file mode 100755 index 69a8d79e1ea03..0000000000000 --- a/docker/compose/withPostgres/init-data.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -e; - - -if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then - psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL - CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}'; - GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER}; - EOSQL -else - echo "SETUP INFO: No Environment variables given!" -fi diff --git a/docker/compose/withPostgresAndWorker/.env b/docker/compose/withPostgresAndWorker/.env deleted file mode 100644 index c074f42c9edae..0000000000000 --- a/docker/compose/withPostgresAndWorker/.env +++ /dev/null @@ -1,9 +0,0 @@ -POSTGRES_USER=changeUser -POSTGRES_PASSWORD=changePassword -POSTGRES_DB=n8n - -POSTGRES_NON_ROOT_USER=changeUser -POSTGRES_NON_ROOT_PASSWORD=changePassword - -N8N_BASIC_AUTH_USER=changeUser -N8N_BASIC_AUTH_PASSWORD=changePassword diff --git a/docker/compose/withPostgresAndWorker/README.md b/docker/compose/withPostgresAndWorker/README.md deleted file mode 100644 index 6c291dea8d624..0000000000000 --- a/docker/compose/withPostgresAndWorker/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# n8n with PostgreSQL and Worker - -Starts n8n with PostgreSQL as database, and the Worker as a separate container. - -## Start - -To start n8n 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 PostgreSQL can be changed in the [`.env`](.env) file in the current directory. diff --git a/docker/compose/withPostgresAndWorker/docker-compose.yml b/docker/compose/withPostgresAndWorker/docker-compose.yml deleted file mode 100644 index 9f947d735d49b..0000000000000 --- a/docker/compose/withPostgresAndWorker/docker-compose.yml +++ /dev/null @@ -1,76 +0,0 @@ -version: '3.8' - -volumes: - db_storage: - n8n_storage: - redis_storage: - -x-shared: &shared - restart: always - environment: - - DB_TYPE=postgresdb - - DB_POSTGRESDB_HOST=postgres - - DB_POSTGRESDB_PORT=5432 - - DB_POSTGRESDB_DATABASE=${POSTGRES_DB} - - DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER} - - DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD} - - 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 - volumes: - - n8n_storage:/home/node/.n8n - depends_on: - redis: - condition: service_healthy - postgres: - condition: service_healthy - -services: - postgres: - image: postgres:11 - restart: always - environment: - - POSTGRES_USER - - POSTGRES_PASSWORD - - POSTGRES_DB - - POSTGRES_NON_ROOT_USER - - POSTGRES_NON_ROOT_PASSWORD - volumes: - - db_storage:/var/lib/postgresql/data - - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh - healthcheck: - test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] - interval: 5s - timeout: 5s - retries: 10 - - redis: - image: redis:6-alpine - restart: always - volumes: - - redis_storage:/data - healthcheck: - test: ['CMD', 'redis-cli', 'ping'] - interval: 5s - timeout: 5s - retries: 10 - - 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" - depends_on: - - n8n diff --git a/docker/compose/withPostgresAndWorker/init-data.sh b/docker/compose/withPostgresAndWorker/init-data.sh deleted file mode 100755 index 69a8d79e1ea03..0000000000000 --- a/docker/compose/withPostgresAndWorker/init-data.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -e; - - -if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then - psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL - CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}'; - GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER}; - EOSQL -else - echo "SETUP INFO: No Environment variables given!" -fi diff --git a/docker/images/n8n-base/.npmrc b/docker/images/n8n-base/.npmrc deleted file mode 100644 index 7a650526cf7dc..0000000000000 --- a/docker/images/n8n-base/.npmrc +++ /dev/null @@ -1,4 +0,0 @@ -audit = false -fund = false -loglevel = warn -update-notifier = false diff --git a/docker/images/n8n-base/Dockerfile b/docker/images/n8n-base/Dockerfile index 62258d8d3857b..1a3236fae5d60 100644 --- a/docker/images/n8n-base/Dockerfile +++ b/docker/images/n8n-base/Dockerfile @@ -1,20 +1,38 @@ -ARG NODE_VERSION=16 -FROM node:${NODE_VERSION}-alpine +ARG NODE_VERSION=18 -WORKDIR /home/node -COPY .npmrc /usr/local/etc/npmrc +# 1. Use a builder step to download various dependencies +FROM node:${NODE_VERSION}-alpine as builder -RUN \ - apk add --update git graphicsmagick tini tzdata ca-certificates && \ - npm install -g npm@8.19.2 full-icu && \ - rm -rf /var/cache/apk/* /root/.npm /tmp/* && \ - # Install fonts +# Install fonts +RUN \ apk --no-cache add --virtual fonts msttcorefonts-installer fontconfig && \ update-ms-fonts && \ fc-cache -f && \ apk del fonts && \ - find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \; && \ - rm -rf /var/cache/apk/* /tmp/* + find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \; + +# Install git and other OS dependencies +RUN apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc6-compat jq + +# Update npm and install full-uci +COPY .npmrc /usr/local/etc/npmrc +RUN npm install -g npm@9.9.2 full-icu@1.5.0 +# Activate corepack, and install pnpm +WORKDIR /tmp +COPY package.json ./ +RUN corepack enable && corepack prepare --activate + +# Cleanup +RUN rm -rf /lib/apk/db /var/cache/apk/ /tmp/* /root/.npm /root/.cache/node /opt/yarn* + +# 2. Start with a new clean image and copy over the added files into a single layer +FROM node:${NODE_VERSION}-alpine +COPY --from=builder / / + +# Delete this folder to make the base image backward compatible to be able to build older version images +RUN rm -rf /tmp/v8-compile-cache* + +WORKDIR /home/node ENV NODE_ICU_DATA /usr/local/lib/node_modules/full-icu EXPOSE 5678/tcp diff --git a/docker/images/n8n-custom/Dockerfile b/docker/images/n8n-custom/Dockerfile index f737a3847d1e4..ff7a4c0e0551e 100644 --- a/docker/images/n8n-custom/Dockerfile +++ b/docker/images/n8n-custom/Dockerfile @@ -1,4 +1,4 @@ -ARG NODE_VERSION=16 +ARG NODE_VERSION=18 ARG ACCESS_TOKEN # 1. Create an image to build n8n @@ -6,27 +6,32 @@ FROM n8nio/base:${NODE_VERSION} as builder ARG ACCESS_TOKEN ENV TOKEN_FOR_PACKAGES=${ACCESS_TOKEN} -COPY --chown=node:node turbo.json package.json .npmrc pnpm-lock.yaml pnpm-workspace.yaml jest.config.js tsconfig.json ./ +RUN mkdir /compiled && chown node:node /compiled + +COPY --chown=node:node turbo.json package.json .npmrc pnpm-lock.yaml pnpm-workspace.yaml jest.config.js tsconfig.backend.json tsconfig.json tsconfig.build.json ./ 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 corepack enable && corepack prepare --activate -USER node RUN printf "@deep-consulting-solutions:registry=https://npm.pkg.github.com/\nalways-auth=true\n//npm.pkg.github.com/:_authToken=%s" $TOKEN_FOR_PACKAGES >> .npmrc -RUN pnpm install --frozen-lockfile -RUN pnpm build -RUN rm -rf node_modules +RUN /usr/local/bin/pnpm install --frozen-lockfile +RUN /usr/local/bin/pnpm build + +# Delete all dev dependencies RUN jq 'del(.pnpm.patchedDependencies)' package.json > package.json.tmp; mv package.json.tmp package.json -RUN jq '{name: .name, version: .version}' packages/editor-ui/package.json > editor-ui.tmp; mv editor-ui.tmp packages/editor-ui/package.json -RUN jq '{name: .name, version: .version}' packages/design-system/package.json > design-system.tmp; mv design-system.tmp packages/design-system/package.json -RUN NODE_ENV=production pnpm install --prod --no-optional +RUN node scripts/trim-fe-packageJson.js + +# Delete any source code, source-mapping, or typings RUN find . -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" -o -name "tsconfig.json" -o -name "*.tsbuildinfo" | xargs rm -rf -RUN rm -rf packages/@n8n_io/eslint-config packages/editor-ui/src packages/editor-ui/node_modules packages/design-system -RUN rm -rf patches .npmrc *.yaml node_modules/.cache packages/**/node_modules/.cache packages/**/.turbo .config .cache .local .node /tmp/* +# Deploy the `n8n` package into /compiled +ENV NODE_ENV=production +RUN pnpm --filter=n8n --prod --no-optional deploy /compiled + +USER node # 2 . install packages FROM node:${NODE_VERSION}-slim as privatenodepackages ARG ACCESS_TOKEN @@ -34,15 +39,22 @@ ENV TOKEN_FOR_PACKAGES=${ACCESS_TOKEN} RUN cd /home/node && mkdir .n8n && cd .n8n && mkdir nodes && cd nodes WORKDIR /home/node/.n8n/nodes RUN printf "@deep-consulting-solutions:registry=https://npm.pkg.github.com/\nalways-auth=true\n//npm.pkg.github.com/:_authToken=%s" $TOKEN_FOR_PACKAGES > .npmrc -RUN npm install "@deep-consulting-solutions/n8n-nodes-dcs-crm" "@deep-consulting-solutions/n8n-nodes-dcs-noco-db" "@deep-consulting-solutions/n8n-nodes-dcs-s3" "@deep-consulting-solutions/n8n-nodes-esa-sendgrid" +RUN npm install "@deep-consulting-solutions/n8n-nodes-dcs-crm" "@deep-consulting-solutions/n8n-nodes-dcs-noco-db" "@deep-consulting-solutions/n8n-nodes-dcs-s3" "@deep-consulting-solutions/n8n-nodes-esa-sendgrid" "@deep-consulting-solutions/n8n-nodes-dcs-wait" # 3. Start with a new clean image with just the code that is needed to run n8n 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 +ENV NODE_ENV=production + +ARG N8N_RELEASE_TYPE=dev +ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE} + +WORKDIR /home/node +COPY --from=builder /compiled /usr/local/lib/node_modules/n8n COPY docker/images/n8n-custom/docker-entrypoint.sh / RUN \ + pnpm rebuild --dir /usr/local/lib/node_modules/n8n sqlite3 && \ + ln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n && \ mkdir .n8n && \ chown node:node .n8n diff --git a/docker/images/n8n-debian/Dockerfile b/docker/images/n8n-debian/Dockerfile deleted file mode 100644 index 8bc7cc982c3e8..0000000000000 --- a/docker/images/n8n-debian/Dockerfile +++ /dev/null @@ -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 diff --git a/docker/images/n8n-debian/README.md b/docker/images/n8n-debian/README.md deleted file mode 100644 index 236305e8ea104..0000000000000 --- a/docker/images/n8n-debian/README.md +++ /dev/null @@ -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= -t n8nio/n8n: . - -# 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 -``` diff --git a/docker/images/n8n-debian/docker-entrypoint.sh b/docker/images/n8n-debian/docker-entrypoint.sh deleted file mode 100755 index 80a252f31e643..0000000000000 --- a/docker/images/n8n-debian/docker-entrypoint.sh +++ /dev/null @@ -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 diff --git a/docker/images/n8n-rhel7/Dockerfile b/docker/images/n8n-rhel7/Dockerfile deleted file mode 100644 index 09fd0ad019c51..0000000000000 --- a/docker/images/n8n-rhel7/Dockerfile +++ /dev/null @@ -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" diff --git a/docker/images/n8n-rhel7/README.md b/docker/images/n8n-rhel7/README.md deleted file mode 100644 index 559aec3fa2b30..0000000000000 --- a/docker/images/n8n-rhel7/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## Build Docker-Image - -``` -docker build --build-arg N8N_VERSION= -t n8nio/n8n: . - -# 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 -``` diff --git a/docker/images/n8n/Dockerfile b/docker/images/n8n/Dockerfile index c85ef12a66b97..af7803820c0bb 100644 --- a/docker/images/n8n/Dockerfile +++ b/docker/images/n8n/Dockerfile @@ -1,4 +1,4 @@ -ARG NODE_VERSION=16 +ARG NODE_VERSION=18 FROM n8nio/base:${NODE_VERSION} ARG N8N_VERSION @@ -6,21 +6,21 @@ RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ENV N8N_VERSION=${N8N_VERSION} ENV NODE_ENV=production +ENV N8N_RELEASE_TYPE=stable RUN set -eux; \ - apkArch="$(apk --print-arch)"; \ - case "$apkArch" in \ - 'armv7') apk --no-cache add --virtual build-dependencies python3 build-base;; \ - esac && \ - npm install -g --omit=dev n8n@${N8N_VERSION} && \ - case "$apkArch" in \ - 'armv7') apk del build-dependencies;; \ - esac && \ - find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm && \ + npm install -g --omit=dev n8n@${N8N_VERSION} --ignore-scripts && \ + npm rebuild --prefix=/usr/local/lib/node_modules/n8n sqlite3 && \ + rm -rf /usr/local/lib/node_modules/n8n/node_modules/@n8n/chat && \ + rm -rf /usr/local/lib/node_modules/n8n/node_modules/n8n-design-system && \ + rm -rf /usr/local/lib/node_modules/n8n/node_modules/n8n-editor-ui/node_modules && \ + find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm -f && \ 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 +ENV SHELL /bin/sh +USER node ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"] diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 376c4494abb68..d65459615086e 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -2,27 +2,32 @@ # n8n - Workflow automation tool -n8n is an extendable workflow automation tool. With a [fair-code](http://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything. +n8n is an extendable workflow automation tool. With a [fair-code](https://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything. n8n.io - Screenshot ## 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) + - [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 @@ -57,7 +62,7 @@ You can then access n8n by opening: To be able to use webhooks which all triggers of external services like Github rely on n8n has to be reachable from the web. To make that easy n8n has a -special tunnel service (uses this code: [https://github.com/localtunnel/localtunnel](https://github.com/localtunnel/localtunnel)) which redirects requests from our servers to your local +special tunnel service (uses this code: [https://github.com/n8n-io/localtunnel](https://github.com/n8n-io/localtunnel)) which redirects requests from our servers to your local n8n instance. To use it simply start n8n with `--tunnel` @@ -71,25 +76,12 @@ 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= -N8N_BASIC_AUTH_PASSWORD= -``` - ## Persist data The workflow data gets by default saved in an SQLite database in the user folder (`/home/node/.n8n`). That folder also additionally contains the settings like webhook URL and encryption key. +Note that the folder needs to be writable by user with UID/GID 1000. ```bash docker run -it --rm \ @@ -102,10 +94,9 @@ docker run -it --rm \ ### Start with other Database By default n8n uses SQLite to save credentials, past executions and workflows. -n8n however also supports PostgresDB, MySQL and MariaDB. To use them simply a few -environment variables have to be set. +n8n however also supports PostgresDB. -It is important to still persist the data in the `/root/.n8n` folder. The reason +It is important to still persist the data in the `/home/node/.n8n` folder. The reason is that it contains n8n user data. That is the name of the webhook (in case) the n8n tunnel gets used and even more important the encryption key for the credentials. If none gets found n8n creates automatically one on @@ -135,43 +126,17 @@ docker run -it --rm \ -e DB_POSTGRESDB_SCHEMA= \ -e DB_POSTGRESDB_PASSWORD= \ -v ~/.n8n:/home/node/.n8n \ - docker.n8n.io/n8nio/n8n \ - n8n start + docker.n8n.io/n8nio/n8n ``` -A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withPostgres/README.md) - -#### Use with MySQL - -Replace the following placeholders with the actual data: - -- MYSQLDB_DATABASE -- MYSQLDB_HOST -- MYSQLDB_PASSWORD -- MYSQLDB_PORT -- MYSQLDB_USER - -```bash -docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - -e DB_TYPE=mysqldb \ - -e DB_MYSQLDB_DATABASE= \ - -e DB_MYSQLDB_HOST= \ - -e DB_MYSQLDB_PORT= \ - -e DB_MYSQLDB_USER= \ - -e DB_MYSQLDB_PASSWORD= \ - -v ~/.n8n:/home/node/.n8n \ - docker.n8n.io/n8nio/n8n \ - n8n start -``` +A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n-hosting/blob/main/docker-compose/withPostgres/README.md) ## Passing Sensitive Data via File 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 +146,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 +156,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: @@ -229,10 +191,10 @@ docker run -it --rm \ ## Build Docker-Image ```bash -docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg N8N_VERSION= -t n8n: . +docker buildx build --platform linux/amd64,linux/arm64 --build-arg N8N_VERSION= -t n8n: . # For example: -docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg N8N_VERSION=0.114.0 -t n8n:0.114.0 . +docker buildx build --platform linux/amd64,linux/arm64 --build-arg N8N_VERSION=1.30.1 -t n8n:1.30.1 . ``` ## What does n8n mean and how do you pronounce it? @@ -268,6 +230,6 @@ Before you upgrade to the latest version make sure to check here if there are an ## License -n8n is [fair-code](http://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). +n8n is [fair-code](https://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). Additional information about the license can be found in the [docs](https://docs.n8n.io/reference/license/). diff --git a/docker/images/n8n/docker-entrypoint.sh b/docker/images/n8n/docker-entrypoint.sh index fa81b3713ca54..63a7c1dca6380 100755 --- a/docker/images/n8n/docker-entrypoint.sh +++ b/docker/images/n8n/docker-entrypoint.sh @@ -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 "$@" + exec n8n "$@" else # Got started without arguments - exec su-exec node n8n + exec n8n fi diff --git a/docker/images/n8n/hooks/build b/docker/images/n8n/hooks/build deleted file mode 100644 index a09c622456a7e..0000000000000 --- a/docker/images/n8n/hooks/build +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -docker build --build-arg N8N_VERSION=$DOCKER_TAG -f $DOCKERFILE_PATH -t $IMAGE_NAME . diff --git a/jest.config.js b/jest.config.js index e34e01fa22e7c..f3f7824c14253 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,15 +1,17 @@ const { compilerOptions } = require('./tsconfig.json'); +/** @type {import('ts-jest').TsJestGlobalOptions} */ const tsJestOptions = { isolatedModules: true, tsconfig: { ...compilerOptions, declaration: false, sourceMap: true, - skipLibCheck: true, }, }; +const { baseUrl, paths } = require('get-tsconfig').getTsconfig().config?.compilerOptions; + /** @type {import('jest').Config} */ const config = { verbose: true, @@ -19,11 +21,17 @@ const config = { transform: { '^.+\\.ts$': ['ts-jest', tsJestOptions], }, - moduleNameMapper: { - '^@/(.*)$': '/src/$1', - }, - collectCoverage: true, - coverageReporters: [process.env.COVERAGE_REPORT === 'true' ? 'text' : 'text-summary'], + // This resolve the path mappings from the tsconfig relative to each jest.config.js + moduleNameMapper: Object.entries(paths || {}).reduce((acc, [path, [mapping]]) => { + path = `^${path.replace(/\/\*$/, '/(.*)$')}`; + mapping = mapping.replace(/^\.\/(?:(.*)\/)?\*$/, '$1'); + mapping = mapping ? `/${mapping}` : ''; + acc[path] = '' + (baseUrl ? `/${baseUrl.replace(/^\.\//, '')}` : '') + mapping + '/$1'; + return acc; + }, {}), + setupFilesAfterEnv: ['jest-expect-message'], + collectCoverage: process.env.COVERAGE_ENABLED === 'true', + coverageReporters: ['text-summary'], collectCoverageFrom: ['src/**/*.ts'], }; diff --git a/package.json b/package.json index 2ede2cf2b6144..914bf5aa35805 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,21 @@ { - "name": "n8n", - "version": "0.225.0", + "name": "n8n-monorepo", + "version": "1.41.0", "private": true, "homepage": "https://n8n.io", "engines": { - "node": ">=16.9", - "pnpm": ">=8.1" + "node": ">=18.10", + "pnpm": ">=8.14" }, - "packageManager": "pnpm@8.1.0", + "packageManager": "pnpm@8.14.3", "scripts": { "preinstall": "node scripts/block-npm-install.js", "build": "turbo run build", + "build:backend": "pnpm --filter=!@n8n/chat --filter=!n8n-design-system --filter=!n8n-editor-ui build", + "build:frontend": "pnpm --filter=@n8n/chat --filter=n8n-design-system --filter=n8n-editor-ui build", "typecheck": "turbo run typecheck", - "dev": "turbo run dev --parallel", + "dev": "turbo run dev --parallel --filter=!n8n-design-system --filter=!@n8n/chat", + "dev:ai": "turbo run dev --parallel --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core", "clean": "turbo run clean --parallel", "format": "turbo run format && node scripts/format.mjs", "lint": "turbo run lint", @@ -23,73 +26,95 @@ "start:tunnel": "./packages/cli/bin/n8n start --tunnel", "start:windows": "cd packages/cli/bin && n8n", "test": "turbo run test", - "watch": "turbo run watch", + "test:backend": "pnpm --filter=!@n8n/chat --filter=!n8n-design-system --filter=!n8n-editor-ui --filter=!n8n-nodes-base --filter=!@n8n/n8n-nodes-langchain test", + "test:nodes": "pnpm --filter=n8n-nodes-base --filter=@n8n/n8n-nodes-langchain test", + "test:frontend": "pnpm --filter=@n8n/chat --filter=n8n-design-system --filter=n8n-editor-ui test", + "watch": "turbo run watch --parallel", "webhook": "./packages/cli/bin/n8n webhook", "worker": "./packages/cli/bin/n8n worker", "cypress:install": "cypress install", "cypress:open": "CYPRESS_BASE_URL=http://localhost:8080 cypress open", - "test:e2e:ui": "cross-env E2E_TESTS=true start-server-and-test start http://localhost:5678/favicon.ico 'cypress open'", - "test:e2e:dev": "cross-env E2E_TESTS=true 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 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 start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'" + "test:e2e:ui": "scripts/run-e2e.js ui", + "test:e2e:dev": "scripts/run-e2e.js dev", + "test:e2e:all": "scripts/run-e2e.js all" }, "dependencies": { + "@deep-consulting-solutions/incident-handling": "^0.0.37", + "@types/lodash.set": "^4.3.9", + "@types/lodash.sortby": "^4.7.9", + "@types/openapi-to-postmanv2": "^3.2.4", + "@types/postman-collection": "^3.5.10", + "@types/qrcode": "^1.5.0", + "@types/request": "^2.48.12", + "@types/request-promise-native": "^1.0.21", + "@types/speakeasy": "^2.0.7", "@types/strftime": "^0.9.8", + "lodash.set": "^4.3.2", + "lodash.sortby": "^4.7.0", "n8n": "workspace:*", + "openapi-to-postmanv2": "^4.20.1", + "postman-collection": "^4.4.0", + "qrcode": "^1.5.3", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "speakeasy": "^2.0.0", "strftime": "^0.10.2" }, "devDependencies": { "@n8n_io/eslint-config": "workspace:*", - "@ngneat/falso": "^6.1.0", - "@types/jest": "^29.5.0", - "@types/supertest": "^2.0.12", + "@ngneat/falso": "^6.4.0", + "@types/jest": "^29.5.3", + "@types/supertest": "^6.0.2", + "@vitest/coverage-v8": "^1.2.1", "cross-env": "^7.0.3", - "cypress": "^12.8.1", - "cypress-real-events": "^1.7.6", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "jest-mock": "^29.5.0", + "cypress": "^13.6.2", + "cypress-otp": "^1.0.3", + "cypress-real-events": "^1.11.0", + "jest": "^29.6.2", + "jest-environment-jsdom": "^29.6.2", + "jest-expect-message": "^1.1.3", + "jest-mock": "^29.6.2", "jest-mock-extended": "^3.0.4", - "nock": "^13.2.9", - "node-fetch": "^2.6.7", + "nock": "^13.3.2", + "nodemon": "^3.0.1", "p-limit": "^3.1.0", - "prettier": "^2.8.3", - "rimraf": "^3.0.2", + "rimraf": "^5.0.1", "run-script-os": "^1.0.7", - "start-server-and-test": "^1.14.0", - "supertest": "^6.3.3", - "ts-jest": "^29.1.0", - "tsc-watch": "^6.0.0", - "turbo": "1.8.8", - "typescript": "*" + "start-server-and-test": "^2.0.3", + "supertest": "^6.3.4", + "ts-jest": "^29.1.1", + "tsc-alias": "^1.8.7", + "tsc-watch": "^6.0.4", + "turbo": "1.13.3", + "typescript": "*", + "vite": "^5.1.6", + "vite-plugin-checker": "^0.6.4", + "vitest": "^1.3.1", + "vue-tsc": "^2.0.6" }, "pnpm": { "onlyBuiltDependencies": [ - "sqlite3", - "vue-demi" + "sqlite3" ], "overrides": { - "@types/node": "^16.18.12", - "browserslist": "^4.21.4", + "@types/node": "^18.16.16", + "axios": "1.6.7", "chokidar": "3.5.2", - "decode-uri-component": "0.2.2", - "ejs": "^3.1.8", - "fork-ts-checker-webpack-plugin": "^6.0.4", - "http-cache-semantics": "4.1.1", - "jsonwebtoken": "9.0.0", - "prettier": "^2.8.3", - "tslib": "^2.5.0", - "ts-node": "^10.9.1", - "typescript": "^5.0.3", - "xml2js": "^0.5.0", - "cpy@8>globby": "^11.1.0", - "qqjs>globby": "^11.1.0" + "formidable": "3.5.1", + "prettier": "^3.2.5", + "semver": "^7.5.4", + "tslib": "^2.6.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.2" }, "patchedDependencies": { - "element-ui@2.15.12": "patches/element-ui@2.15.12.patch", "typedi@0.10.0": "patches/typedi@0.10.0.patch", "@sentry/cli@2.17.0": "patches/@sentry__cli@2.17.0.patch", - "@typescript-eslint/eslint-plugin@5.59.0": "patches/@typescript-eslint__eslint-plugin@5.59.0.patch" + "pkce-challenge@3.0.0": "patches/pkce-challenge@3.0.0.patch", + "pyodide@0.23.4": "patches/pyodide@0.23.4.patch", + "@types/express-serve-static-core@4.17.43": "patches/@types__express-serve-static-core@4.17.43.patch", + "@types/ws@8.5.4": "patches/@types__ws@8.5.4.patch", + "vite-plugin-checker@0.6.4": "patches/vite-plugin-checker@0.6.4.patch" } } } diff --git a/packages/@n8n/chat/.eslintignore b/packages/@n8n/chat/.eslintignore new file mode 100644 index 0000000000000..40a7b4122be25 --- /dev/null +++ b/packages/@n8n/chat/.eslintignore @@ -0,0 +1,2 @@ +.eslintrc.cjs +vitest.config.ts diff --git a/packages/@n8n/chat/.eslintrc.cjs b/packages/@n8n/chat/.eslintrc.cjs new file mode 100644 index 0000000000000..a1ad467ea3b92 --- /dev/null +++ b/packages/@n8n/chat/.eslintrc.cjs @@ -0,0 +1,10 @@ +const sharedOptions = require('@n8n_io/eslint-config/shared'); + +/** + * @type {import('@types/eslint').ESLint.ConfigData} + */ +module.exports = { + extends: ['@n8n_io/eslint-config/frontend'], + + ...sharedOptions(__dirname, 'frontend'), +}; diff --git a/packages/@n8n/chat/.gitignore b/packages/@n8n/chat/.gitignore new file mode 100644 index 0000000000000..38adffa64e830 --- /dev/null +++ b/packages/@n8n/chat/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/@n8n/chat/.np-config.json b/packages/@n8n/chat/.np-config.json new file mode 100644 index 0000000000000..5b1cb0a96ae65 --- /dev/null +++ b/packages/@n8n/chat/.np-config.json @@ -0,0 +1,5 @@ +{ + "yarn": false, + "tests": false, + "contents": "./dist" +} diff --git a/packages/@n8n/chat/.storybook/main.ts b/packages/@n8n/chat/.storybook/main.ts new file mode 100644 index 0000000000000..0636327e99ad6 --- /dev/null +++ b/packages/@n8n/chat/.storybook/main.ts @@ -0,0 +1,4 @@ +import { sharedConfig } from '@n8n/storybook/main'; + +const config = { ...sharedConfig }; +export default config; diff --git a/packages/@n8n/chat/.storybook/preview.scss b/packages/@n8n/chat/.storybook/preview.scss new file mode 100644 index 0000000000000..abaf406f8d257 --- /dev/null +++ b/packages/@n8n/chat/.storybook/preview.scss @@ -0,0 +1,4 @@ +html, body, #storybook-root, #n8n-chat { + width: 100%; + height: 100%; +} diff --git a/packages/@n8n/chat/.storybook/preview.ts b/packages/@n8n/chat/.storybook/preview.ts new file mode 100644 index 0000000000000..fbdb007564016 --- /dev/null +++ b/packages/@n8n/chat/.storybook/preview.ts @@ -0,0 +1,15 @@ +import type { Preview } from '@storybook/vue3'; +import './preview.scss'; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; diff --git a/packages/@n8n/chat/.vscode/extensions.json b/packages/@n8n/chat/.vscode/extensions.json new file mode 100644 index 0000000000000..c0a6e5a48110e --- /dev/null +++ b/packages/@n8n/chat/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/packages/@n8n/chat/LICENSE.md b/packages/@n8n/chat/LICENSE.md new file mode 100644 index 0000000000000..aab68b6d9301b --- /dev/null +++ b/packages/@n8n/chat/LICENSE.md @@ -0,0 +1,86 @@ +# License + +Portions of this software are licensed as follows: + +- Content of branches other than the main branch (i.e. "master") are not licensed. +- Source code files that contain ".ee." in their filename are NOT licensed under the Sustainable Use License. + To use source code files that contain ".ee." in their filename you must hold a valid n8n Enterprise License + specifically allowing you access to such source code files and as defined in "LICENSE_EE.md". +- All third party components incorporated into the n8n Software are licensed under the original license + provided by the owner of the applicable component. +- Content outside of the above mentioned files or restrictions is available under the "Sustainable Use + License" as defined below. + +## Sustainable Use License + +Version 1.0 + +### Acceptance + +By using the software, you agree to all of the terms and conditions below. + +### Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license +to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject +to the limitations below. + +### Limitations + +You may use or modify the software only for your own internal business purposes or for non-commercial or +personal use. You may distribute the software or provide it to others only if you do so free of charge for +non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of +the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. + +### Patents + +The licensor grants you a license, under any patent claims the licensor can license, or becomes able to +license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case +subject to the limitations and conditions in this license. This license does not cover any patent claims that +you cause to be infringed by modifications or additions to the software. If you or your company make any +written claim that the software infringes or contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If your company makes such a claim, your patent +license ends immediately for work on behalf of your company. + +### Notices + +You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these +terms. If you modify the software, you must include in any modified copies of the software a prominent notice +stating that you have modified the software. + +### No Other Rights + +These terms do not imply any licenses other than those expressly granted in these terms. + +### Termination + +If you use the software in violation of these terms, such use is not licensed, and your license will +automatically terminate. If the licensor provides you with a notice of your violation, and you cease all +violation of this license no later than 30 days after you receive that notice, your license will be reinstated +retroactively. However, if you violate these terms after such reinstatement, any additional violation of these +terms will cause your license to terminate automatically and permanently. + +### No Liability + +As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will +not be liable to you for any damages arising out of these terms or the use or nature of the software, under +any kind of legal claim. + +### Definitions + +The “licensor” is the entity offering these terms. + +The “software” is the software the licensor makes available under these terms, including any portion of it. + +“You” refers to the individual or entity agreeing to these terms. + +“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus +all organizations that have control over, are under the control of, or are under common control with that +organization. Control means ownership of substantially all the assets of an entity, or the power to direct its +management and policies by vote, contract, or otherwise. Control can be direct or indirect. + +“Your license” is the license granted to you for the software under these terms. + +“Use” means anything you do with the software requiring your license. + +“Trademark” means trademarks, service marks, and similar rights. diff --git a/packages/@n8n/chat/README.md b/packages/@n8n/chat/README.md new file mode 100644 index 0000000000000..8055e9677761a --- /dev/null +++ b/packages/@n8n/chat/README.md @@ -0,0 +1,269 @@ +# n8n Chat +This is an embeddable Chat widget for n8n. It allows the execution of AI-Powered Workflows through a Chat window. + +**Windowed Example** +![n8n Chat Windowed](https://raw.githubusercontent.com/n8n-io/n8n/master/packages/%40n8n/chat/resources/images/windowed.png) + +**Fullscreen Example** +![n8n Chat Fullscreen](https://raw.githubusercontent.com/n8n-io/n8n/master/packages/%40n8n/chat/resources/images/fullscreen.png) + +## Prerequisites +Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Chat Trigger** node. + +Open the **Chat Trigger** node and add your domain to the **Allowed Origins (CORS)** field. This makes sure that only requests from your domain are accepted. + +[See example workflow](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/chat/resources/workflow.json) + +> Make sure the workflow is **Active.** + +### How it works +Each Chat request is sent to the n8n Webhook endpoint, which then sends back a response. + +Each request is accompanied by an `action` query parameter, where `action` can be one of: +- `loadPreviousSession` - When the user opens the Chatbot again and the previous chat session should be loaded +- `sendMessage` - When the user sends a message + +## Installation + +Open the **Webhook** node and replace `YOUR_PRODUCTION_WEBHOOK_URL` with your production URL. This is the URL that the Chat widget will use to send requests to. + +### a. CDN Embed +Add the following code to your HTML page. + +```html + + +``` + +### b. Import Embed +Install and save n8n Chat as a production dependency. + +```sh +npm install @n8n/chat +``` + +Import the CSS and use the `createChat` function to initialize your Chat window. + +```ts +import '@n8n/chat/style.css'; +import { createChat } from '@n8n/chat'; + +createChat({ + webhookUrl: 'YOUR_PRODUCTION_WEBHOOK_URL' +}); +``` + +##### Vue.js + +```html + + +``` + +##### React + +```tsx +// App.tsx +import { useEffect } from 'react'; +import '@n8n/chat/style.css'; +import { createChat } from '@n8n/chat'; + +export const App = () => { + useEffect(() => { + createChat({ + webhookUrl: 'YOUR_PRODUCTION_WEBHOOK_URL' + }); + }, []); + + return (
); +}; +``` + +## Options +The default options are: + +```ts +createChat({ + webhookUrl: '', + webhookConfig: { + method: 'POST', + headers: {} + }, + target: '#n8n-chat', + mode: 'window', + chatInputKey: 'chatInput', + chatSessionKey: 'sessionId', + metadata: {}, + showWelcomeScreen: false, + defaultLanguage: 'en', + initialMessages: [ + 'Hi there! 👋', + 'My name is Nathan. How can I assist you today?' + ], + i18n: { + en: { + title: 'Hi there! 👋', + subtitle: "Start a chat. We're here to help you 24/7.", + footer: '', + getStarted: 'New Conversation', + inputPlaceholder: 'Type your question..', + }, + }, +}); +``` + +### `webhookUrl` +- **Type**: `string` +- **Required**: `true` +- **Examples**: + - `https://yourname.app.n8n.cloud/webhook/513107b3-6f3a-4a1e-af21-659f0ed14183` + - `http://localhost:5678/webhook/513107b3-6f3a-4a1e-af21-659f0ed14183` +- **Description**: The URL of the n8n Webhook endpoint. Should be the production URL. + +### `webhookConfig` +- **Type**: `{ method: string, headers: Record }` +- **Default**: `{ method: 'POST', headers: {} }` +- **Description**: The configuration for the Webhook request. + +### `target` +- **Type**: `string` +- **Default**: `'#n8n-chat'` +- **Description**: The CSS selector of the element where the Chat window should be embedded. + +### `mode` +- **Type**: `'window' | 'fullscreen'` +- **Default**: `'window'` +- **Description**: The render mode of the Chat window. + - In `window` mode, the Chat window will be embedded in the target element as a chat toggle button and a fixed size chat window. + - In `fullscreen` mode, the Chat will take up the entire width and height of its target container. + +### `showWelcomeScreen` +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Whether to show the welcome screen when the Chat window is opened. + +### `chatSessionKey` +- **Type**: `string` +- **Default**: `'sessionId'` +- **Description**: The key to use for sending the chat history session ID for the AI Memory node. + +### `chatInputKey` +- **Type**: `string` +- **Default**: `'chatInput'` +- **Description**: The key to use for sending the chat input for the AI Agent node. + +### `defaultLanguage` +- **Type**: `string` +- **Default**: `'en'` +- **Description**: The default language of the Chat window. Currently only `en` is supported. + +### `i18n` +- **Type**: `{ [key: string]: Record }` +- **Description**: The i18n configuration for the Chat window. Currently only `en` is supported. + +### `initialMessages` +- **Type**: `string[]` +- **Description**: The initial messages to be displayed in the Chat window. + +## Customization +The Chat window is entirely customizable using CSS variables. + +```css +:root { + --chat--color-primary: #e74266; + --chat--color-primary-shade-50: #db4061; + --chat--color-primary-shade-100: #cf3c5c; + --chat--color-secondary: #20b69e; + --chat--color-secondary-shade-50: #1ca08a; + --chat--color-white: #ffffff; + --chat--color-light: #f2f4f8; + --chat--color-light-shade-50: #e6e9f1; + --chat--color-light-shade-100: #c2c5cc; + --chat--color-medium: #d2d4d9; + --chat--color-dark: #101330; + --chat--color-disabled: #777980; + --chat--color-typing: #404040; + + --chat--spacing: 1rem; + --chat--border-radius: 0.25rem; + --chat--transition-duration: 0.15s; + + --chat--window--width: 400px; + --chat--window--height: 600px; + + --chat--header-height: auto; + --chat--header--padding: var(--chat--spacing); + --chat--header--background: var(--chat--color-dark); + --chat--header--color: var(--chat--color-light); + --chat--header--border-top: none; + --chat--header--border-bottom: none; + --chat--header--border-bottom: none; + --chat--header--border-bottom: none; + --chat--heading--font-size: 2em; + --chat--header--color: var(--chat--color-light); + --chat--subtitle--font-size: inherit; + --chat--subtitle--line-height: 1.8; + + --chat--textarea--height: 50px; + + --chat--message--font-size: 1rem; + --chat--message--padding: var(--chat--spacing); + --chat--message--border-radius: var(--chat--border-radius); + --chat--message-line-height: 1.8; + --chat--message--bot--background: var(--chat--color-white); + --chat--message--bot--color: var(--chat--color-dark); + --chat--message--bot--border: none; + --chat--message--user--background: var(--chat--color-secondary); + --chat--message--user--color: var(--chat--color-white); + --chat--message--user--border: none; + --chat--message--pre--background: rgba(0, 0, 0, 0.05); + + --chat--toggle--background: var(--chat--color-primary); + --chat--toggle--hover--background: var(--chat--color-primary-shade-50); + --chat--toggle--active--background: var(--chat--color-primary-shade-100); + --chat--toggle--color: var(--chat--color-white); + --chat--toggle--size: 64px; +} +``` + +## Caveats + +### Fullscreen mode +In fullscreen mode, the Chat window will take up the entire width and height of its target container. Make sure that the container has a set width and height. + +```css +html, +body, +#n8n-chat { + width: 100%; + height: 100%; +} +``` + +## License +n8n Chat is [fair-code](https://faircode.io) distributed under the +[**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). + +Proprietary licenses are available for enterprise customers. [Get in touch](mailto:license@n8n.io) + +Additional information about the license model can be found in the +[docs](https://docs.n8n.io/reference/license/). diff --git a/packages/@n8n/chat/build.config.js b/packages/@n8n/chat/build.config.js new file mode 100644 index 0000000000000..9d5910dbe778b --- /dev/null +++ b/packages/@n8n/chat/build.config.js @@ -0,0 +1,21 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + entries: [ + { + builder: 'mkdist', + format: 'esm', + input: './src', + outDir: './tmp/lib', + }, + { + builder: 'mkdist', + format: 'cjs', + input: './src', + outDir: './tmp/cjs', + }, + ], + clean: true, + declaration: true, + failOnWarn: false, +}); diff --git a/packages/@n8n/chat/env.d.ts b/packages/@n8n/chat/env.d.ts new file mode 100644 index 0000000000000..11f02fe2a0061 --- /dev/null +++ b/packages/@n8n/chat/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/@n8n/chat/index.html b/packages/@n8n/chat/index.html new file mode 100644 index 0000000000000..a888544898a50 --- /dev/null +++ b/packages/@n8n/chat/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/packages/@n8n/chat/package.json b/packages/@n8n/chat/package.json new file mode 100644 index 0000000000000..092987cf0e7d7 --- /dev/null +++ b/packages/@n8n/chat/package.json @@ -0,0 +1,63 @@ +{ + "name": "@n8n/chat", + "version": "0.14.0", + "scripts": { + "dev": "pnpm run storybook", + "build": "pnpm type-check && pnpm build:vite && pnpm run build:individual && npm run build:prepare", + "build:full": "pnpm type-check && pnpm build:vite && pnpm build:vite:full && pnpm run build:individual && npm run build:prepare", + "build:vite": "vite build", + "build:vite:full": "INCLUDE_VUE=true vite build", + "build:individual": "unbuild", + "build:prepare": "node scripts/postbuild.js", + "build:pack": "node scripts/pack.js", + "preview": "vite preview", + "test:dev": "vitest", + "test": "vitest run --coverage", + "type-check": "vue-tsc --noEmit -p tsconfig.json --composite false", + "lint": "eslint . --ext .js,.ts,.vue --quiet", + "lintfix": "eslint . --ext .js,.ts,.vue --fix", + "format": "prettier --write src/", + "storybook": "storybook dev -p 6006 --no-open", + "build:storybook": "storybook build", + "release": "pnpm run build:full && cd dist && pnpm publish" + }, + "main": "./chat.umd.js", + "module": "./chat.es.js", + "types": "./types/index.d.ts", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + }, + "./style.css": { + "import": "./style.css", + "require": "./style.css" + }, + "./*": { + "import": "./*", + "require": "./*" + } + }, + "dependencies": { + "highlight.js": "^11.8.0", + "markdown-it-link-attributes": "^4.0.1", + "uuid": "^8.3.2", + "vue": "^3.4.21", + "vue-markdown-render": "^2.1.1" + }, + "devDependencies": { + "@iconify-json/mdi": "^1.1.54", + "@n8n/storybook": "workspace:*", + "@types/markdown-it": "^12.2.3", + "shelljs": "^0.8.5", + "unbuild": "^2.0.0", + "unplugin-icons": "^0.17.0", + "vite-plugin-dts": "^3.7.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/n8n-io/n8n.git" + }, + "license": "SEE LICENSE IN LICENSE.md", + "homepage": "https://n8n.io" +} diff --git a/packages/@n8n/chat/public/favicon.ico b/packages/@n8n/chat/public/favicon.ico new file mode 100644 index 0000000000000..df36fcfb72584 Binary files /dev/null and b/packages/@n8n/chat/public/favicon.ico differ diff --git a/packages/@n8n/chat/resources/images/fullscreen.png b/packages/@n8n/chat/resources/images/fullscreen.png new file mode 100644 index 0000000000000..4c3f5aed4eb7e Binary files /dev/null and b/packages/@n8n/chat/resources/images/fullscreen.png differ diff --git a/packages/@n8n/chat/resources/images/windowed.png b/packages/@n8n/chat/resources/images/windowed.png new file mode 100644 index 0000000000000..1a598c823e57c Binary files /dev/null and b/packages/@n8n/chat/resources/images/windowed.png differ diff --git a/packages/@n8n/chat/resources/workflow-manual.json b/packages/@n8n/chat/resources/workflow-manual.json new file mode 100644 index 0000000000000..a21028b9fb6b6 --- /dev/null +++ b/packages/@n8n/chat/resources/workflow-manual.json @@ -0,0 +1,238 @@ +{ + "name": "Hosted n8n AI Chat Manual", + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "e6043748-44fc-4019-9301-5690fe26c614", + "name": "OpenAI Chat Model", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1, + "position": [ + 860, + 540 + ], + "credentials": { + "openAiApi": { + "id": "cIIkOhl7tUX1KsL6", + "name": "OpenAi account" + } + } + }, + { + "parameters": { + "sessionKey": "={{ $json.sessionId }}" + }, + "id": "0a68a59a-8ab6-4fa5-a1ea-b7f99a93109b", + "name": "Window Buffer Memory", + "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", + "typeVersion": 1, + "position": [ + 640, + 540 + ] + }, + { + "parameters": { + "text": "={{ $json.chatInput }}", + "options": {} + }, + "id": "3d4e0fbf-d761-4569-b02e-f5c1eeb830c8", + "name": "AI Agent", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 1.1, + "position": [ + 840, + 300 + ] + }, + { + "parameters": { + "dataType": "string", + "value1": "={{ $json.action }}", + "rules": { + "rules": [ + { + "value2": "loadPreviousSession", + "outputKey": "loadPreviousSession" + }, + { + "value2": "sendMessage", + "outputKey": "sendMessage" + } + ] + } + }, + "id": "84213c7b-abc7-4f40-9567-cd3484a4ae6b", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 2, + "position": [ + 300, + 280 + ] + }, + { + "parameters": { + "simplifyOutput": false + }, + "id": "3be7f076-98ed-472a-80b6-bf8d9538ac87", + "name": "Chat Messages Retriever", + "type": "@n8n/n8n-nodes-langchain.memoryChatRetriever", + "typeVersion": 1, + "position": [ + 620, + 140 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "3417c644-8a91-4524-974a-45b4a46d0e2e", + "name": "Respond to Webhook", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 1240, + 140 + ] + }, + { + "parameters": { + "public": true, + "authentication": "n8nUserAuth", + "options": { + "loadPreviousSession": "manually", + "responseMode": "responseNode" + } + }, + "id": "1b30c239-a819-45b4-b0ae-bdd5b92a5424", + "name": "Chat Trigger", + "type": "@n8n/n8n-nodes-langchain.chatTrigger", + "typeVersion": 1, + "position": [ + 80, + 280 + ], + "webhookId": "ed3dea26-7d68-42b3-9032-98fe967d441d" + }, + { + "parameters": { + "aggregate": "aggregateAllItemData", + "options": {} + }, + "id": "79672cf0-686b-41eb-90ae-fd31b6da837d", + "name": "Aggregate", + "type": "n8n-nodes-base.aggregate", + "typeVersion": 1, + "position": [ + 1000, + 140 + ] + } + ], + "pinData": {}, + "connections": { + "OpenAI Chat Model": { + "ai_languageModel": [ + [ + { + "node": "AI Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Window Buffer Memory": { + "ai_memory": [ + [ + { + "node": "AI Agent", + "type": "ai_memory", + "index": 0 + }, + { + "node": "Chat Messages Retriever", + "type": "ai_memory", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "Chat Messages Retriever", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "AI Agent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Chat Messages Retriever": { + "main": [ + [ + { + "node": "Aggregate", + "type": "main", + "index": 0 + } + ] + ] + }, + "AI Agent": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Chat Trigger": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Aggregate": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "425c0efe-3aa0-4e0e-8c06-abe12234b1fd", + "id": "1569HF92Y02EUtsU", + "meta": { + "instanceId": "374b43d8b8d6299cc777811a4ad220fc688ee2d54a308cfb0de4450a5233ca9e" + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/@n8n/chat/resources/workflow.json b/packages/@n8n/chat/resources/workflow.json new file mode 100644 index 0000000000000..1bf4be681bc11 --- /dev/null +++ b/packages/@n8n/chat/resources/workflow.json @@ -0,0 +1,119 @@ +{ + "name": "Hosted n8n AI Chat", + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "4c109d13-62a2-4e23-9979-e50201db743d", + "name": "OpenAI Chat Model", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1, + "position": [ + 640, + 540 + ], + "credentials": { + "openAiApi": { + "id": "cIIkOhl7tUX1KsL6", + "name": "OpenAi account" + } + } + }, + { + "parameters": { + "sessionKey": "={{ $json.sessionId }}" + }, + "id": "b416df7b-4802-462f-8f74-f0a71dc4c0be", + "name": "Window Buffer Memory", + "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", + "typeVersion": 1, + "position": [ + 340, + 540 + ] + }, + { + "parameters": { + "text": "={{ $json.chatInput }}", + "options": {} + }, + "id": "4de25807-a2ef-4453-900e-e00e0021ecdc", + "name": "AI Agent", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 1.1, + "position": [ + 620, + 300 + ] + }, + { + "parameters": { + "public": true, + "options": { + "loadPreviousSession": "memory" + } + }, + "id": "5a9612ae-51c1-4be2-bd8b-8556872d1149", + "name": "Chat Trigger", + "type": "@n8n/n8n-nodes-langchain.chatTrigger", + "typeVersion": 1, + "position": [ + 340, + 300 + ], + "webhookId": "f406671e-c954-4691-b39a-66c90aa2f103" + } + ], + "pinData": {}, + "connections": { + "OpenAI Chat Model": { + "ai_languageModel": [ + [ + { + "node": "AI Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Window Buffer Memory": { + "ai_memory": [ + [ + { + "node": "AI Agent", + "type": "ai_memory", + "index": 0 + }, + { + "node": "Chat Trigger", + "type": "ai_memory", + "index": 0 + } + ] + ] + }, + "Chat Trigger": { + "main": [ + [ + { + "node": "AI Agent", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "6076136f-fdb4-48d9-b483-d1c24c95ef9e", + "id": "zaBHnDtj22BzEQ6K", + "meta": { + "instanceId": "374b43d8b8d6299cc777811a4ad220fc688ee2d54a308cfb0de4450a5233ca9e" + }, + "tags": [] +} diff --git a/packages/@n8n/chat/scripts/pack.js b/packages/@n8n/chat/scripts/pack.js new file mode 100644 index 0000000000000..0dffb892e4e33 --- /dev/null +++ b/packages/@n8n/chat/scripts/pack.js @@ -0,0 +1,11 @@ +const path = require('path'); +const shelljs = require('shelljs'); + +const rootDirPath = path.resolve(__dirname, '..'); +const distDirPath = path.resolve(rootDirPath, 'dist'); + +shelljs.cd(rootDirPath); +shelljs.exec('npm run build'); + +shelljs.cd(distDirPath); +shelljs.exec('npm pack'); diff --git a/packages/@n8n/chat/scripts/postbuild.js b/packages/@n8n/chat/scripts/postbuild.js new file mode 100644 index 0000000000000..2ce6a4e8c28be --- /dev/null +++ b/packages/@n8n/chat/scripts/postbuild.js @@ -0,0 +1,36 @@ +const path = require('path'); +const shelljs = require('shelljs'); +const glob = require('fast-glob'); + +const rootDirPath = path.resolve(__dirname, '..'); +const n8nRootDirPath = path.resolve(rootDirPath, '..', '..', '..'); +const distDirPath = path.resolve(rootDirPath, 'dist'); +const srcDirPath = path.resolve(rootDirPath, 'src'); +const libDirPath = path.resolve(rootDirPath, 'tmp', 'lib'); +const cjsDirPath = path.resolve(rootDirPath, 'tmp', 'cjs'); + +const packageJsonFilePath = path.resolve(rootDirPath, 'package.json'); +const readmeFilePath = path.resolve(rootDirPath, 'README.md'); +const licenseFilePath = path.resolve(n8nRootDirPath, 'LICENSE.md'); + +shelljs.cp(packageJsonFilePath, distDirPath); +shelljs.cp(readmeFilePath, distDirPath); +shelljs.cp(licenseFilePath, distDirPath); + +shelljs.mv(path.resolve(distDirPath, 'src'), path.resolve(distDirPath, 'types')); + +function moveFiles(files, from, to) { + files.forEach((file) => { + const toFile = file.replace(from, to); + shelljs.mkdir('-p', path.dirname(toFile)); + shelljs.mv(file, toFile); + }); +} + +const cjsFiles = glob.sync(path.resolve(cjsDirPath, '**', '*')); +moveFiles(cjsFiles, 'tmp/cjs', 'dist'); +shelljs.rm('-rf', cjsDirPath); + +const libFiles = glob.sync(path.resolve(libDirPath, '**/*')); +moveFiles(libFiles, 'tmp/lib', 'dist'); +shelljs.rm('-rf', libDirPath); diff --git a/packages/@n8n/chat/src/App.vue b/packages/@n8n/chat/src/App.vue new file mode 100644 index 0000000000000..ec90bef42583e --- /dev/null +++ b/packages/@n8n/chat/src/App.vue @@ -0,0 +1,23 @@ + + diff --git a/packages/@n8n/chat/src/__stories__/App.stories.ts b/packages/@n8n/chat/src/__stories__/App.stories.ts new file mode 100644 index 0000000000000..ca93cdb240d1c --- /dev/null +++ b/packages/@n8n/chat/src/__stories__/App.stories.ts @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { StoryObj } from '@storybook/vue3'; +import { onMounted } from 'vue'; +import type { ChatOptions } from '@n8n/chat/types'; +import { createChat } from '@n8n/chat/index'; + +const webhookUrl = 'http://localhost:5678/webhook/f406671e-c954-4691-b39a-66c90aa2f103/chat'; + +const meta = { + title: 'Chat', + render: (args: Partial) => ({ + setup() { + onMounted(() => { + createChat(args); + }); + + return {}; + }, + template: '
', + }), + parameters: { + layout: 'fullscreen', + }, + tags: ['autodocs'], +}; + +// eslint-disable-next-line import/no-default-export +export default meta; +type Story = StoryObj; + +export const Fullscreen: Story = { + args: { + webhookUrl, + mode: 'fullscreen', + } satisfies Partial, +}; + +export const Windowed: Story = { + args: { + webhookUrl, + mode: 'window', + } satisfies Partial, +}; diff --git a/packages/@n8n/chat/src/__tests__/index.spec.ts b/packages/@n8n/chat/src/__tests__/index.spec.ts new file mode 100644 index 0000000000000..5307880d67fa5 --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/index.spec.ts @@ -0,0 +1,218 @@ +import { fireEvent, waitFor } from '@testing-library/vue'; +import { + createFetchResponse, + createGetLatestMessagesResponse, + createSendMessageResponse, + getChatInputSendButton, + getChatInputTextarea, + getChatMessage, + getChatMessageByText, + getChatMessages, + getChatMessageTyping, + getChatWindowToggle, + getChatWindowWrapper, + getChatWrapper, + getGetStartedButton, + getMountingTarget, +} from '@n8n/chat/__tests__/utils'; +import { createChat } from '@n8n/chat/index'; + +describe('createChat()', () => { + let app: ReturnType; + + afterEach(() => { + vi.clearAllMocks(); + + app.unmount(); + }); + + describe('mode', () => { + it('should create fullscreen chat app with default options', () => { + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse())); + + app = createChat({ + mode: 'fullscreen', + }); + + expect(getMountingTarget()).toBeVisible(); + expect(getChatWrapper()).toBeVisible(); + expect(getChatWindowWrapper()).not.toBeInTheDocument(); + }); + + it('should create window chat app with default options', () => { + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse())); + + app = createChat({ + mode: 'window', + }); + + expect(getMountingTarget()).toBeDefined(); + expect(getChatWindowWrapper()).toBeVisible(); + expect(getChatWrapper()).not.toBeVisible(); + }); + + it('should open window chat app using toggle button', async () => { + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse())); + + app = createChat(); + + expect(getMountingTarget()).toBeVisible(); + expect(getChatWindowWrapper()).toBeVisible(); + + const trigger = getChatWindowToggle(); + await fireEvent.click(trigger as HTMLElement); + + expect(getChatWrapper()).toBeVisible(); + }); + }); + + describe('loadPreviousMessages', () => { + it('should load previous messages on mount', async () => { + const fetchSpy = vi.spyOn(global, 'fetch'); + fetchSpy.mockImplementation(createFetchResponse(createGetLatestMessagesResponse())); + + app = createChat({ + mode: 'fullscreen', + showWelcomeScreen: true, + }); + + const getStartedButton = getGetStartedButton(); + await fireEvent.click(getStartedButton as HTMLElement); + + expect(fetchSpy.mock.calls[0][1]).toEqual( + expect.objectContaining({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: expect.stringContaining('"action":"loadPreviousSession"') as unknown, + mode: 'cors', + cache: 'no-cache', + }), + ); + }); + }); + + describe('initialMessages', () => { + it.each(['fullscreen', 'window'] as Array<'fullscreen' | 'window'>)( + 'should show initial default messages in %s mode', + async (mode) => { + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse())); + + const initialMessages = ['Hello tester!', 'How are you?']; + app = createChat({ + mode, + initialMessages, + }); + + if (mode === 'window') { + const trigger = getChatWindowToggle(); + await fireEvent.click(trigger as HTMLElement); + } + + expect(getChatMessages().length).toBe(initialMessages.length); + expect(getChatMessageByText(initialMessages[0])).toBeInTheDocument(); + expect(getChatMessageByText(initialMessages[1])).toBeInTheDocument(); + }, + ); + }); + + describe('sendMessage', () => { + it.each(['window', 'fullscreen'] as Array<'fullscreen' | 'window'>)( + 'should send a message and render a text message in %s mode', + async (mode) => { + const input = 'Hello User World!'; + const output = 'Hello Bot World!'; + + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy + .mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse)) + .mockImplementationOnce(createFetchResponse(createSendMessageResponse(output))); + + app = createChat({ + mode, + }); + + if (mode === 'window') { + const trigger = getChatWindowToggle(); + await fireEvent.click(trigger as HTMLElement); + } + + expect(getChatMessageTyping()).not.toBeInTheDocument(); + expect(getChatMessages().length).toBe(2); + + await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument()); + + const textarea = getChatInputTextarea(); + const sendButton = getChatInputSendButton(); + await fireEvent.update(textarea as HTMLElement, input); + expect(sendButton).not.toBeDisabled(); + await fireEvent.click(sendButton as HTMLElement); + + expect(fetchSpy.mock.calls[1][1]).toEqual( + expect.objectContaining({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: expect.stringMatching(/"action":"sendMessage"/) as unknown, + mode: 'cors', + cache: 'no-cache', + }), + ); + expect(fetchSpy.mock.calls[1][1]?.body).toContain(`"${input}"`); + + expect(getChatMessages().length).toBe(3); + expect(getChatMessageByText(input)).toBeInTheDocument(); + expect(getChatMessageTyping()).toBeVisible(); + + await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument()); + expect(getChatMessageByText(output)).toBeInTheDocument(); + }, + ); + + it.each(['fullscreen', 'window'] as Array<'fullscreen' | 'window'>)( + 'should send a message and render a code markdown message in %s mode', + async (mode) => { + const input = 'Teach me javascript!'; + const output = '# Code\n```js\nconsole.log("Hello World!");\n```'; + + const fetchSpy = vi.spyOn(window, 'fetch'); + fetchSpy + .mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse)) + .mockImplementationOnce(createFetchResponse(createSendMessageResponse(output))); + + app = createChat({ + mode, + }); + + if (mode === 'window') { + const trigger = getChatWindowToggle(); + await fireEvent.click(trigger as HTMLElement); + } + + await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument()); + + const textarea = getChatInputTextarea(); + const sendButton = getChatInputSendButton(); + await fireEvent.update(textarea as HTMLElement, input); + await fireEvent.click(sendButton as HTMLElement); + + expect(getChatMessageByText(input)).toBeInTheDocument(); + expect(getChatMessages().length).toBe(3); + + await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument()); + + const lastMessage = getChatMessage(-1); + expect(lastMessage).toBeInTheDocument(); + + expect(lastMessage.querySelector('h1')).toHaveTextContent('Code'); + expect(lastMessage.querySelector('code')).toHaveTextContent('console.log("Hello World!");'); + }, + ); + }); +}); diff --git a/packages/@n8n/chat/src/__tests__/setup.ts b/packages/@n8n/chat/src/__tests__/setup.ts new file mode 100644 index 0000000000000..7b0828bfa80fb --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/packages/@n8n/chat/src/__tests__/utils/create.ts b/packages/@n8n/chat/src/__tests__/utils/create.ts new file mode 100644 index 0000000000000..db34921acd489 --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/utils/create.ts @@ -0,0 +1,16 @@ +import { createChat } from '@n8n/chat/index'; + +export function createTestChat(options: Parameters[0] = {}): { + unmount: () => void; + container: Element; +} { + const app = createChat(options); + + const container = app._container as Element; + const unmount = () => app.unmount(); + + return { + unmount, + container, + }; +} diff --git a/packages/@n8n/chat/src/__tests__/utils/fetch.ts b/packages/@n8n/chat/src/__tests__/utils/fetch.ts new file mode 100644 index 0000000000000..6cf31c7d0d0c1 --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/utils/fetch.ts @@ -0,0 +1,18 @@ +import type { LoadPreviousSessionResponse, SendMessageResponse } from '@n8n/chat/types'; + +export function createFetchResponse(data: T) { + return async () => + ({ + json: async () => await new Promise((resolve) => resolve(data)), + }) as Response; +} + +export const createGetLatestMessagesResponse = ( + data: LoadPreviousSessionResponse['data'] = [], +): LoadPreviousSessionResponse => ({ data }); + +export const createSendMessageResponse = ( + output: SendMessageResponse['output'], +): SendMessageResponse => ({ + output, +}); diff --git a/packages/@n8n/chat/src/__tests__/utils/index.ts b/packages/@n8n/chat/src/__tests__/utils/index.ts new file mode 100644 index 0000000000000..6dc234f1d0b94 --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/utils/index.ts @@ -0,0 +1,3 @@ +export * from './create'; +export * from './fetch'; +export * from './selectors'; diff --git a/packages/@n8n/chat/src/__tests__/utils/selectors.ts b/packages/@n8n/chat/src/__tests__/utils/selectors.ts new file mode 100644 index 0000000000000..a510652b0d863 --- /dev/null +++ b/packages/@n8n/chat/src/__tests__/utils/selectors.ts @@ -0,0 +1,53 @@ +import { screen } from '@testing-library/vue'; +import { defaultMountingTarget } from '@n8n/chat/constants'; + +export function getMountingTarget(target = defaultMountingTarget) { + return document.querySelector(target); +} + +export function getChatWindowWrapper() { + return document.querySelector('.chat-window-wrapper'); +} + +export function getChatWindowToggle() { + return document.querySelector('.chat-window-toggle'); +} + +export function getChatWrapper() { + return document.querySelector('.chat-wrapper'); +} + +export function getChatMessages() { + return document.querySelectorAll('.chat-message:not(.chat-message-typing)'); +} + +export function getChatMessage(index: number) { + const messages = getChatMessages(); + return index < 0 ? messages[messages.length + index] : messages[index]; +} + +export function getChatMessageByText(text: string) { + return screen.queryByText(text, { + selector: '.chat-message:not(.chat-message-typing) .chat-message-markdown p', + }); +} + +export function getChatMessageTyping() { + return document.querySelector('.chat-message-typing'); +} + +export function getGetStartedButton() { + return document.querySelector('.chat-get-started .chat-button'); +} + +export function getChatInput() { + return document.querySelector('.chat-input'); +} + +export function getChatInputTextarea() { + return document.querySelector('.chat-input textarea'); +} + +export function getChatInputSendButton() { + return document.querySelector('.chat-input .chat-input-send-button'); +} diff --git a/packages/@n8n/chat/src/api/generic.ts b/packages/@n8n/chat/src/api/generic.ts new file mode 100644 index 0000000000000..04b6d61b65034 --- /dev/null +++ b/packages/@n8n/chat/src/api/generic.ts @@ -0,0 +1,63 @@ +async function getAccessToken() { + return ''; +} + +export async function authenticatedFetch(...args: Parameters): Promise { + const accessToken = await getAccessToken(); + + const response = await fetch(args[0], { + ...args[1], + mode: 'cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}), + ...args[1]?.headers, + }, + }); + + return (await response.json()) as T; +} + +export async function get(url: string, query: object = {}, options: RequestInit = {}) { + let resolvedUrl = url; + if (Object.keys(query).length > 0) { + resolvedUrl = `${resolvedUrl}?${new URLSearchParams( + query as Record, + ).toString()}`; + } + + return await authenticatedFetch(resolvedUrl, { ...options, method: 'GET' }); +} + +export async function post(url: string, body: object = {}, options: RequestInit = {}) { + return await authenticatedFetch(url, { + ...options, + method: 'POST', + body: JSON.stringify(body), + }); +} + +export async function put(url: string, body: object = {}, options: RequestInit = {}) { + return await authenticatedFetch(url, { + ...options, + method: 'PUT', + body: JSON.stringify(body), + }); +} + +export async function patch(url: string, body: object = {}, options: RequestInit = {}) { + return await authenticatedFetch(url, { + ...options, + method: 'PATCH', + body: JSON.stringify(body), + }); +} + +export async function del(url: string, body: object = {}, options: RequestInit = {}) { + return await authenticatedFetch(url, { + ...options, + method: 'DELETE', + body: JSON.stringify(body), + }); +} diff --git a/packages/@n8n/chat/src/api/index.ts b/packages/@n8n/chat/src/api/index.ts new file mode 100644 index 0000000000000..a78afb92f2fcd --- /dev/null +++ b/packages/@n8n/chat/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './generic'; +export * from './message'; diff --git a/packages/@n8n/chat/src/api/message.ts b/packages/@n8n/chat/src/api/message.ts new file mode 100644 index 0000000000000..72f8e2fb2741c --- /dev/null +++ b/packages/@n8n/chat/src/api/message.ts @@ -0,0 +1,37 @@ +import { get, post } from '@n8n/chat/api/generic'; +import type { + ChatOptions, + LoadPreviousSessionResponse, + SendMessageResponse, +} from '@n8n/chat/types'; + +export async function loadPreviousSession(sessionId: string, options: ChatOptions) { + const method = options.webhookConfig?.method === 'POST' ? post : get; + return await method( + `${options.webhookUrl}`, + { + action: 'loadPreviousSession', + [options.chatSessionKey as string]: sessionId, + ...(options.metadata ? { metadata: options.metadata } : {}), + }, + { + headers: options.webhookConfig?.headers, + }, + ); +} + +export async function sendMessage(message: string, sessionId: string, options: ChatOptions) { + const method = options.webhookConfig?.method === 'POST' ? post : get; + return await method( + `${options.webhookUrl}`, + { + action: 'sendMessage', + [options.chatSessionKey as string]: sessionId, + [options.chatInputKey as string]: message, + ...(options.metadata ? { metadata: options.metadata } : {}), + }, + { + headers: options.webhookConfig?.headers, + }, + ); +} diff --git a/packages/@n8n/chat/src/components/Button.vue b/packages/@n8n/chat/src/components/Button.vue new file mode 100644 index 0000000000000..ca35153fc6b96 --- /dev/null +++ b/packages/@n8n/chat/src/components/Button.vue @@ -0,0 +1,41 @@ + + diff --git a/packages/@n8n/chat/src/components/Chat.vue b/packages/@n8n/chat/src/components/Chat.vue new file mode 100644 index 0000000000000..c6b0a08618fab --- /dev/null +++ b/packages/@n8n/chat/src/components/Chat.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/packages/@n8n/chat/src/components/ChatWindow.vue b/packages/@n8n/chat/src/components/ChatWindow.vue new file mode 100644 index 0000000000000..1d1f7ae3c22f2 --- /dev/null +++ b/packages/@n8n/chat/src/components/ChatWindow.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/packages/@n8n/chat/src/components/GetStarted.vue b/packages/@n8n/chat/src/components/GetStarted.vue new file mode 100644 index 0000000000000..7c1a6aea2b092 --- /dev/null +++ b/packages/@n8n/chat/src/components/GetStarted.vue @@ -0,0 +1,24 @@ + + + + diff --git a/packages/@n8n/chat/src/components/GetStartedFooter.vue b/packages/@n8n/chat/src/components/GetStartedFooter.vue new file mode 100644 index 0000000000000..dca1d16bc6b9f --- /dev/null +++ b/packages/@n8n/chat/src/components/GetStartedFooter.vue @@ -0,0 +1,20 @@ + + + + diff --git a/packages/@n8n/chat/src/components/Input.vue b/packages/@n8n/chat/src/components/Input.vue new file mode 100644 index 0000000000000..0f8746369019a --- /dev/null +++ b/packages/@n8n/chat/src/components/Input.vue @@ -0,0 +1,109 @@ + + +