diff --git a/.github/workflows/test-workflows.yml b/.github/workflows/test-workflows.yml index 2bb91dd065..90a69438ab 100644 --- a/.github/workflows/test-workflows.yml +++ b/.github/workflows/test-workflows.yml @@ -4,18 +4,70 @@ on: schedule: - cron: '0 2 * * *' workflow_dispatch: + pull_request: + paths: + - packages/core/package.json + - packages/nodes-base/package.json + - packages/@n8n/nodes-langchain/package.json + - .github/workflows/test-workflows.yml + pull_request_review: + types: [submitted] jobs: - run-test-workflows: + build: + name: Install & Build runs-on: ubuntu-latest - - timeout-minutes: 30 - + if: github.event_name != 'pull_request_review' || startsWith(github.event.pull_request.base.ref, 'release/') steps: - - name: Checkout - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.1 + - run: corepack enable + - uses: actions/setup-node@v4.0.2 with: - path: n8n + node-version: 20.x + cache: 'pnpm' + - run: pnpm install --frozen-lockfile + + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + + - name: Build Backend + run: pnpm build:backend + + - name: Cache build artifacts + uses: actions/cache/save@v4.0.0 + with: + path: ./packages/**/dist + key: ${{ github.sha }}:workflow-tests + + run-test-workflows: + name: Workflow Tests + runs-on: ubuntu-latest + needs: build + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4.1.1 + - run: corepack enable + - uses: actions/setup-node@v4.0.2 + with: + node-version: 20.x + cache: 'pnpm' + - run: pnpm install --frozen-lockfile + + - name: Setup build cache + uses: rharkor/caching-for-turbo@v1.5 + + - name: Restore cached build artifacts + uses: actions/cache/restore@v4.0.0 + with: + path: ./packages/**/dist + key: ${{ github.sha }}:workflow-tests + + - name: Install OS dependencies + run: | + sudo apt update -y + echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections + echo 'tzdata tzdata/Zones/Europe select Paris' | sudo debconf-set-selections + DEBIAN_FRONTEND="noninteractive" sudo apt-get install -y graphicsmagick - name: Checkout workflows repo uses: actions/checkout@v4.1.1 @@ -23,77 +75,34 @@ jobs: repository: n8n-io/test-workflows path: test-workflows - - run: corepack enable - working-directory: n8n - - - uses: actions/setup-node@v4.0.2 - with: - node-version: 20.x - cache: 'pnpm' - cache-dependency-path: 'n8n/pnpm-lock.yaml' - - - name: Install dependencies - run: | - sudo apt update -y - echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections - echo 'tzdata tzdata/Zones/Europe select Paris' | sudo debconf-set-selections - DEBIAN_FRONTEND="noninteractive" sudo apt-get install -y graphicsmagick - shell: bash - - - name: pnpm install and build - working-directory: n8n - run: | - pnpm install --frozen-lockfile - pnpm build:backend - shell: bash - - name: Import credentials - run: n8n/packages/cli/bin/n8n import:credentials --input=test-workflows/credentials.json - shell: bash + run: packages/cli/bin/n8n import:credentials --input=test-workflows/credentials.json env: N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} - name: Import workflows - run: n8n/packages/cli/bin/n8n import:workflow --separate --input=test-workflows/workflows - shell: bash + run: packages/cli/bin/n8n import:workflow --separate --input=test-workflows/workflows env: N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} - name: Copy static assets run: | - cp n8n/assets/n8n-logo.png /tmp/n8n-logo.png - cp n8n/assets/n8n-screenshot.png /tmp/n8n-screenshot.png + cp assets/n8n-logo.png /tmp/n8n-logo.png + cp assets/n8n-screenshot.png /tmp/n8n-screenshot.png cp test-workflows/testData/pdfs/*.pdf /tmp/ - shell: bash - name: Run tests - run: n8n/packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --githubWorkflow --shortOutput --concurrency=16 --compare=test-workflows/snapshots - shell: bash + run: packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --githubWorkflow --shortOutput --concurrency=16 --compare=test-workflows/snapshots id: tests env: N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} SKIP_STATISTICS_EVENTS: true DB_SQLITE_POOL_SIZE: 4 - # - - # name: Export credentials - # if: always() - # run: n8n/packages/cli/bin/n8n export:credentials --output=test-workflows/credentials.json --all --pretty - # shell: bash - # env: - # N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} - # - - # name: Commit and push credential changes - # if: always() - # run: | - # cd test-workflows - # git config --global user.name 'n8n test bot' - # git config --global user.email 'n8n-test-bot@users.noreply.github.com' - # git commit -am "Automated credential update" - # git push --force --quiet "https://janober:${{ secrets.TOKEN }}@github.com/n8n-io/test-workflows.git" main:main + N8N_SENTRY_DSN: ${{secrets.CI_SENTRY_DSN}} - name: Notify Slack on failure uses: act10ns/slack@v2.0.0 - if: failure() + if: failure() && github.ref == 'refs/heads/master' with: status: ${{ job.status }} channel: '#alerts-build' diff --git a/CHANGELOG.md b/CHANGELOG.md index 32def33aa7..e7d455c95d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,88 @@ +# [1.71.0](https://github.com/n8n-io/n8n/compare/n8n@1.70.0...n8n@1.71.0) (2024-12-04) + + +### Bug Fixes + +* **core:** Fix push for waiting executions ([#11984](https://github.com/n8n-io/n8n/issues/11984)) ([8d71307](https://github.com/n8n-io/n8n/commit/8d71307da0398e7e39bf53e8e1cfa21ac1ceaf69)) +* **core:** Improve header parameter parsing on http client responses ([#11953](https://github.com/n8n-io/n8n/issues/11953)) ([41e9e39](https://github.com/n8n-io/n8n/commit/41e9e39b5b53ecd9d8d1b385df65a26ecb9bccd8)) +* **core:** Opt-out from optimizations if `$item` is used ([#12036](https://github.com/n8n-io/n8n/issues/12036)) ([872535a](https://github.com/n8n-io/n8n/commit/872535a40c85dcfad3a4b27c57c026ae003f562f)) +* **core:** Use the configured timezone in task runner ([#12032](https://github.com/n8n-io/n8n/issues/12032)) ([2e6845a](https://github.com/n8n-io/n8n/commit/2e6845afcbc30dff73c3f3f15f21278cab397387)) +* **core:** Validate node name when creating `NodeOperationErrror` ([#11999](https://github.com/n8n-io/n8n/issues/11999)) ([e68c9da](https://github.com/n8n-io/n8n/commit/e68c9da30c31cd5f994cb01ce759192562bfbd40)) +* **editor:** Add execution concurrency info and paywall ([#11847](https://github.com/n8n-io/n8n/issues/11847)) ([57d3269](https://github.com/n8n-io/n8n/commit/57d3269e400ee4e7e3636614870ebdfdb0aa8c1d)) +* **editor:** Fix bug causing connection lines to disappear when hovering stickies ([#11950](https://github.com/n8n-io/n8n/issues/11950)) ([439a1cc](https://github.com/n8n-io/n8n/commit/439a1cc4f39243e91715b21a84b8e7266ce872cd)) +* **editor:** Fix canvas keybindings using splitter keys such as zooming using `+` key ([#12022](https://github.com/n8n-io/n8n/issues/12022)) ([6af9c82](https://github.com/n8n-io/n8n/commit/6af9c82af6020e99d61e442ee9c2d40761baf027)) +* **editor:** Fix community check ([#11979](https://github.com/n8n-io/n8n/issues/11979)) ([af0398a](https://github.com/n8n-io/n8n/commit/af0398a5e3a8987c01c7112e6f689b35e1ef92fe)) +* **editor:** Fix copy/paste keyboard events in canvas chat ([#12004](https://github.com/n8n-io/n8n/issues/12004)) ([967340a](https://github.com/n8n-io/n8n/commit/967340a2938a79c89319121bf57a8d654f88e06c)) +* **editor:** Fix node showing as successful if errors exists on subsequent runs ([#12019](https://github.com/n8n-io/n8n/issues/12019)) ([8616b17](https://github.com/n8n-io/n8n/commit/8616b17cc6c305da69bbb54fd56ab7cb34213f7c)) +* **editor:** Fix pin data showing up in production executions on new canvas ([#11951](https://github.com/n8n-io/n8n/issues/11951)) ([5f6f8a1](https://github.com/n8n-io/n8n/commit/5f6f8a1bddfd76b586c08da821e8b59070f449fc)) +* **editor:** Handle source control initialization to prevent UI form crashing ([#11776](https://github.com/n8n-io/n8n/issues/11776)) ([6be8e86](https://github.com/n8n-io/n8n/commit/6be8e86c45bd64d000bc95d2ef2d68220e930c02)) +* **editor:** Implement dirty nodes for partial executions ([#11739](https://github.com/n8n-io/n8n/issues/11739)) ([b8da4ff](https://github.com/n8n-io/n8n/commit/b8da4ff9edb0fbb0093c4c41fe11f8e67b696ca3)) +* **editor:** Resolve going back from Settings ([#11958](https://github.com/n8n-io/n8n/issues/11958)) ([d74423c](https://github.com/n8n-io/n8n/commit/d74423c75198d38d0d99a1879051b5e964ecae74)) +* **editor:** Unify executions card label color ([#11949](https://github.com/n8n-io/n8n/issues/11949)) ([fc79718](https://github.com/n8n-io/n8n/commit/fc797188d63e87df34b3a153eb4a0d0b7361b3f5)) +* **editor:** Use optional chaining for all members in execution data when using the debug feature ([#12024](https://github.com/n8n-io/n8n/issues/12024)) ([67aa0c9](https://github.com/n8n-io/n8n/commit/67aa0c9107bda16b1cb6d273e17c3cde77035f51)) +* **GraphQL Node:** Throw error if GraphQL variables are not objects or strings ([#11904](https://github.com/n8n-io/n8n/issues/11904)) ([85f30b2](https://github.com/n8n-io/n8n/commit/85f30b27ae282da58a25186d13ff17196dcd7d9c)) +* **HTTP Request Node:** Use iconv-lite to decode http responses, to support more encoding types ([#11930](https://github.com/n8n-io/n8n/issues/11930)) ([461b39c](https://github.com/n8n-io/n8n/commit/461b39c5df5dd446cb8ceef469b204c7c5111229)) +* Load workflows with unconnected Switch outputs ([#12020](https://github.com/n8n-io/n8n/issues/12020)) ([abc851c](https://github.com/n8n-io/n8n/commit/abc851c0cff298607a0dc2f2882aa17136898f45)) +* **n8n Form Node:** Use https to load google fonts ([#11948](https://github.com/n8n-io/n8n/issues/11948)) ([eccd924](https://github.com/n8n-io/n8n/commit/eccd924f5e8dbe59e37099d1a6fbe8866fef55bf)) +* **Telegram Trigger Node:** Fix header secret check ([#12018](https://github.com/n8n-io/n8n/issues/12018)) ([f16de4d](https://github.com/n8n-io/n8n/commit/f16de4db01c0496205635a3203a44098e7908453)) +* **Webflow Node:** Fix issue with pagination in v2 node ([#11934](https://github.com/n8n-io/n8n/issues/11934)) ([1eb94bc](https://github.com/n8n-io/n8n/commit/1eb94bcaf54d9e581856ce0b87253e1c28fa68e2)) +* **Webflow Node:** Fix issue with publishing items ([#11982](https://github.com/n8n-io/n8n/issues/11982)) ([0a8a57e](https://github.com/n8n-io/n8n/commit/0a8a57e4ec8081ab1a53f36d686b3d5dcaae2476)) + + +### Features + +* **AI Transform Node:** Node Prompt improvements ([#11611](https://github.com/n8n-io/n8n/issues/11611)) ([40a7445](https://github.com/n8n-io/n8n/commit/40a7445f0873af2cdbd10b12bd691c07a43e27cc)) +* **Code Node:** Warning if pairedItem absent or could not be auto mapped ([#11737](https://github.com/n8n-io/n8n/issues/11737)) ([3a5bd12](https://github.com/n8n-io/n8n/commit/3a5bd129459272cbac960ae2754db3028943f87e)) +* **editor:** Canvas chat UI & UX improvements ([#11924](https://github.com/n8n-io/n8n/issues/11924)) ([1e25774](https://github.com/n8n-io/n8n/commit/1e25774541461c86da5c4af8efec792e2814eeb1)) +* **editor:** Persist user's preferred display modes on localStorage ([#11929](https://github.com/n8n-io/n8n/issues/11929)) ([bd69316](https://github.com/n8n-io/n8n/commit/bd693162b86a21c90880bab2c2e67aab733095ff)) + + +### Performance Improvements + +* **editor:** Virtualize SchemaView ([#11694](https://github.com/n8n-io/n8n/issues/11694)) ([9c6def9](https://github.com/n8n-io/n8n/commit/9c6def91975764522fa52cdf21e9cb5bdb4d721d)) + + + +# [1.70.0](https://github.com/n8n-io/n8n/compare/n8n@1.69.0...n8n@1.70.0) (2024-11-27) + + +### Bug Fixes + +* **AI Agent Node:** Add binary message before scratchpad to prevent tool calling loops ([#11845](https://github.com/n8n-io/n8n/issues/11845)) ([5c80cb5](https://github.com/n8n-io/n8n/commit/5c80cb57cf709a1097a38e0394aad6fce5330eba)) +* CodeNodeEditor walk cannot read properties of null ([#11129](https://github.com/n8n-io/n8n/issues/11129)) ([d99e0a7](https://github.com/n8n-io/n8n/commit/d99e0a7c979a1ee96b2eea1b9011d5bce375289a)) +* **core:** Bring back execution data on the `executionFinished` push message ([#11821](https://github.com/n8n-io/n8n/issues/11821)) ([0313570](https://github.com/n8n-io/n8n/commit/03135702f18e750ba44840dccfec042270629a2b)) +* **core:** Correct invalid WS status code on removing connection ([#11901](https://github.com/n8n-io/n8n/issues/11901)) ([1d80225](https://github.com/n8n-io/n8n/commit/1d80225d26ba01f78934a455acdcca7b83be7205)) +* **core:** Don't use unbound context methods in code sandboxes ([#11914](https://github.com/n8n-io/n8n/issues/11914)) ([f6c0d04](https://github.com/n8n-io/n8n/commit/f6c0d045e9683cd04ee849f37b96697097c5b41d)) +* **core:** Fix broken execution query when using projectId ([#11852](https://github.com/n8n-io/n8n/issues/11852)) ([a061dbc](https://github.com/n8n-io/n8n/commit/a061dbca07ad686c563e85c56081bc1a7830259b)) +* **core:** Fix validation of items returned in the task runner ([#11897](https://github.com/n8n-io/n8n/issues/11897)) ([a535e88](https://github.com/n8n-io/n8n/commit/a535e88f1aec8fbbf2eb9397d38748f49773de2d)) +* **editor:** Add missing trigger waiting tooltip on new canvas ([#11918](https://github.com/n8n-io/n8n/issues/11918)) ([a8df221](https://github.com/n8n-io/n8n/commit/a8df221bfbb5428d93d03f539bcfdaf29ee20c21)) +* **editor:** Don't re-render input panel after node finishes executing ([#11813](https://github.com/n8n-io/n8n/issues/11813)) ([b3a99a2](https://github.com/n8n-io/n8n/commit/b3a99a2351079c37ed6d83f43920ba80f3832234)) +* **editor:** Fix AI assistant loading message layout ([#11819](https://github.com/n8n-io/n8n/issues/11819)) ([89b4807](https://github.com/n8n-io/n8n/commit/89b48072432753137b498c338af7777036fdde7a)) +* **editor:** Fix new canvas discovery tooltip position after adding github stars button ([#11898](https://github.com/n8n-io/n8n/issues/11898)) ([f4ab5c7](https://github.com/n8n-io/n8n/commit/f4ab5c7b9244b8fdde427c12c1a152fbaaba0c34)) +* **editor:** Fix node position not getting set when dragging selection on new canvas ([#11871](https://github.com/n8n-io/n8n/issues/11871)) ([595de81](https://github.com/n8n-io/n8n/commit/595de81c03b3e488ab41fb8d1d316c3db6a8372a)) +* **editor:** Restore workers view ([#11876](https://github.com/n8n-io/n8n/issues/11876)) ([3aa72f6](https://github.com/n8n-io/n8n/commit/3aa72f613f64c16d7dff67ffe66037894e45aa7c)) +* **editor:** Turn NPS survey into a modal and make sure it shows above the Ask AI button ([#11814](https://github.com/n8n-io/n8n/issues/11814)) ([ca169f3](https://github.com/n8n-io/n8n/commit/ca169f3f3455fa39ce9120b30d7b409bade6561e)) +* **editor:** Use `crypto.randomUUID()` to initialize node id if missing on new canvas ([#11873](https://github.com/n8n-io/n8n/issues/11873)) ([bc4857a](https://github.com/n8n-io/n8n/commit/bc4857a1b3d6ea389f11fb8246a1cee33b8a008e)) +* **n8n Form Node:** Duplicate popup in manual mode ([#11925](https://github.com/n8n-io/n8n/issues/11925)) ([2c34bf4](https://github.com/n8n-io/n8n/commit/2c34bf4ea6137fb0fb321969684ffa621da20fa3)) +* **n8n Form Node:** Redirect if completion page to trigger ([#11822](https://github.com/n8n-io/n8n/issues/11822)) ([1a8fb7b](https://github.com/n8n-io/n8n/commit/1a8fb7bdc428c6a23c8708e2dcf924f1f10b47a9)) +* **OpenAI Node:** Remove preview chatInput parameter for `Assistant:Messsage` operation ([#11825](https://github.com/n8n-io/n8n/issues/11825)) ([4dde287](https://github.com/n8n-io/n8n/commit/4dde287cde3af7c9c0e57248e96b8f1270da9332)) +* Retain execution data between partial executions (new flow) ([#11828](https://github.com/n8n-io/n8n/issues/11828)) ([3320436](https://github.com/n8n-io/n8n/commit/3320436a6fdf8472b3843b9fe8d4de7af7f5ef5c)) + + +### Features + +* Add SharePoint credentials ([#11570](https://github.com/n8n-io/n8n/issues/11570)) ([05c6109](https://github.com/n8n-io/n8n/commit/05c61091db9bdd62fdcca910ead50d0bd512966a)) +* Add Zabbix credential only node ([#11489](https://github.com/n8n-io/n8n/issues/11489)) ([fbd1ecf](https://github.com/n8n-io/n8n/commit/fbd1ecfb29461fee393914bc200ec72c654d8944)) +* **AI Transform Node:** Support for drag and drop ([#11276](https://github.com/n8n-io/n8n/issues/11276)) ([2c252b0](https://github.com/n8n-io/n8n/commit/2c252b0b2d5282f4a87bce76f93c4c02dd8ff5e3)) +* **editor:** Drop `response` wrapper requirement from Subworkflow Tool output ([#11785](https://github.com/n8n-io/n8n/issues/11785)) ([cd3598a](https://github.com/n8n-io/n8n/commit/cd3598aaab6cefe58a4cb9df7d93fb501415e9d3)) +* **editor:** Improve node and edge bring-to-front mechanism on new canvas ([#11793](https://github.com/n8n-io/n8n/issues/11793)) ([b89ca9d](https://github.com/n8n-io/n8n/commit/b89ca9d482faa5cb542898f3973fb6e7c9a8437a)) +* **editor:** Make new canvas connections go underneath node when looping backwards ([#11833](https://github.com/n8n-io/n8n/issues/11833)) ([91d1bd8](https://github.com/n8n-io/n8n/commit/91d1bd8d333454f3971605df73c3703102d2a9e9)) +* **editor:** Make the left sidebar in Expressions editor draggable ([#11838](https://github.com/n8n-io/n8n/issues/11838)) ([a713b3e](https://github.com/n8n-io/n8n/commit/a713b3ed25feb1790412fc320cf41a0967635263)) +* **editor:** Migrate existing users to new canvas and set new canvas as default ([#11896](https://github.com/n8n-io/n8n/issues/11896)) ([caa7447](https://github.com/n8n-io/n8n/commit/caa744785a2cc5063a5fb9d269c0ea53ea432298)) +* **Slack Node:** Update wait for approval to use markdown ([#11754](https://github.com/n8n-io/n8n/issues/11754)) ([40dd02f](https://github.com/n8n-io/n8n/commit/40dd02f360d0d8752fe89c4304c18cac9858c530)) + + + # [1.69.0](https://github.com/n8n-io/n8n/compare/n8n@1.68.0...n8n@1.69.0) (2024-11-20) diff --git a/cypress/composables/modals/credential-modal.ts b/cypress/composables/modals/credential-modal.ts index 77b69fc586..53ba0c3a28 100644 --- a/cypress/composables/modals/credential-modal.ts +++ b/cypress/composables/modals/credential-modal.ts @@ -40,6 +40,7 @@ export function saveCredential() { .within(() => { cy.get('button').should('not.exist'); }); + getCredentialSaveButton().should('have.text', 'Saved'); } export function closeCredentialModal() { diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts index 251db6e75d..bc27048219 100644 --- a/cypress/composables/workflow.ts +++ b/cypress/composables/workflow.ts @@ -76,6 +76,10 @@ export function getCanvasNodes() { ); } +export function getCanvasNodeByName(nodeName: string) { + return getCanvasNodes().filter(`:contains(${nodeName})`); +} + export function getSaveButton() { return cy.getByTestId('workflow-save-button'); } @@ -194,3 +198,8 @@ export function pasteWorkflow(workflow: object) { export function clickZoomToFit() { getZoomToFitButton().click(); } + +export function deleteNode(name: string) { + getCanvasNodeByName(name).first().click(); + cy.get('body').type('{del}'); +} diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index 8a8fd4e4c1..30a990fb28 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -1,3 +1,4 @@ +import { saveCredential } from '../composables/modals/credential-modal'; import * as projects from '../composables/projects'; import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN, NOTION_NODE_NAME } from '../constants'; import { @@ -225,8 +226,7 @@ describe('Sharing', { disableAutoLogin: true }, () => { .filter(':contains("Development")') .should('have.length', 1) .click(); - credentialsModal.getters.saveButton().click(); - credentialsModal.getters.saveButton().should('have.text', 'Saved'); + saveCredential(); credentialsModal.actions.close(); projects.getProjectTabWorkflows().click(); @@ -252,8 +252,7 @@ describe('Sharing', { disableAutoLogin: true }, () => { credentialsModal.actions.changeTab('Sharing'); credentialsModal.getters.usersSelect().click(); getVisibleSelect().find('li').should('have.length', 4).first().click(); - credentialsModal.getters.saveButton().click(); - credentialsModal.getters.saveButton().should('have.text', 'Saved'); + saveCredential(); credentialsModal.actions.close(); credentialsPage.getters diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index 71c3083856..cda01c71a3 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -1,5 +1,6 @@ import { type ICredentialType } from 'n8n-workflow'; +import { getCredentialSaveButton, saveCredential } from '../composables/modals/credential-modal'; import { AGENT_NODE_NAME, AI_TOOL_HTTP_NODE_NAME, @@ -194,7 +195,7 @@ describe('Credentials', () => { credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.name().click(); credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME); - credentialsModal.getters.saveButton().click(); + saveCredential(); credentialsModal.getters.closeButton().click(); workflowPage.getters .nodeCredentialsSelect() @@ -212,7 +213,7 @@ describe('Credentials', () => { credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.name().click(); credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME2); - credentialsModal.getters.saveButton().click(); + saveCredential(); credentialsModal.getters.closeButton().click(); workflowPage.getters .nodeCredentialsSelect() @@ -237,7 +238,7 @@ describe('Credentials', () => { credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.name().click(); credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME); - credentialsModal.getters.saveButton().click(); + saveCredential(); credentialsModal.getters.closeButton().click(); workflowPage.getters .nodeCredentialsSelect() @@ -342,7 +343,8 @@ describe('Credentials', () => { credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890'); credentialsModal.actions.setName('My awesome Notion account'); - credentialsModal.getters.saveButton().click({ force: true }); + getCredentialSaveButton().click(); + errorToast().should('have.length', 1); errorToast().should('be.visible'); diff --git a/cypress/e2e/27-two-factor-authentication.cy.ts b/cypress/e2e/27-two-factor-authentication.cy.ts index dc62a0c58c..05949a188c 100644 --- a/cypress/e2e/27-two-factor-authentication.cy.ts +++ b/cypress/e2e/27-two-factor-authentication.cy.ts @@ -49,33 +49,47 @@ describe('Two-factor authentication', { disableAutoLogin: true }, () => { cy.intercept('GET', '/rest/mfa/qr').as('getMfaQrCode'); }); - it('Should be able to login with MFA token', () => { + it('Should be able to login with MFA code', () => { const { email, password } = user; signinPage.actions.loginWithEmailAndPassword(email, password); personalSettingsPage.actions.enableMfa(); mainSidebar.actions.signout(); - const token = generateOTPToken(user.mfaSecret); - mfaLoginPage.actions.loginWithMfaToken(email, password, token); + const mfaCode = generateOTPToken(user.mfaSecret); + mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode); mainSidebar.actions.signout(); }); - it('Should be able to login with recovery code', () => { + it('Should be able to login with MFA recovery code', () => { const { email, password } = user; signinPage.actions.loginWithEmailAndPassword(email, password); personalSettingsPage.actions.enableMfa(); mainSidebar.actions.signout(); - mfaLoginPage.actions.loginWithRecoveryCode(email, password, user.mfaRecoveryCodes[0]); + mfaLoginPage.actions.loginWithMfaRecoveryCode(email, password, user.mfaRecoveryCodes[0]); mainSidebar.actions.signout(); }); - it('Should be able to disable MFA in account', () => { + it('Should be able to disable MFA in account with MFA code', () => { const { email, password } = user; signinPage.actions.loginWithEmailAndPassword(email, password); personalSettingsPage.actions.enableMfa(); mainSidebar.actions.signout(); - const token = generateOTPToken(user.mfaSecret); - mfaLoginPage.actions.loginWithMfaToken(email, password, token); - personalSettingsPage.actions.disableMfa(); + const mfaCode = generateOTPToken(user.mfaSecret); + mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode); + const disableToken = generateOTPToken(user.mfaSecret); + personalSettingsPage.actions.disableMfa(disableToken); + personalSettingsPage.getters.enableMfaButton().should('exist'); + mainSidebar.actions.signout(); + }); + + it('Should be able to disable MFA in account with recovery code', () => { + const { email, password } = user; + signinPage.actions.loginWithEmailAndPassword(email, password); + personalSettingsPage.actions.enableMfa(); + mainSidebar.actions.signout(); + const mfaCode = generateOTPToken(user.mfaSecret); + mfaLoginPage.actions.loginWithMfaCode(email, password, mfaCode); + personalSettingsPage.actions.disableMfa(user.mfaRecoveryCodes[0]); + personalSettingsPage.getters.enableMfaButton().should('exist'); mainSidebar.actions.signout(); }); }); diff --git a/cypress/e2e/2929-ado-can-load-old-switch-node-workflows.cy.ts b/cypress/e2e/2929-ado-can-load-old-switch-node-workflows.cy.ts new file mode 100644 index 0000000000..39edc54163 --- /dev/null +++ b/cypress/e2e/2929-ado-can-load-old-switch-node-workflows.cy.ts @@ -0,0 +1,17 @@ +import { + deleteNode, + getCanvasNodes, + navigateToNewWorkflowPage, + pasteWorkflow, +} from '../composables/workflow'; +import Workflow from '../fixtures/Switch_node_with_null_connection.json'; + +describe('ADO-2929 can load Switch nodes', () => { + it('can load workflows with Switch nodes with null at connection index', () => { + navigateToNewWorkflowPage(); + pasteWorkflow(Workflow); + getCanvasNodes().should('have.length', 3); + deleteNode('Switch'); + getCanvasNodes().should('have.length', 2); + }); +}); diff --git a/cypress/e2e/43-oauth-flow.cy.ts b/cypress/e2e/43-oauth-flow.cy.ts index 300a202540..d91315627b 100644 --- a/cypress/e2e/43-oauth-flow.cy.ts +++ b/cypress/e2e/43-oauth-flow.cy.ts @@ -1,3 +1,4 @@ +import { getCredentialSaveButton } from '../composables/modals/credential-modal'; import { CredentialsPage, CredentialsModal } from '../pages'; const credentialsPage = new CredentialsPage(); @@ -40,7 +41,7 @@ describe('Credentials', () => { }); // Check that the credential was saved and connected successfully - credentialsModal.getters.saveButton().should('contain.text', 'Saved'); + getCredentialSaveButton().should('contain.text', 'Saved'); credentialsModal.getters.oauthConnectSuccessBanner().should('be.visible'); }); }); diff --git a/cypress/fixtures/Switch_node_with_null_connection.json b/cypress/fixtures/Switch_node_with_null_connection.json new file mode 100644 index 0000000000..325e097bd0 --- /dev/null +++ b/cypress/fixtures/Switch_node_with_null_connection.json @@ -0,0 +1,85 @@ +{ + "nodes": [ + { + "parameters": {}, + "id": "418350b8-b402-4d3b-93ba-3794d36c1ad5", + "name": "When clicking \"Test workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [440, 380] + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + } + }, + {}, + {} + ] + }, + "options": {} + }, + "id": "b67ad46f-6b0d-4ff4-b2d2-dfbde44e287c", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 3, + "position": [660, 380] + }, + { + "parameters": { + "options": {} + }, + "id": "24731c11-e2a4-4854-81a6-277ce72e8a93", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [840, 480] + } + ], + "connections": { + "When clicking \"Test workflow\"": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + null, + null, + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} +} diff --git a/cypress/pages/mfa-login.ts b/cypress/pages/mfa-login.ts index 66fc197e3f..7e679804ff 100644 --- a/cypress/pages/mfa-login.ts +++ b/cypress/pages/mfa-login.ts @@ -8,18 +8,18 @@ export class MfaLoginPage extends BasePage { getters = { form: () => cy.getByTestId('mfa-login-form'), - token: () => cy.getByTestId('token'), - recoveryCode: () => cy.getByTestId('recoveryCode'), + mfaCode: () => cy.getByTestId('mfaCode'), + mfaRecoveryCode: () => cy.getByTestId('mfaRecoveryCode'), enterRecoveryCodeButton: () => cy.getByTestId('mfa-enter-recovery-code-button'), }; actions = { - loginWithMfaToken: (email: string, password: string, mfaToken: string) => { + loginWithMfaCode: (email: string, password: string, mfaCode: string) => { const signinPage = new SigninPage(); const workflowsPage = new WorkflowsPage(); cy.session( - [mfaToken], + [mfaCode], () => { cy.visit(signinPage.url); @@ -30,7 +30,7 @@ export class MfaLoginPage extends BasePage { }); this.getters.form().within(() => { - this.getters.token().type(mfaToken); + this.getters.mfaCode().type(mfaCode); }); // we should be redirected to /workflows @@ -43,12 +43,12 @@ export class MfaLoginPage extends BasePage { }, ); }, - loginWithRecoveryCode: (email: string, password: string, recoveryCode: string) => { + loginWithMfaRecoveryCode: (email: string, password: string, mfaRecoveryCode: string) => { const signinPage = new SigninPage(); const workflowsPage = new WorkflowsPage(); cy.session( - [recoveryCode], + [mfaRecoveryCode], () => { cy.visit(signinPage.url); @@ -61,7 +61,7 @@ export class MfaLoginPage extends BasePage { this.getters.enterRecoveryCodeButton().click(); this.getters.form().within(() => { - this.getters.recoveryCode().type(recoveryCode); + this.getters.mfaRecoveryCode().type(mfaRecoveryCode); }); // we should be redirected to /workflows diff --git a/cypress/pages/modals/credentials-modal.ts b/cypress/pages/modals/credentials-modal.ts index cd3ded63f8..b8907386a0 100644 --- a/cypress/pages/modals/credentials-modal.ts +++ b/cypress/pages/modals/credentials-modal.ts @@ -1,3 +1,4 @@ +import { getCredentialSaveButton, saveCredential } from '../../composables/modals/credential-modal'; import { getVisibleSelect } from '../../utils'; import { BasePage } from '../base'; @@ -13,8 +14,6 @@ export class CredentialsModal extends BasePage { this.getters.credentialInputs().find(`:contains('${fieldName}') .n8n-input input`), name: () => cy.getByTestId('credential-name'), nameInput: () => cy.getByTestId('credential-name').find('input'), - // Saving of the credentials takes a while on the CI so we need to increase the timeout - saveButton: () => cy.getByTestId('credential-save-button', { timeout: 5000 }), deleteButton: () => cy.getByTestId('credential-delete-button'), closeButton: () => this.getters.editCredentialModal().find('.el-dialog__close').first(), oauthConnectButton: () => cy.getByTestId('oauth-connect-button'), @@ -41,17 +40,17 @@ export class CredentialsModal extends BasePage { }, save: (test = false) => { cy.intercept('POST', '/rest/credentials').as('saveCredential'); - this.getters.saveButton().click({ force: true }); + saveCredential(); cy.wait('@saveCredential'); if (test) cy.wait('@testCredential'); - this.getters.saveButton().should('contain.text', 'Saved'); + getCredentialSaveButton().should('contain.text', 'Saved'); }, saveSharing: () => { cy.intercept('PUT', '/rest/credentials/*/share').as('shareCredential'); - this.getters.saveButton().click({ force: true }); + saveCredential(); cy.wait('@shareCredential'); - this.getters.saveButton().should('contain.text', 'Saved'); + getCredentialSaveButton().should('contain.text', 'Saved'); }, close: () => { this.getters.closeButton().click(); @@ -65,7 +64,7 @@ export class CredentialsModal extends BasePage { .each(($el) => { cy.wrap($el).type('test'); }); - this.getters.saveButton().click(); + saveCredential(); if (closeModal) { this.getters.closeButton().click(); } diff --git a/cypress/pages/settings-personal.ts b/cypress/pages/settings-personal.ts index 4574f95691..5602bd7e92 100644 --- a/cypress/pages/settings-personal.ts +++ b/cypress/pages/settings-personal.ts @@ -22,6 +22,8 @@ export class PersonalSettingsPage extends BasePage { saveSettingsButton: () => cy.getByTestId('save-settings-button'), enableMfaButton: () => cy.getByTestId('enable-mfa-button'), disableMfaButton: () => cy.getByTestId('disable-mfa-button'), + mfaCodeOrMfaRecoveryCodeInput: () => cy.getByTestId('mfa-code-or-recovery-code-input'), + mfaSaveButton: () => cy.getByTestId('mfa-save-button'), themeSelector: () => cy.getByTestId('theme-select'), selectOptionsVisible: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'), }; @@ -83,9 +85,11 @@ export class PersonalSettingsPage extends BasePage { mfaSetupModal.getters.saveButton().click(); }); }, - disableMfa: () => { + disableMfa: (mfaCodeOrRecoveryCode: string) => { cy.visit(this.url); this.getters.disableMfaButton().click(); + this.getters.mfaCodeOrMfaRecoveryCodeInput().type(mfaCodeOrRecoveryCode); + this.getters.mfaSaveButton().click(); }, }; } diff --git a/docker/images/n8n-custom/Dockerfile b/docker/images/n8n-custom/Dockerfile index 210fc0630f..f4c1da897b 100644 --- a/docker/images/n8n-custom/Dockerfile +++ b/docker/images/n8n-custom/Dockerfile @@ -33,7 +33,7 @@ COPY docker/images/n8n/docker-entrypoint.sh / # Setup the Task Runner Launcher ARG TARGETPLATFORM -ARG LAUNCHER_VERSION=0.6.0-rc +ARG LAUNCHER_VERSION=0.7.0-rc COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json # Download, verify, then extract the launcher binary RUN \ diff --git a/docker/images/n8n/Dockerfile b/docker/images/n8n/Dockerfile index fe4aee41dc..0f28ee706f 100644 --- a/docker/images/n8n/Dockerfile +++ b/docker/images/n8n/Dockerfile @@ -24,7 +24,7 @@ RUN set -eux; \ # Setup the Task Runner Launcher ARG TARGETPLATFORM -ARG LAUNCHER_VERSION=0.6.0-rc +ARG LAUNCHER_VERSION=0.7.0-rc COPY n8n-task-runners.json /etc/n8n-task-runners.json # Download, verify, then extract the launcher binary RUN \ diff --git a/docker/images/n8n/n8n-task-runners.json b/docker/images/n8n/n8n-task-runners.json index 10cf338731..a37c59fccb 100644 --- a/docker/images/n8n/n8n-task-runners.json +++ b/docker/images/n8n/n8n-task-runners.json @@ -7,6 +7,7 @@ "args": ["/usr/local/lib/node_modules/n8n/node_modules/@n8n/task-runner/dist/start.js"], "allowed-env": [ "PATH", + "GENERIC_TIMEZONE", "N8N_RUNNERS_GRANT_TOKEN", "N8N_RUNNERS_N8N_URI", "N8N_RUNNERS_MAX_PAYLOAD", diff --git a/package.json b/package.json index 2098141852..e2a0628773 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-monorepo", - "version": "1.69.0", + "version": "1.71.0", "private": true, "engines": { "node": ">=20.15", @@ -62,7 +62,7 @@ "ts-jest": "^29.1.1", "tsc-alias": "^1.8.10", "tsc-watch": "^6.2.0", - "turbo": "2.1.2", + "turbo": "2.3.3", "typescript": "*", "zx": "^8.1.4" }, @@ -80,7 +80,7 @@ "tslib": "^2.6.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.7.2", - "vue-tsc": "^2.1.6", + "vue-tsc": "^2.1.10", "ws": ">=8.17.1" }, "patchedDependencies": { @@ -90,7 +90,7 @@ "@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", "@types/uuencode@0.0.3": "patches/@types__uuencode@0.0.3.patch", - "vue-tsc@2.1.6": "patches/vue-tsc@2.1.6.patch" + "vue-tsc@2.1.10": "patches/vue-tsc@2.1.10.patch" } } } diff --git a/packages/@n8n/api-types/package.json b/packages/@n8n/api-types/package.json index 12399cb35c..9f045e31d4 100644 --- a/packages/@n8n/api-types/package.json +++ b/packages/@n8n/api-types/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/api-types", - "version": "0.7.0", + "version": "0.9.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/api-types/src/frontend-settings.ts b/packages/@n8n/api-types/src/frontend-settings.ts index 54b7956821..8f9c740ad6 100644 --- a/packages/@n8n/api-types/src/frontend-settings.ts +++ b/packages/@n8n/api-types/src/frontend-settings.ts @@ -172,4 +172,5 @@ export interface FrontendSettings { blockFileAccessToN8nFiles: boolean; }; betaFeatures: FrontendBetaFeatures[]; + virtualSchemaView: boolean; } diff --git a/packages/@n8n/chat/package.json b/packages/@n8n/chat/package.json index 6ad77655b4..4cc32dc8fe 100644 --- a/packages/@n8n/chat/package.json +++ b/packages/@n8n/chat/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/chat", - "version": "0.31.0", + "version": "0.32.0", "scripts": { "dev": "pnpm run storybook", "build": "pnpm build:vite && pnpm build:bundle", @@ -46,11 +46,12 @@ "devDependencies": { "@iconify-json/mdi": "^1.1.54", "@n8n/storybook": "workspace:*", + "@vitejs/plugin-vue": "catalog:frontend", "@vitest/coverage-v8": "catalog:frontend", "unplugin-icons": "^0.19.0", "vite": "catalog:frontend", "vitest": "catalog:frontend", - "vite-plugin-dts": "^4.2.3", + "vite-plugin-dts": "^4.3.0", "vue-tsc": "catalog:frontend" }, "files": [ diff --git a/packages/@n8n/chat/src/components/Input.vue b/packages/@n8n/chat/src/components/Input.vue index 4abfc76849..1b9e0b9608 100644 --- a/packages/@n8n/chat/src/components/Input.vue +++ b/packages/@n8n/chat/src/components/Input.vue @@ -38,12 +38,12 @@ const isSubmitting = ref(false); const resizeObserver = ref(null); const isSubmitDisabled = computed(() => { - return input.value === '' || waitingForResponse.value || options.disabled?.value === true; + return input.value === '' || unref(waitingForResponse) || options.disabled?.value === true; }); const isInputDisabled = computed(() => options.disabled?.value === true); const isFileUploadDisabled = computed( - () => isFileUploadAllowed.value && waitingForResponse.value && !options.disabled?.value, + () => isFileUploadAllowed.value && unref(waitingForResponse) && !options.disabled?.value, ); const isFileUploadAllowed = computed(() => unref(options.allowFileUploads) === true); const allowedFileTypes = computed(() => unref(options.allowedFilesMimeTypes)); @@ -194,10 +194,13 @@ function adjustHeight(event: Event) {