From 72eea0dfb9e679bde95996d99684e23d081db5a7 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:29:47 +0300 Subject: [PATCH 01/18] fix(Set Node): Convert string to number --- packages/nodes-base/nodes/Set/Set.node.ts | 10 +- .../nodes/Set/test/Set.workflow.number.json | 145 ++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/nodes/Set/test/Set.workflow.number.json diff --git a/packages/nodes-base/nodes/Set/Set.node.ts b/packages/nodes-base/nodes/Set/Set.node.ts index 0bfacf0437..e6e0737ee7 100644 --- a/packages/nodes-base/nodes/Set/Set.node.ts +++ b/packages/nodes-base/nodes/Set/Set.node.ts @@ -15,7 +15,7 @@ export class Set implements INodeType { name: 'set', icon: 'fa:pen', group: ['input'], - version: 1, + version: [1, 2], description: 'Sets values on items and optionally remove other values', defaults: { name: 'Set', @@ -137,6 +137,7 @@ export class Set implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const nodeVersion = this.getNode().typeVersion; if (items.length === 0) { items.push({ json: {} }); @@ -182,6 +183,13 @@ export class Set implements INodeType { // Add number values (this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach( (setItem) => { + if ( + nodeVersion >= 2 && + typeof setItem.value === 'string' && + !Number.isNaN(Number(setItem.value)) + ) { + setItem.value = Number(setItem.value); + } if (options.dotNotation === false) { newItem.json[setItem.name as string] = setItem.value; } else { diff --git a/packages/nodes-base/nodes/Set/test/Set.workflow.number.json b/packages/nodes-base/nodes/Set/test/Set.workflow.number.json new file mode 100644 index 0000000000..198d147d78 --- /dev/null +++ b/packages/nodes-base/nodes/Set/test/Set.workflow.number.json @@ -0,0 +1,145 @@ +{ + "name": "My workflow 31", + "nodes": [ + { + "parameters": {}, + "id": "5363525f-2065-4132-82cc-808941e51d47", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [-380, -40] + }, + { + "parameters": { + "values": { + "string": [ + { + "name": "myNumber", + "value": "123456" + } + ] + }, + "options": {} + }, + "id": "9e6fa15e-f6d8-4764-815b-832ff00c531c", + "name": "Set", + "type": "n8n-nodes-base.set", + "typeVersion": 1, + "position": [-80, -40] + }, + { + "parameters": { + "keepOnlySet": true, + "values": { + "number": [ + { + "name": "updatedNumber", + "value": "={{ $json[\"myNumber\"] }}" + } + ] + }, + "options": {} + }, + "id": "4893a819-9f21-446a-b89f-3079d87d5f40", + "name": "Set1", + "type": "n8n-nodes-base.set", + "typeVersion": 1, + "position": [140, -40] + }, + { + "parameters": { + "keepOnlySet": true, + "values": { + "number": [ + { + "name": "numField", + "value": "={{ $json.updatedNumber }}" + } + ] + }, + "options": {} + }, + "id": "8e6b36ca-4f50-4a51-b42f-8098d71248bf", + "name": "Set2", + "type": "n8n-nodes-base.set", + "typeVersion": 2, + "position": [540, -40] + }, + { + "parameters": { + "content": "## Set Node V1 sets numbers as string", + "height": 265, + "width": 479 + }, + "id": "12610911-6841-486e-924a-af66cececdca", + "name": "Sticky Note", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [-146, -120] + }, + { + "parameters": { + "content": "## Set Node V2 sets numbers as numbers", + "height": 264, + "width": 480 + }, + "id": "e5896250-3e0f-47ac-b47f-9f24bac4f52f", + "name": "Sticky Note1", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [420, -120] + } + ], + "pinData": { + "Set2": [ + { + "json": { + "numField": 123456 + } + } + ] + }, + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Set", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set": { + "main": [ + [ + { + "node": "Set1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set1": { + "main": [ + [ + { + "node": "Set2", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": {}, + "versionId": "f15acb8d-6e15-4962-b568-66f5a412efe0", + "id": "240", + "meta": { + "instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c" + }, + "tags": [] +} From 00181cd803f5a0cca50b5fc1ab84d0a26dd618ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 30 Mar 2023 16:27:40 +0200 Subject: [PATCH 02/18] fix(editor): Show correct status on canceled executions (#5813) Co-authored-by: Milorad Filipovic --- .../src/components/ExecutionFilter.vue | 1 + .../src/components/ExecutionsList.vue | 4 ++ .../ExecutionsView/ExecutionCard.vue | 4 +- .../editor-ui/src/mixins/executionsHelpers.ts | 38 ++++++++----------- .../src/plugins/i18n/locales/en.json | 2 + 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/editor-ui/src/components/ExecutionFilter.vue b/packages/editor-ui/src/components/ExecutionFilter.vue index 5cc1244954..2b0e0dd87b 100644 --- a/packages/editor-ui/src/components/ExecutionFilter.vue +++ b/packages/editor-ui/src/components/ExecutionFilter.vue @@ -74,6 +74,7 @@ const vModel = reactive( const statuses = computed(() => [ { id: 'all', name: locale.baseText('executionsList.anyStatus') }, { id: 'error', name: locale.baseText('executionsList.error') }, + { id: 'canceled', name: locale.baseText('executionsList.canceled') }, { id: 'running', name: locale.baseText('executionsList.running') }, { id: 'success', name: locale.baseText('executionsList.success') }, { id: 'waiting', name: locale.baseText('executionsList.waiting') }, diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue index b29b7a3abf..992a0f16b9 100644 --- a/packages/editor-ui/src/components/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsList.vue @@ -779,6 +779,8 @@ export default mixins(externalHooks, genericHelpers, executionHelpers, restApi, if (status === 'waiting') { text = this.$locale.baseText('executionsList.waiting'); + } else if (status === 'canceled') { + text = this.$locale.baseText('executionsList.canceled'); } else if (status === 'crashed') { text = this.$locale.baseText('executionsList.error'); } else if (status === 'new') { @@ -801,6 +803,8 @@ export default mixins(externalHooks, genericHelpers, executionHelpers, restApi, if (status === 'waiting') { path = 'executionsList.statusWaiting'; + } else if (status === 'canceled') { + path = 'executionsList.statusCanceled'; } else if (status === 'crashed') { path = 'executionsList.statusText'; } else if (status === 'new') { diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue index a1e0f41f0c..8885b44490 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue @@ -38,9 +38,7 @@ diff --git a/packages/editor-ui/src/mixins/executionsHelpers.ts b/packages/editor-ui/src/mixins/executionsHelpers.ts index 2f17833078..2887fe7da7 100644 --- a/packages/editor-ui/src/mixins/executionsHelpers.ts +++ b/packages/editor-ui/src/mixins/executionsHelpers.ts @@ -50,34 +50,26 @@ export const executionHelpers = mixins(genericHelpers).extend({ ) { status.name = 'running'; status.label = this.$locale.baseText('executionsList.running'); - if (execution.startedAt) { - status.runningTime = this.displayTimer( - new Date().getTime() - new Date(execution.startedAt).getTime(), - true, - ); - } } else if (execution.status === 'success' || execution.finished) { status.name = 'success'; status.label = this.$locale.baseText('executionsList.succeeded'); - if (execution.stoppedAt) { - status.runningTime = this.displayTimer( - new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(), - true, - ); - } - } else if ( - execution.status === 'failed' || - execution.status === 'crashed' || - execution.stoppedAt !== null - ) { + } else if (execution.status === 'failed' || execution.status === 'crashed') { status.name = 'error'; status.label = this.$locale.baseText('executionsList.error'); - if (execution.stoppedAt) { - status.runningTime = this.displayTimer( - new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(), - true, - ); - } + } else if (execution.status === 'canceled') { + status.label = this.$locale.baseText('executionsList.canceled'); + } + + if (!execution.status) execution.status = 'unknown'; + + if (execution.startedAt && execution.stoppedAt) { + const stoppedAt = execution.stoppedAt + ? new Date(execution.stoppedAt).getTime() + : Date.now(); + status.runningTime = this.displayTimer( + stoppedAt - new Date(execution.startedAt).getTime(), + true, + ); } return status; diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 88b59b0a0c..f1262bf647 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -451,6 +451,7 @@ "executionsList.allWorkflows": "All Workflows", "executionsList.anyStatus": "Any Status", "executionsList.autoRefresh": "Auto refresh", + "executionsList.canceled": "Canceled", "executionsList.confirmMessage.cancelButtonText": "", "executionsList.confirmMessage.confirmButtonText": "Yes, delete", "executionsList.confirmMessage.headline": "Delete Executions?", @@ -496,6 +497,7 @@ "executionsList.started": "{date} at {time}", "executionsList.id": "Execution ID", "executionsList.status": "Status", + "executionsList.statusCanceled": "Canceled", "executionsList.statusText": "{status} in {time}", "executionsList.statusRunning": "{status} for {time}", "executionsList.statusWaiting": "{status} until {time}", From 3bf267c14757fa67dcfa0edf0b4db74ab1b7415c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 30 Mar 2023 16:44:39 +0200 Subject: [PATCH 03/18] fix(core): Password reset should pass in the correct values to external hooks (#5842) --- .../cli/src/controllers/passwordReset.controller.ts | 6 ++++-- .../cli/test/integration/passwordReset.api.test.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index 4b0ec6c9df..462ad22839 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -244,8 +244,10 @@ export class PasswordResetController { throw new NotFoundError(''); } + const passwordHash = await hashPassword(validPassword); + await this.userRepository.update(userId, { - password: await hashPassword(validPassword), + password: passwordHash, resetPasswordToken: null, resetPasswordTokenExpiration: null, }); @@ -268,6 +270,6 @@ export class PasswordResetController { }); } - await this.externalHooks.run('user.password.update', [user.email, password]); + await this.externalHooks.run('user.password.update', [user.email, passwordHash]); } } diff --git a/packages/cli/test/integration/passwordReset.api.test.ts b/packages/cli/test/integration/passwordReset.api.test.ts index 38c9042ddf..0b48f2cdd2 100644 --- a/packages/cli/test/integration/passwordReset.api.test.ts +++ b/packages/cli/test/integration/passwordReset.api.test.ts @@ -14,6 +14,7 @@ import { randomValidPassword, } from './shared/random'; import * as testDb from './shared/testDb'; +import { ExternalHooks } from '@/ExternalHooks'; jest.mock('@/UserManagement/email/NodeMailer'); @@ -21,6 +22,7 @@ let globalOwnerRole: Role; let globalMemberRole: Role; let owner: User; let authlessAgent: SuperAgentTest; +let externalHooks = utils.mockInstance(ExternalHooks); beforeAll(async () => { const app = await utils.initTestServer({ endpointGroups: ['passwordReset'] }); @@ -36,6 +38,7 @@ beforeEach(async () => { owner = await testDb.createUser({ globalRole: globalOwnerRole }); config.set('userManagement.isInstanceOwnerSetUp', true); + externalHooks.run.mockReset(); }); afterAll(async () => { @@ -191,6 +194,11 @@ describe('POST /change-password', () => { const comparisonResult = await compare(passwordToStore, storedPassword); expect(comparisonResult).toBe(true); expect(storedPassword).not.toBe(passwordToStore); + + expect(externalHooks.run).toHaveBeenCalledWith('user.password.update', [ + owner.email, + storedPassword, + ]); }); test('should fail with invalid inputs', async () => { @@ -246,5 +254,7 @@ describe('POST /change-password', () => { }); expect(response.statusCode).toBe(404); + + expect(externalHooks.run).not.toHaveBeenCalled(); }); }); From 918c79c137f781764f11ad3a33ead337efce681a Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 31 Mar 2023 14:02:32 +0300 Subject: [PATCH 04/18] fix(HubSpot Trigger Node): Developer API key is required for webhooks --- .../nodes-base/credentials/HubspotDeveloperApi.credentials.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/credentials/HubspotDeveloperApi.credentials.ts b/packages/nodes-base/credentials/HubspotDeveloperApi.credentials.ts index d32200b8f4..4a3c03e2b9 100644 --- a/packages/nodes-base/credentials/HubspotDeveloperApi.credentials.ts +++ b/packages/nodes-base/credentials/HubspotDeveloperApi.credentials.ts @@ -58,6 +58,7 @@ export class HubspotDeveloperApi implements ICredentialType { displayName: 'Developer API Key', name: 'apiKey', type: 'string', + required: true, typeOptions: { password: true }, default: '', }, From 86e20173da24df82b6f1316cdede69784162a72e Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Fri, 31 Mar 2023 15:59:54 +0300 Subject: [PATCH 05/18] fix: Fix parameter inputField ref not yet mounted (no-changelog) (#5864) * fix: fix parameter inputField ref not yet mounted * fix: move optional chaining * fix: fix focus condition --- packages/editor-ui/src/components/ParameterInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 8d0c887531..bba554514a 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -953,7 +953,7 @@ export default mixins( // Set focus on field setTimeout(() => { // @ts-ignore - if (this.$refs.inputField && this.$refs.inputField.$el) { + if (this.$refs.inputField?.focus && this.$refs.inputField?.$el) { // @ts-ignore this.$refs.inputField.focus(); this.isFocused = true; From 3be37e25a52983b43b4eed3847922e99f2bf07bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 31 Mar 2023 16:44:26 +0200 Subject: [PATCH 06/18] fix(editor): Fix focused state in Code node editor (#5869) :art: Fix focused state in Code node --- packages/editor-ui/src/components/CodeNodeEditor/theme.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/theme.ts b/packages/editor-ui/src/components/CodeNodeEditor/theme.ts index 8dabe33098..96fc32e0b3 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/theme.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/theme.ts @@ -48,6 +48,10 @@ export const CODE_NODE_EDITOR_THEME = [ '&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': { backgroundColor: 'var(--color-code-selection)', }, + '&.cm-editor.cm-focused': { + outline: 'none', + borderColor: 'var(--color-secondary)', + }, '.cm-activeLine': { backgroundColor: 'var(--color-code-lineHighlight)', }, @@ -57,6 +61,7 @@ export const CODE_NODE_EDITOR_THEME = [ '.cm-gutters': { backgroundColor: 'var(--color-code-gutterBackground)', color: 'var(--color-code-gutterForeground)', + borderRadius: 'var(--border-radius-base)', }, '.cm-tooltip': { maxWidth: BASE_STYLING.tooltip.maxWidth, From d5d9f58f1777040c63a2a0aa3c0d7bf632dc7b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Fri, 31 Mar 2023 18:48:30 +0200 Subject: [PATCH 07/18] fix(editor): Fix loading executions in long execution list (#5843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(editor): Fix loading executions in long execution list * ⚡ Added max number of attempts before displaying temporary execution card * ⚡ Simplifying temp execution preview logic, handling current execution delete, updating style * 💄 Renaming `executionWIthGap` -> `temporaryExecution` --- .../ExecutionsView/ExecutionCard.vue | 12 ++++++ .../ExecutionsView/ExecutionsList.vue | 43 ++++++++++++++----- .../ExecutionsView/ExecutionsSidebar.vue | 14 +++++- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue index 8885b44490..54b550d563 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionCard.vue @@ -6,6 +6,7 @@ [$style.active]: isActive, [$style[executionUIDetails.name]]: true, [$style.highlight]: highlight, + [$style.showGap]: showGap, }" > .executionCard { display: flex; + flex-direction: column; padding-right: var(--spacing-m); &.active { @@ -252,4 +258,10 @@ export default mixins(executionHelpers, showMessage, restApi).extend({ margin-left: var(--spacing-2xs); } } +.showGap { + margin-bottom: var(--spacing-2xs); + .executionLink { + border-bottom: 1px solid var(--color-foreground-dark); + } +} diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue index a64b056ead..08babeb3ec 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue @@ -4,9 +4,10 @@ :executions="executions" :loading="loading" :loadingMore="loadingMore" + :temporaryExecution="temporaryExecution" @reloadExecutions="setExecutions" @filterUpdated="onFilterUpdated" - @loadMore="loadMore" + @loadMore="onLoadMore" @retryExecution="onRetryExecution" @refresh="loadAutoRefresh" /> @@ -55,7 +56,7 @@ import { Route } from 'vue-router'; import { executionHelpers } from '@/mixins/executionsHelpers'; import { range as _range } from 'lodash-es'; import { debounceHelper } from '@/mixins/debounce'; -import { getNodeViewTab, isEmpty, NO_NETWORK_ERROR_CODE } from '@/utils'; +import { getNodeViewTab, NO_NETWORK_ERROR_CODE } from '@/utils'; import { workflowHelpers } from '@/mixins/workflowHelpers'; import { mapStores } from 'pinia'; import { useWorkflowsStore } from '@/stores/workflows'; @@ -65,6 +66,11 @@ import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useTagsStore } from '@/stores/tags'; import { executionFilterToQueryFilter } from '@/utils/executionUtils'; +// Number of execution pages that are fetched before temporary execution card is shown +const MAX_LOADING_ATTEMPTS = 1; +// Number of executions fetched on each page +const LOAD_MORE_PAGE_SIZE = 10; + export default mixins( restApi, showMessage, @@ -81,6 +87,7 @@ export default mixins( loading: false, loadingMore: false, filter: {} as ExecutionFilterType, + temporaryExecution: null as IExecutionsSummary | null, }; }, computed: { @@ -234,6 +241,10 @@ export default mixins( if (currentExecutions.find((ex) => ex.id === newExecution.id) === undefined) { currentExecutions.push(newExecution); } + // If we loaded temp execution, put it into it's place and remove from top of the list + if (newExecution.id === this.temporaryExecution?.id) { + this.temporaryExecution = null; + } } this.workflowsStore.currentWorkflowExecutions = currentExecutions; this.loadingMore = false; @@ -250,6 +261,9 @@ export default mixins( this.executions[0]; await this.restApi().deleteExecutions({ ids: [this.$route.params.executionId] }); + if (this.temporaryExecution?.id === this.$route.params.executionId) { + this.temporaryExecution = null; + } if (this.executions.length > 0) { await this.$router .push({ @@ -371,7 +385,7 @@ export default mixins( this.workflowsStore.activeWorkflowExecution = updatedActiveExecution; } else { const activeInList = existingExecutions.some((ex) => ex.id === this.activeExecution?.id); - if (!activeInList && this.executions.length > 0) { + if (!activeInList && this.executions.length > 0 && !this.temporaryExecution) { this.$router .push({ name: VIEWS.EXECUTION_PREVIEW, @@ -422,7 +436,11 @@ export default mixins( } // If there is no execution in the route, select the first one - if (this.workflowsStore.activeWorkflowExecution === null && this.executions.length > 0) { + if ( + this.workflowsStore.activeWorkflowExecution === null && + this.executions.length > 0 && + !this.temporaryExecution + ) { this.workflowsStore.activeWorkflowExecution = this.executions[0]; this.$router .push({ @@ -435,8 +453,8 @@ export default mixins( async tryToFindExecution(executionId: string, attemptCount = 0): Promise { // First check if executions exists in the DB at all if (attemptCount === 0) { - const executionExists = await this.workflowsStore.fetchExecutionDataById(executionId); - if (!executionExists) { + const existingExecution = await this.workflowsStore.fetchExecutionDataById(executionId); + if (!existingExecution) { this.workflowsStore.activeWorkflowExecution = null; this.$showError( new Error( @@ -447,17 +465,21 @@ export default mixins( this.$locale.baseText('nodeView.showError.openExecution.title'), ); return; + } else { + this.temporaryExecution = existingExecution as IExecutionsSummary; } } - // stop if the execution wasn't found in the first 1000 lookups - if (attemptCount >= 10) { + if (attemptCount >= MAX_LOADING_ATTEMPTS) { + if (this.temporaryExecution) { + this.workflowsStore.activeWorkflowExecution = this.temporaryExecution; + return; + } this.workflowsStore.activeWorkflowExecution = null; return; } - // Fetch next batch of executions - await this.loadMore(100); + await this.loadMore(LOAD_MORE_PAGE_SIZE); const execution = this.workflowsStore.getExecutionDataById(executionId); if (!execution) { // If it's not there load next until found @@ -467,6 +489,7 @@ export default mixins( } else { // When found set execution as active this.workflowsStore.activeWorkflowExecution = execution; + this.temporaryExecution = null; return; } }, diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue index a186db53c7..4a126a33d3 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue @@ -36,7 +36,15 @@ + , + default: null, + }, }, data() { return { From 6d5c35d17f4f7bc18ba91d2553651c6b33771639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Sat, 1 Apr 2023 07:45:11 +0200 Subject: [PATCH 08/18] fix(editor): Update execution loading parameters after pushing test values (no-changelog) (#5876) --- .../src/components/ExecutionsView/ExecutionsList.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue index 08babeb3ec..e92af5cc4b 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue @@ -67,9 +67,9 @@ import { useTagsStore } from '@/stores/tags'; import { executionFilterToQueryFilter } from '@/utils/executionUtils'; // Number of execution pages that are fetched before temporary execution card is shown -const MAX_LOADING_ATTEMPTS = 1; +const MAX_LOADING_ATTEMPTS = 5; // Number of executions fetched on each page -const LOAD_MORE_PAGE_SIZE = 10; +const LOAD_MORE_PAGE_SIZE = 100; export default mixins( restApi, From f0a51a0b7671945b84e18774483dc7079f559845 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 1 Apr 2023 19:03:12 +0200 Subject: [PATCH 09/18] fix(core): Improve axios error handling in nodes (#5891) --- packages/core/src/NodeExecuteFunctions.ts | 2 +- packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 147cc61fb9..804c866845 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -707,7 +707,7 @@ async function proxyRequestToAxios( const message = `${response.status as number} - ${JSON.stringify(responseData)}`; throw Object.assign(new Error(message, { cause: error }), { - status: response.status, + statusCode: response.status, options: pick(config ?? {}, ['url', 'method', 'data', 'headers']), }); } else { diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts index 791927f979..d5da6c8eb0 100644 --- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts @@ -1310,7 +1310,7 @@ export class HttpRequestV3 implements INodeType { if (autoDetectResponseFormat && response.reason.error instanceof Buffer) { response.reason.error = Buffer.from(response.reason.error as Buffer).toString(); } - throw new NodeApiError(this.getNode(), response as JsonObject); + throw new NodeApiError(this.getNode(), response.reason as JsonObject); } else { // Return the actual reason as error returnItems.push({ From 695fabb28465d01caa85aefb1e873f88720ce304 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Mon, 3 Apr 2023 12:48:11 +0300 Subject: [PATCH 10/18] fix(Gmail Node): Gmail luxon object support, fix for timestamp --- .../nodes/Google/Gmail/GenericFunctions.ts | 128 ++++++++++-------- .../nodes/Google/Gmail/GmailTrigger.node.ts | 2 +- .../nodes/Google/Gmail/test/v2/utils.test.ts | 111 +++++++++++++++ .../nodes/Google/Gmail/v2/GmailV2.node.ts | 4 +- 4 files changed, 184 insertions(+), 61 deletions(-) create mode 100644 packages/nodes-base/nodes/Google/Gmail/test/v2/utils.test.ts diff --git a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts index 99fcce1943..e1fd977b2d 100644 --- a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts @@ -9,6 +9,7 @@ import type { IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions, + INode, INodeExecutionData, IPollFunctions, JsonObject, @@ -351,9 +352,64 @@ export function extractEmail(s: string) { return s; } +export const prepareTimestamp = ( + node: INode, + itemIndex: number, + query: string, + dateValue: string | number | DateTime, + label: 'after' | 'before', +) => { + if (dateValue instanceof DateTime) { + dateValue = dateValue.toISO(); + } + + let timestamp = DateTime.fromISO(dateValue as string).toSeconds(); + const timestampLengthInMilliseconds1990 = 12; + + if (typeof timestamp === 'number') { + timestamp = Math.round(timestamp); + } + + if ( + !timestamp && + typeof dateValue === 'number' && + dateValue.toString().length < timestampLengthInMilliseconds1990 + ) { + timestamp = dateValue; + } + + if (!timestamp && (dateValue as string).length < timestampLengthInMilliseconds1990) { + timestamp = parseInt(dateValue as string, 10); + } + + if (!timestamp) { + timestamp = Math.floor(DateTime.fromMillis(parseInt(dateValue as string, 10)).toSeconds()); + } + + if (!timestamp) { + const description = `'${dateValue}' isn't a valid date and time. If you're using an expression, be sure to set an ISO date string or a timestamp.`; + throw new NodeOperationError( + node, + `Invalid date/time in 'Received ${label[0].toUpperCase() + label.slice(1)}' field`, + { + description, + itemIndex, + }, + ); + } + + if (query) { + query += ` ${label}:${timestamp}`; + } else { + query = `${label}:${timestamp}`; + } + return query; +}; + export function prepareQuery( this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, fields: IDataObject, + itemIndex: number, ) { const qs: IDataObject = { ...fields }; if (qs.labelIds) { @@ -383,68 +439,24 @@ export function prepareQuery( } if (qs.receivedAfter) { - let timestamp = DateTime.fromISO(qs.receivedAfter as string).toSeconds(); - const timestampLengthInMilliseconds1990 = 12; - - if ( - !timestamp && - typeof qs.receivedAfter === 'number' && - qs.receivedAfter.toString().length < timestampLengthInMilliseconds1990 - ) { - timestamp = qs.receivedAfter; - } - - if (!timestamp && (qs.receivedAfter as string).length < timestampLengthInMilliseconds1990) { - timestamp = parseInt(qs.receivedAfter as string, 10); - } - - if (!timestamp) { - timestamp = Math.floor( - DateTime.fromMillis(parseInt(qs.receivedAfter as string, 10)).toSeconds(), - ); - } - - if (!timestamp) { - const description = `'${qs.receivedAfter}' isn't a valid date and time. If you're using an expression, be sure to set an ISO date string or a timestamp.`; - throw new NodeOperationError(this.getNode(), "Invalid date/time in 'Received After' field", { - description, - }); - } - - if (qs.q) { - qs.q += ` after:${timestamp}`; - } else { - qs.q = `after:${timestamp}`; - } + qs.q = prepareTimestamp( + this.getNode(), + itemIndex, + qs.q as string, + qs.receivedAfter as string, + 'after', + ); delete qs.receivedAfter; } if (qs.receivedBefore) { - let timestamp = DateTime.fromISO(qs.receivedBefore as string).toSeconds(); - const timestampLengthInMilliseconds1990 = 12; - - if (!timestamp && (qs.receivedBefore as string).length < timestampLengthInMilliseconds1990) { - timestamp = parseInt(qs.receivedBefore as string, 10); - } - - if (!timestamp) { - timestamp = Math.floor( - DateTime.fromMillis(parseInt(qs.receivedBefore as string, 10)).toSeconds(), - ); - } - - if (!timestamp) { - const description = `'${qs.receivedBefore}' isn't a valid date and time. If you're using an expression, be sure to set an ISO date string or a timestamp.`; - throw new NodeOperationError(this.getNode(), "Invalid date/time in 'Received Before' field", { - description, - }); - } - - if (qs.q) { - qs.q += ` before:${timestamp}`; - } else { - qs.q = `before:${timestamp}`; - } + qs.q = prepareTimestamp( + this.getNode(), + itemIndex, + qs.q as string, + qs.receivedBefore as string, + 'before', + ); delete qs.receivedBefore; } diff --git a/packages/nodes-base/nodes/Google/Gmail/GmailTrigger.node.ts b/packages/nodes-base/nodes/Google/Gmail/GmailTrigger.node.ts index ed2834d13e..73cef44222 100644 --- a/packages/nodes-base/nodes/Google/Gmail/GmailTrigger.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/GmailTrigger.node.ts @@ -246,7 +246,7 @@ export class GmailTrigger implements INodeType { delete filters.receivedAfter; } - Object.assign(qs, prepareQuery.call(this, filters), options); + Object.assign(qs, prepareQuery.call(this, filters, 0), options); responseData = await googleApiRequest.call( this, diff --git a/packages/nodes-base/nodes/Google/Gmail/test/v2/utils.test.ts b/packages/nodes-base/nodes/Google/Gmail/test/v2/utils.test.ts new file mode 100644 index 0000000000..91813b0273 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Gmail/test/v2/utils.test.ts @@ -0,0 +1,111 @@ +import type { INode } from 'n8n-workflow'; +import { prepareTimestamp } from '../../GenericFunctions'; + +import { DateTime } from 'luxon'; + +const node: INode = { + id: '1', + name: 'Gmail node', + typeVersion: 2, + type: 'n8n-nodes-base.gmail', + position: [50, 50], + parameters: { + operation: 'getAll', + }, +}; + +describe('Google Gmail v2, prepareTimestamp', () => { + it('should return a valid timestamp from ISO', () => { + const dateInput = '2020-01-01T00:00:00.000Z'; + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from integer in miliseconds', () => { + const dateInput = 1577836800000; + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from integer in seconds', () => { + const dateInput = 1577836800; + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from string in miliseconds', () => { + const dateInput = '1577836800000'; + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from string in seconds', () => { + const dateInput = '1577836800'; + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from luxon DateTime', () => { + const dateInput = DateTime.fromISO('2020-01-01T00:00:00.000Z'); + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should return a valid timestamp from luxon DateTime ISO', () => { + const dateInput = DateTime.fromISO('2020-01-01T00:00:00.000Z').toISO(); + const timestampBefore = prepareTimestamp(node, 0, '', dateInput, 'before'); + const timestampAfter = prepareTimestamp(node, 0, '', dateInput, 'after'); + + expect(timestampBefore).toBeDefined(); + expect(timestampBefore).toBe('before:1577836800'); + + expect(timestampAfter).toBeDefined(); + expect(timestampAfter).toBe('after:1577836800'); + }); + + it('should throw error on invalid data', () => { + const dateInput = 'invalid'; + expect(() => prepareTimestamp(node, 0, '', dateInput, 'before')).toThrow( + "Invalid date/time in 'Received Before' field", + ); + expect(() => prepareTimestamp(node, 0, '', dateInput, 'after')).toThrow( + "Invalid date/time in 'Received After' field", + ); + }); +}); diff --git a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts index 43a01aacb7..a21f3898c2 100644 --- a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts @@ -382,7 +382,7 @@ export class GmailV2 implements INodeType { const options = this.getNodeParameter('options', i, {}); const filters = this.getNodeParameter('filters', i, {}); const qs: IDataObject = {}; - Object.assign(qs, prepareQuery.call(this, filters), options); + Object.assign(qs, prepareQuery.call(this, filters, i), options, i); if (returnAll) { responseData = await googleApiRequestAllItems.call( @@ -708,7 +708,7 @@ export class GmailV2 implements INodeType { const returnAll = this.getNodeParameter('returnAll', i); const filters = this.getNodeParameter('filters', i); const qs: IDataObject = {}; - Object.assign(qs, prepareQuery.call(this, filters)); + Object.assign(qs, prepareQuery.call(this, filters, i)); if (returnAll) { responseData = await googleApiRequestAllItems.call( From 163446c674d07c060eaa0d7ec54a087a4cb671d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Apr 2023 12:14:41 +0200 Subject: [PATCH 11/18] fix(core): Fix the issue of nodes not loading when run via npx (#5888) --- packages/cli/src/LoadNodesAndCredentials.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 8048673425..47623076c4 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -67,9 +67,19 @@ export class LoadNodesAndCredentials implements INodesAndCredentials { this.downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath(); // Load nodes from `n8n-nodes-base` and any other `n8n-nodes-*` package in the main `node_modules` - await this.loadNodesFromNodeModules(CLI_DIR); - // Load nodes from installed community packages - await this.loadNodesFromNodeModules(this.downloadFolder); + const pathsToScan = [ + // In case "n8n" package is in same node_modules folder. + path.join(CLI_DIR, '..'), + // In case "n8n" package is the root and the packages are + // in the "node_modules" folder underneath it. + path.join(CLI_DIR, 'node_modules'), + // Path where all community nodes are installed + path.join(this.downloadFolder, 'node_modules'), + ]; + + for (const nodeModulesDir of pathsToScan) { + await this.loadNodesFromNodeModules(nodeModulesDir); + } await this.loadNodesFromCustomDirectories(); await this.postProcessLoaders(); @@ -117,8 +127,7 @@ export class LoadNodesAndCredentials implements INodesAndCredentials { await writeStaticJSON('credentials', this.types.credentials); } - private async loadNodesFromNodeModules(scanDir: string): Promise { - const nodeModulesDir = path.join(scanDir, 'node_modules'); + private async loadNodesFromNodeModules(nodeModulesDir: string): Promise { const globOptions = { cwd: nodeModulesDir, onlyDirectories: true }; const installedPackagePaths = [ ...(await glob('n8n-nodes-*', { ...globOptions, deep: 1 })), From f0954b94e164f0ff3e809849731137cf479670a4 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 3 Apr 2023 11:30:22 +0100 Subject: [PATCH 12/18] fix(AWS SNS Node): Fix an issue with messages failing to send if they contain certain characters (#5807) --- packages/nodes-base/nodes/Aws/AwsSns.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Aws/AwsSns.node.ts b/packages/nodes-base/nodes/Aws/AwsSns.node.ts index 3cd4d33f2e..cde82e341a 100644 --- a/packages/nodes-base/nodes/Aws/AwsSns.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsSns.node.ts @@ -302,8 +302,8 @@ export class AwsSns implements INodeType { const params = [ 'TopicArn=' + topic, - 'Subject=' + (this.getNodeParameter('subject', i) as string), - 'Message=' + (this.getNodeParameter('message', i) as string), + 'Subject=' + encodeURIComponent(this.getNodeParameter('subject', i) as string), + 'Message=' + encodeURIComponent(this.getNodeParameter('message', i) as string), ]; const responseData = await awsApiRequestSOAP.call( From de58fb9860d37a39a1e8963e304b8fc87f234bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Apr 2023 14:32:49 +0200 Subject: [PATCH 13/18] fix(core): Use table-prefixes in queries in import commands (#5887) --- packages/cli/src/commands/import/credentials.ts | 3 ++- packages/cli/src/commands/import/workflow.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/import/credentials.ts b/packages/cli/src/commands/import/credentials.ts index efed0a972c..985c70fe89 100644 --- a/packages/cli/src/commands/import/credentials.ts +++ b/packages/cli/src/commands/import/credentials.ts @@ -169,8 +169,9 @@ export class ImportCredentialsCommand extends BaseCommand { ['credentialsId', 'userId'], ); if (config.getEnv('database.type') === 'postgresdb') { + const tablePrefix = config.getEnv('database.tablePrefix'); await this.transactionManager.query( - "SELECT setval('credentials_entity_id_seq', (SELECT MAX(id) from credentials_entity))", + `SELECT setval('${tablePrefix}credentials_entity_id_seq', (SELECT MAX(id) from ${tablePrefix}credentials_entity))`, ); } } diff --git a/packages/cli/src/commands/import/workflow.ts b/packages/cli/src/commands/import/workflow.ts index df93f7f79f..5bbb956e76 100644 --- a/packages/cli/src/commands/import/workflow.ts +++ b/packages/cli/src/commands/import/workflow.ts @@ -215,8 +215,9 @@ export class ImportWorkflowsCommand extends BaseCommand { ['workflowId', 'userId'], ); if (config.getEnv('database.type') === 'postgresdb') { + const tablePrefix = config.getEnv('database.tablePrefix'); await this.transactionManager.query( - "SELECT setval('workflow_entity_id_seq', (SELECT MAX(id) from workflow_entity))", + `SELECT setval('${tablePrefix}workflow_entity_id_seq', (SELECT MAX(id) from "${tablePrefix}workflow_entity"))`, ); } } From 35cf783e83e666ade87474736e53447833fb1747 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Mon, 3 Apr 2023 16:04:59 +0300 Subject: [PATCH 14/18] fix: Add optional chaining for 'isArtificalRecoveredEventItem' (no-changelog) (#5860) * fix: add optional chaining for isArtificalRecoveredEventItem * fix: fix isArtificalRecoveredEventItem typo --- .../cli/src/eventbus/MessageEventBus/recoverEvents.ts | 2 +- packages/editor-ui/src/components/RunData.vue | 10 ++++++---- packages/editor-ui/src/mixins/pushConnection.ts | 2 +- packages/editor-ui/src/utils/nodeViewUtils.ts | 10 +++++----- packages/editor-ui/src/views/NodeView.vue | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/eventbus/MessageEventBus/recoverEvents.ts b/packages/cli/src/eventbus/MessageEventBus/recoverEvents.ts index af95df2e88..310d455e64 100644 --- a/packages/cli/src/eventbus/MessageEventBus/recoverEvents.ts +++ b/packages/cli/src/eventbus/MessageEventBus/recoverEvents.ts @@ -108,7 +108,7 @@ export async function recoverExecutionDataFromEventLogMessages( [ { json: { - isArtificalRecoveredEventItem: true, + isArtificialRecoveredEventItem: true, }, pairedItem: undefined, }, diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 70666e31f1..8b38a0a94f 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -162,7 +162,9 @@
@@ -216,7 +218,7 @@
-
+
@@ -680,8 +682,8 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers, pinData).exten this.hasPinData), ); }, - isArtificalRecoveredEventItem(): boolean { - return this.inputData?.[0]?.json?.isArtificalRecoveredEventItem !== undefined ?? false; + isArtificialRecoveredEventItem(): boolean { + return this.inputData?.[0]?.json?.isArtificialRecoveredEventItem !== undefined ?? false; }, subworkflowExecutionError(): Error | null { return this.workflowsStore.subWorkflowExecutionError; diff --git a/packages/editor-ui/src/mixins/pushConnection.ts b/packages/editor-ui/src/mixins/pushConnection.ts index d9478184bb..44da3de6c3 100644 --- a/packages/editor-ui/src/mixins/pushConnection.ts +++ b/packages/editor-ui/src/mixins/pushConnection.ts @@ -295,7 +295,7 @@ export const pushConnection = mixins( for (const key of Object.keys(activeRunData)) { if ( pushData.data.data.resultData.runData[key]?.[0]?.data?.main?.[0]?.[0]?.json - .isArtificalRecoveredEventItem === true && + ?.isArtificialRecoveredEventItem === true && activeRunData[key].length > 0 ) pushData.data.data.resultData.runData[key] = activeRunData[key]; diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index 9388ecc550..a626537ed1 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -544,7 +544,7 @@ export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputCo [targetInputIndex: string]: { total: number; iterations: number; - isArtificalRecoveredEventItem?: boolean; + isArtificialRecoveredEventItem?: boolean; }; }; }; @@ -558,10 +558,10 @@ export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputCo run.data.main.forEach((output: INodeExecutionData[] | null, i: number) => { const sourceOutputIndex = i; - // executionData that was recovered by recoverEvents in the CLI will have an isArtificalRecoveredEventItem property + // executionData that was recovered by recoverEvents in the CLI will have an isArtificialRecoveredEventItem property // to indicate that it was not part of the original executionData // we do not want to count these items in the summary - // if (output?.[0]?.json?.isArtificalRecoveredEventItem) { + // if (output?.[0]?.json?.isArtificialRecoveredEventItem) { // return outputMap; // } @@ -600,10 +600,10 @@ export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputCo }; } - if (output?.[0]?.json?.isArtificalRecoveredEventItem) { + if (output?.[0]?.json?.isArtificialRecoveredEventItem) { outputMap[sourceOutputIndex][targetNodeName][ targetInputIndex - ].isArtificalRecoveredEventItem = true; + ].isArtificialRecoveredEventItem = true; outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].total = 0; } else { outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].total += output diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index e6d1413722..0b0e2f8fbb 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2934,9 +2934,9 @@ export default mixins( if (connection) { const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex]; - if (output.isArtificalRecoveredEventItem) { + if (output.isArtificialRecoveredEventItem) { NodeViewUtils.recoveredConnection(connection); - } else if ((!output || !output.total) && !output.isArtificalRecoveredEventItem) { + } else if ((!output || !output.total) && !output.isArtificialRecoveredEventItem) { NodeViewUtils.resetConnection(connection); } else { NodeViewUtils.addConnectionOutputSuccess(connection, output); From 6906b00b0e734dbe59d1a3a91f07ec1007166b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Apr 2023 17:18:52 +0200 Subject: [PATCH 15/18] fix(core): Prevent augmentObject from creating infinitely deep proxies (#5893) fixes #5848 --- packages/workflow/src/AugmentObject.ts | 41 ++++++++++++-------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/workflow/src/AugmentObject.ts b/packages/workflow/src/AugmentObject.ts index a56fd7cb06..515f7a554c 100644 --- a/packages/workflow/src/AugmentObject.ts +++ b/packages/workflow/src/AugmentObject.ts @@ -1,18 +1,9 @@ import type { IDataObject } from './Interfaces'; const augmentedObjects = new WeakSet(); + function augment(value: T): T { - if ( - typeof value !== 'object' || - value === null || - value instanceof RegExp || - augmentedObjects.has(value) - ) - return value; - - // Track augmented objects to prevent infinite recursion in cases where an object contains circular references - augmentedObjects.add(value); - + if (typeof value !== 'object' || value === null || value instanceof RegExp) return value; if (value instanceof Date) return new Date(value.valueOf()) as T; // eslint-disable-next-line @typescript-eslint/no-use-before-define @@ -23,6 +14,8 @@ function augment(value: T): T { } export function augmentArray(data: T[]): T[] { + if (augmentedObjects.has(data)) return data; + let newData: unknown[] | undefined = undefined; function getData(): unknown[] { @@ -32,7 +25,7 @@ export function augmentArray(data: T[]): T[] { return newData; } - return new Proxy(data, { + const proxy = new Proxy(data, { deleteProperty(target, key: string) { return Reflect.deleteProperty(getData(), key); }, @@ -63,24 +56,25 @@ export function augmentArray(data: T[]): T[] { return Reflect.ownKeys(newData !== undefined ? newData : target); }, set(target, key: string, newValue: unknown) { - if (newValue !== null && typeof newValue === 'object') { - // Always proxy all objects. Like that we can check in get simply if it - // is a proxy and it does then not matter if it was already there from the - // beginning and it got proxied at some point or set later and so theoretically - // does not have to get proxied - newValue = new Proxy(newValue, {}); - } - - return Reflect.set(getData(), key, newValue); + // Always proxy all objects. Like that we can check in get simply if it + // is a proxy and it does then not matter if it was already there from the + // beginning and it got proxied at some point or set later and so theoretically + // does not have to get proxied + return Reflect.set(getData(), key, augment(newValue)); }, }); + + augmentedObjects.add(proxy); + return proxy; } export function augmentObject(data: T): T { + if (augmentedObjects.has(data)) return data; + const newData = {} as IDataObject; const deletedProperties: Array = []; - return new Proxy(data, { + const proxy = new Proxy(data, { get(target, key: string, receiver): unknown { if (deletedProperties.indexOf(key) !== -1) { return undefined; @@ -144,4 +138,7 @@ export function augmentObject(data: T): T { }; }, }); + + augmentedObjects.add(proxy); + return proxy; } From b1ee8f4d991ffc5017894f588e9bd21f652f23c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Apr 2023 17:19:12 +0200 Subject: [PATCH 16/18] fix(core): `augmentObject` should use existing property descriptors whenever possible (#5872) * fix(core): Augmented objects should use existing property descriptors whenever possible * add a test for non-enumerable keys --- packages/workflow/src/AugmentObject.ts | 18 ++++++++++-------- packages/workflow/test/AugmentObject.test.ts | 8 ++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/workflow/src/AugmentObject.ts b/packages/workflow/src/AugmentObject.ts index 515f7a554c..d991e7fab7 100644 --- a/packages/workflow/src/AugmentObject.ts +++ b/packages/workflow/src/AugmentObject.ts @@ -1,5 +1,7 @@ import type { IDataObject } from './Interfaces'; +const defaultPropertyDescriptor = Object.freeze({ enumerable: true, configurable: true }); + const augmentedObjects = new WeakSet(); function augment(value: T): T { @@ -47,7 +49,8 @@ export function augmentArray(data: T[]): T[] { if (key === 'length') { return Reflect.getOwnPropertyDescriptor(newData, key); } - return { configurable: true, enumerable: true }; + + return Object.getOwnPropertyDescriptor(data, key) ?? defaultPropertyDescriptor; }, has(target, key) { return Reflect.has(newData !== undefined ? newData : target, key); @@ -124,18 +127,17 @@ export function augmentObject(data: T): T { return true; }, + ownKeys(target) { - return [...new Set([...Reflect.ownKeys(target), ...Object.keys(newData)])].filter( + const originalKeys = Reflect.ownKeys(target); + const newKeys = Object.keys(newData); + return [...new Set([...originalKeys, ...newKeys])].filter( (key) => deletedProperties.indexOf(key) === -1, ); }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getOwnPropertyDescriptor(k) { - return { - enumerable: true, - configurable: true, - }; + getOwnPropertyDescriptor(target, key) { + return Object.getOwnPropertyDescriptor(data, key) ?? defaultPropertyDescriptor; }, }); diff --git a/packages/workflow/test/AugmentObject.test.ts b/packages/workflow/test/AugmentObject.test.ts index f30989aec3..e9fe6ee948 100644 --- a/packages/workflow/test/AugmentObject.test.ts +++ b/packages/workflow/test/AugmentObject.test.ts @@ -520,5 +520,13 @@ describe('AugmentObject', () => { expect(timeAugmented).toBeLessThan(timeCopied); }); + + test('should ignore non-enumerable keys', () => { + const originalObject = { a: 1, b: 2 }; + Object.defineProperty(originalObject, '__hiddenProp', { enumerable: false }); + + const augmentedObject = augmentObject(originalObject); + expect(Object.keys(augmentedObject)).toEqual(['a', 'b']); + }); }); }); From 0be129254e96c822dceddbfffaf36e0e6b2ef5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Apr 2023 18:37:07 +0200 Subject: [PATCH 17/18] fix(HTTP Request Node): Detect mime-type from streaming responses (#5896) --- packages/core/src/NodeExecuteFunctions.ts | 44 ++++++++++++++--------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 804c866845..846338e6f1 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -77,6 +77,7 @@ import { import pick from 'lodash.pick'; import { Agent } from 'https'; +import { IncomingMessage } from 'http'; import { stringify } from 'qs'; import type { Token } from 'oauth-1.0a'; import clientOAuth1 from 'oauth-1.0a'; @@ -932,15 +933,17 @@ export async function copyBinaryFile( fileExtension = fileTypeData.ext; } } + } - if (!mimeType) { - // Fall back to text - mimeType = 'text/plain'; - } - } else if (!fileExtension) { + if (!fileExtension && mimeType) { fileExtension = extension(mimeType) || undefined; } + if (!mimeType) { + // Fall back to text + mimeType = 'text/plain'; + } + const returnData: IBinaryData = { mimeType, fileType: fileTypeFromMimeType(mimeType), @@ -979,24 +982,31 @@ async function prepareBinaryData( } } - // TODO: detect filetype from streams - if (!mimeType && Buffer.isBuffer(binaryData)) { - // Use buffer to guess mime type - const fileTypeData = await FileType.fromBuffer(binaryData); - if (fileTypeData) { - mimeType = fileTypeData.mime; - fileExtension = fileTypeData.ext; + if (!mimeType) { + if (Buffer.isBuffer(binaryData)) { + // Use buffer to guess mime type + const fileTypeData = await FileType.fromBuffer(binaryData); + if (fileTypeData) { + mimeType = fileTypeData.mime; + fileExtension = fileTypeData.ext; + } + } else if (binaryData instanceof IncomingMessage) { + mimeType = binaryData.headers['content-type']; + } else { + // TODO: detect filetype from other kind of streams } } + } - if (!mimeType) { - // Fall back to text - mimeType = 'text/plain'; - } - } else if (!fileExtension) { + if (!fileExtension && mimeType) { fileExtension = extension(mimeType) || undefined; } + if (!mimeType) { + // Fall back to text + mimeType = 'text/plain'; + } + const returnData: IBinaryData = { mimeType, fileType: fileTypeFromMimeType(mimeType), From 64fa80fe8a3a508b38c19234f201758d6089daee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:36:08 +0200 Subject: [PATCH 18/18] :rocket: Release 0.222.1 (#5897) --- .github/workflows/docker-images.yml | 2 -- .github/workflows/release-publish.yml | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ package.json | 2 +- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- packages/editor-ui/package.json | 2 +- packages/node-dev/package.json | 2 +- packages/nodes-base/package.json | 2 +- packages/workflow/package.json | 2 +- packages/workflow/src/AugmentObject.ts | 1 + 11 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index fde545ff6d..0064f7eabc 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -49,6 +49,4 @@ jobs: push: true tags: | ${{ secrets.DOCKER_USERNAME }}/n8n:${{ steps.vars.outputs.tag }}${{ matrix.docker-context }} - ${{ secrets.DOCKER_USERNAME }}/n8n:latest${{ matrix.docker-context }} ghcr.io/${{ github.repository_owner }}/n8n:${{ steps.vars.outputs.tag }}${{ matrix.docker-context }} - ghcr.io/${{ github.repository_owner }}/n8n:latest${{ matrix.docker-context }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index b8b03ab8e1..9375defd37 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -37,7 +37,7 @@ jobs: - name: Publish to NPM run: | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public + pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public --tag rc echo "RELEASE=$(node -e 'console.log(require("./package.json").version)')" >> $GITHUB_ENV - name: Create Release diff --git a/CHANGELOG.md b/CHANGELOG.md index a4b09477f4..285c37d903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## [0.222.1](https://github.com/n8n-io/n8n/compare/n8n@0.222.0...n8n@0.222.1) (2023-04-04) + + +### Bug Fixes + +* **AWS SNS Node:** Fix an issue with messages failing to send if they contain certain characters ([#5807](https://github.com/n8n-io/n8n/issues/5807)) ([f0954b9](https://github.com/n8n-io/n8n/commit/f0954b94e164f0ff3e809849731137cf479670a4)) +* **core:** `augmentObject` should clone Buffer/Uint8Array instead of wrapping them in a proxy ([#5902](https://github.com/n8n-io/n8n/issues/5902)) ([a877b02](https://github.com/n8n-io/n8n/commit/a877b025b8c93a31822e7ccb5b935dca00595439)) +* **core:** `augmentObject` should use existing property descriptors whenever possible ([#5872](https://github.com/n8n-io/n8n/issues/5872)) ([b1ee8f4](https://github.com/n8n-io/n8n/commit/b1ee8f4d991ffc5017894f588e9bd21f652f23c6)) +* **core:** Fix the issue of nodes not loading when run via npx ([#5888](https://github.com/n8n-io/n8n/issues/5888)) ([163446c](https://github.com/n8n-io/n8n/commit/163446c674d07c060eaa0d7ec54a087a4cb671d5)) +* **core:** Improve axios error handling in nodes ([#5891](https://github.com/n8n-io/n8n/issues/5891)) ([f0a51a0](https://github.com/n8n-io/n8n/commit/f0a51a0b7671945b84e18774483dc7079f559845)) +* **core:** Password reset should pass in the correct values to external hooks ([#5842](https://github.com/n8n-io/n8n/issues/5842)) ([3bf267c](https://github.com/n8n-io/n8n/commit/3bf267c14757fa67dcfa0edf0b4db74ab1b7415c)) +* **core:** Prevent augmentObject from creating infinitely deep proxies ([#5893](https://github.com/n8n-io/n8n/issues/5893)) ([6906b00](https://github.com/n8n-io/n8n/commit/6906b00b0e734dbe59d1a3a91f07ec1007166b72)), closes [#5848](https://github.com/n8n-io/n8n/issues/5848) +* **core:** Use table-prefixes in queries in import commands ([#5887](https://github.com/n8n-io/n8n/issues/5887)) ([de58fb9](https://github.com/n8n-io/n8n/commit/de58fb9860d37a39a1e8963e304b8fc87f234bf6)) +* **editor:** Fix focused state in Code node editor ([#5869](https://github.com/n8n-io/n8n/issues/5869)) ([3be37e2](https://github.com/n8n-io/n8n/commit/3be37e25a52983b43b4eed3847922e99f2bf07bc)) +* **editor:** Fix loading executions in long execution list ([#5843](https://github.com/n8n-io/n8n/issues/5843)) ([d5d9f58](https://github.com/n8n-io/n8n/commit/d5d9f58f1777040c63a2a0aa3c0d7bf632dc7b26)) +* **editor:** Show correct status on canceled executions ([#5813](https://github.com/n8n-io/n8n/issues/5813)) ([00181cd](https://github.com/n8n-io/n8n/commit/00181cd803f5a0cca50b5fc1ab84d0a26dd618ae)) +* **Gmail Node:** Gmail luxon object support, fix for timestamp ([695fabb](https://github.com/n8n-io/n8n/commit/695fabb28465d01caa85aefb1e873f88720ce304)) +* **HTTP Request Node:** Detect mime-type from streaming responses ([#5896](https://github.com/n8n-io/n8n/issues/5896)) ([0be1292](https://github.com/n8n-io/n8n/commit/0be129254e96c822dceddbfffaf36e0e6b2ef5e8)) +* **HubSpot Trigger Node:** Developer API key is required for webhooks ([918c79c](https://github.com/n8n-io/n8n/commit/918c79c137f781764f11ad3a33ead337efce681a)) +* **Set Node:** Convert string to number ([72eea0d](https://github.com/n8n-io/n8n/commit/72eea0dfb9e679bde95996d99684e23d081db5a7)) + + + # [0.222.0](https://github.com/n8n-io/n8n/compare/n8n@0.221.2...n8n@0.222.0) (2023-03-30) diff --git a/package.json b/package.json index 67d4ca3610..c906b2f5fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.222.0", + "version": "0.222.1", "private": true, "homepage": "https://n8n.io", "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 295080b0c9..c5b8663d82 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.222.0", + "version": "0.222.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/core/package.json b/packages/core/package.json index c00bfb8e89..bce01db33f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.161.0", + "version": "0.161.1", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index fea032dd24..bfeb9bab45 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.188.0", + "version": "0.188.1", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 8cc7447d75..0ac324674c 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.100.0", + "version": "0.100.1", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 34e94c2429..ccb9462907 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.220.0", + "version": "0.220.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index d1d59f4b12..aae671e7f8 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.143.0", + "version": "0.143.1", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/workflow/src/AugmentObject.ts b/packages/workflow/src/AugmentObject.ts index d991e7fab7..80a80b905a 100644 --- a/packages/workflow/src/AugmentObject.ts +++ b/packages/workflow/src/AugmentObject.ts @@ -7,6 +7,7 @@ const augmentedObjects = new WeakSet(); function augment(value: T): T { if (typeof value !== 'object' || value === null || value instanceof RegExp) return value; if (value instanceof Date) return new Date(value.valueOf()) as T; + if (value instanceof Uint8Array) return value.slice() as T; // eslint-disable-next-line @typescript-eslint/no-use-before-define if (Array.isArray(value)) return augmentArray(value) as T;