From 2010c5a3cd4d3f5c4aef16869c0611035725483f Mon Sep 17 00:00:00 2001 From: Cornelius Suermann Date: Fri, 24 May 2024 16:58:33 +0200 Subject: [PATCH 01/20] build: Bump license-sdk to v2.11.1 (no-changelog) --- packages/cli/package.json | 2 +- pnpm-lock.yaml | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e8beba4b56b5a..d94a6163a5f1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -99,7 +99,7 @@ "@n8n/n8n-nodes-langchain": "workspace:*", "@n8n/permissions": "workspace:*", "@n8n/typeorm": "0.3.20-9", - "@n8n_io/license-sdk": "2.10.0", + "@n8n_io/license-sdk": "2.11.1", "@oclif/core": "3.18.1", "@pinecone-database/pinecone": "2.1.0", "@rudderstack/rudder-sdk-node": "2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2f9059b8218b..31e205f6f9b84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -523,8 +523,8 @@ importers: specifier: 0.3.20-9 version: 0.3.20-9(@sentry/node@7.87.0)(ioredis@5.3.2)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.7) '@n8n_io/license-sdk': - specifier: 2.10.0 - version: 2.10.0 + specifier: 2.11.1 + version: 2.11.1 '@oclif/core': specifier: 3.18.1 version: 3.18.1 @@ -7067,14 +7067,14 @@ packages: acorn-walk: 8.2.0 dev: false - /@n8n_io/license-sdk@2.10.0: - resolution: {integrity: sha512-o6jJSQJ80uGGgGCaSr0NPVGpJd3ePR+fGBQ7MtnNKxxQy3m4iX8reKGoLAphANNzQHnXVFHqo7U2zXlI8h4ZvA==} - engines: {node: '>=18.12.1', npm: '>=8.19.2'} + /@n8n_io/license-sdk@2.11.1: + resolution: {integrity: sha512-7bv2Wu4+29VELXbePpGNtzwFkc0/AFWYDG46PRbhdFMUMvUhK2q3N/J/HW5qHzXrQpOvL7fZGbzGxUQi8AdvAQ==} + engines: {node: '>=18.12.1'} dependencies: crypto-js: 4.2.0 node-machine-id: 1.1.12 node-rsa: 1.1.1 - undici: 6.4.0 + undici: 6.18.1 dev: false /@n8n_io/riot-tmpl@4.0.0: @@ -23986,11 +23986,9 @@ packages: '@fastify/busboy': 2.0.0 dev: false - /undici@6.4.0: - resolution: {integrity: sha512-wYaKgftNqf6Je7JQ51YzkEkEevzOgM7at5JytKO7BjaURQpERW8edQSMrr2xb+Yv4U8Yg47J24+lc9+NbeXMFA==} - engines: {node: '>=18.0'} - dependencies: - '@fastify/busboy': 2.0.0 + /undici@6.18.1: + resolution: {integrity: sha512-/0BWqR8rJNRysS5lqVmfc7eeOErcOP4tZpATVjJOojjHZ71gSYVAtFhEmadcIjwMIUehh5NFyKGsXCnXIajtbA==} + engines: {node: '>=18.17'} dev: false /unicode-canonical-property-names-ecmascript@2.0.0: From 36b99cd277fc1b61c1005d7d9d616889d28fcb79 Mon Sep 17 00:00:00 2001 From: Cornelius Suermann Date: Fri, 24 May 2024 17:01:39 +0200 Subject: [PATCH 02/20] Revert "build: Bump license-sdk to v2.11.1 (no-changelog)" This reverts commit 2010c5a3cd4d3f5c4aef16869c0611035725483f. --- packages/cli/package.json | 2 +- pnpm-lock.yaml | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index d94a6163a5f1e..e8beba4b56b5a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -99,7 +99,7 @@ "@n8n/n8n-nodes-langchain": "workspace:*", "@n8n/permissions": "workspace:*", "@n8n/typeorm": "0.3.20-9", - "@n8n_io/license-sdk": "2.11.1", + "@n8n_io/license-sdk": "2.10.0", "@oclif/core": "3.18.1", "@pinecone-database/pinecone": "2.1.0", "@rudderstack/rudder-sdk-node": "2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31e205f6f9b84..b2f9059b8218b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -523,8 +523,8 @@ importers: specifier: 0.3.20-9 version: 0.3.20-9(@sentry/node@7.87.0)(ioredis@5.3.2)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.7) '@n8n_io/license-sdk': - specifier: 2.11.1 - version: 2.11.1 + specifier: 2.10.0 + version: 2.10.0 '@oclif/core': specifier: 3.18.1 version: 3.18.1 @@ -7067,14 +7067,14 @@ packages: acorn-walk: 8.2.0 dev: false - /@n8n_io/license-sdk@2.11.1: - resolution: {integrity: sha512-7bv2Wu4+29VELXbePpGNtzwFkc0/AFWYDG46PRbhdFMUMvUhK2q3N/J/HW5qHzXrQpOvL7fZGbzGxUQi8AdvAQ==} - engines: {node: '>=18.12.1'} + /@n8n_io/license-sdk@2.10.0: + resolution: {integrity: sha512-o6jJSQJ80uGGgGCaSr0NPVGpJd3ePR+fGBQ7MtnNKxxQy3m4iX8reKGoLAphANNzQHnXVFHqo7U2zXlI8h4ZvA==} + engines: {node: '>=18.12.1', npm: '>=8.19.2'} dependencies: crypto-js: 4.2.0 node-machine-id: 1.1.12 node-rsa: 1.1.1 - undici: 6.18.1 + undici: 6.4.0 dev: false /@n8n_io/riot-tmpl@4.0.0: @@ -23986,9 +23986,11 @@ packages: '@fastify/busboy': 2.0.0 dev: false - /undici@6.18.1: - resolution: {integrity: sha512-/0BWqR8rJNRysS5lqVmfc7eeOErcOP4tZpATVjJOojjHZ71gSYVAtFhEmadcIjwMIUehh5NFyKGsXCnXIajtbA==} - engines: {node: '>=18.17'} + /undici@6.4.0: + resolution: {integrity: sha512-wYaKgftNqf6Je7JQ51YzkEkEevzOgM7at5JytKO7BjaURQpERW8edQSMrr2xb+Yv4U8Yg47J24+lc9+NbeXMFA==} + engines: {node: '>=18.0'} + dependencies: + '@fastify/busboy': 2.0.0 dev: false /unicode-canonical-property-names-ecmascript@2.0.0: From e8c824bad6b7feb3f78aa487e4ae290ae818b587 Mon Sep 17 00:00:00 2001 From: Cornelius Suermann Date: Sat, 25 May 2024 12:01:17 +0200 Subject: [PATCH 03/20] build: Bump license-sdk to v2.12.0 (no-changelog) (#9510) --- packages/cli/package.json | 2 +- pnpm-lock.yaml | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e8beba4b56b5a..e9ebe137bdd63 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -99,7 +99,7 @@ "@n8n/n8n-nodes-langchain": "workspace:*", "@n8n/permissions": "workspace:*", "@n8n/typeorm": "0.3.20-9", - "@n8n_io/license-sdk": "2.10.0", + "@n8n_io/license-sdk": "2.12.0", "@oclif/core": "3.18.1", "@pinecone-database/pinecone": "2.1.0", "@rudderstack/rudder-sdk-node": "2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2f9059b8218b..091cf59f30b46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -523,8 +523,8 @@ importers: specifier: 0.3.20-9 version: 0.3.20-9(@sentry/node@7.87.0)(ioredis@5.3.2)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.7) '@n8n_io/license-sdk': - specifier: 2.10.0 - version: 2.10.0 + specifier: 2.12.0 + version: 2.12.0 '@oclif/core': specifier: 3.18.1 version: 3.18.1 @@ -7067,14 +7067,14 @@ packages: acorn-walk: 8.2.0 dev: false - /@n8n_io/license-sdk@2.10.0: - resolution: {integrity: sha512-o6jJSQJ80uGGgGCaSr0NPVGpJd3ePR+fGBQ7MtnNKxxQy3m4iX8reKGoLAphANNzQHnXVFHqo7U2zXlI8h4ZvA==} - engines: {node: '>=18.12.1', npm: '>=8.19.2'} + /@n8n_io/license-sdk@2.12.0: + resolution: {integrity: sha512-hxXdaFlzd1moR5NWPCdGiKHyIuDTuiM8AYcH5qo2/Cbu93C8za8a0x19q5gZ1ahADRPfO48k9eYMTjxGhqNkBg==} + engines: {node: '>=18.12.1'} dependencies: crypto-js: 4.2.0 node-machine-id: 1.1.12 node-rsa: 1.1.1 - undici: 6.4.0 + undici: 6.18.1 dev: false /@n8n_io/riot-tmpl@4.0.0: @@ -23986,11 +23986,9 @@ packages: '@fastify/busboy': 2.0.0 dev: false - /undici@6.4.0: - resolution: {integrity: sha512-wYaKgftNqf6Je7JQ51YzkEkEevzOgM7at5JytKO7BjaURQpERW8edQSMrr2xb+Yv4U8Yg47J24+lc9+NbeXMFA==} - engines: {node: '>=18.0'} - dependencies: - '@fastify/busboy': 2.0.0 + /undici@6.18.1: + resolution: {integrity: sha512-/0BWqR8rJNRysS5lqVmfc7eeOErcOP4tZpATVjJOojjHZ71gSYVAtFhEmadcIjwMIUehh5NFyKGsXCnXIajtbA==} + engines: {node: '>=18.17'} dev: false /unicode-canonical-property-names-ecmascript@2.0.0: From 8164ca2398a6dbbf32f75fe9df8844cfc64a10fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 27 May 2024 11:50:57 +0200 Subject: [PATCH 04/20] fix(core): Fix worker encryption key warning docs link (no-changelog) (#9513) --- packages/cli/src/commands/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index 28beddb49f5a9..c2aa66bf9fdb7 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -234,7 +234,7 @@ export class Worker extends BaseCommand { if (!process.env.N8N_ENCRYPTION_KEY) { throw new ApplicationError( - 'Missing encryption key. Worker started without the required N8N_ENCRYPTION_KEY env var. More information: https://docs.n8n.io/hosting/environment-variables/configuration-methods/#encryption-key', + 'Missing encryption key. Worker started without the required N8N_ENCRYPTION_KEY env var. More information: https://docs.n8n.io/hosting/configuration/configuration-examples/encryption-key/', ); } From 3a2e5455a98dae35ba1a52ec98f67a1fb27fac96 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Mon, 27 May 2024 12:44:04 +0200 Subject: [PATCH 05/20] fix(editor): Make sure auto loading and auto scrolling works in executions tab (#9505) --- cypress/e2e/20-workflow-executions.cy.ts | 39 ++++++++++++++ .../workflow/WorkflowExecutionsCard.vue | 4 ++ .../workflow/WorkflowExecutionsSidebar.vue | 51 +++++++++++++++---- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 37036a7971d6e..0773d6f8fc35c 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -71,6 +71,45 @@ describe('Current Workflow Executions', () => { cy.wait(executionsRefreshInterval); cy.url().should('not.include', '/executions'); }); + + it.only('should auto load more items if there is space and auto scroll', () => { + cy.viewport(1280, 960); + executionsTab.actions.createManualExecutions(24); + + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + cy.intercept('GET', '/rest/executions/*').as('getExecution'); + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + executionsTab.getters.executionListItems().its('length').should('be.gte', 10); + + cy.getByTestId('current-executions-list').scrollTo('bottom'); + cy.wait(['@getExecutions']); + executionsTab.getters.executionListItems().should('have.length', 24); + + executionsTab.getters.executionListItems().eq(14).click(); + cy.wait(['@getExecution']); + cy.reload(); + + cy.wait(['@getExecutions']); + executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); + executionsTab.getters.executionListItems().should('have.length', 24); + executionsTab.getters.executionListItems().first().should('not.be.visible'); + cy.getByTestId('current-executions-list').scrollTo(0, 0); + executionsTab.getters.executionListItems().first().should('be.visible'); + executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); + + executionsTab.actions.switchToEditorTab(); + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); + executionsTab.getters.executionListItems().should('have.length', 24); + executionsTab.getters.executionListItems().first().should('not.be.visible'); + cy.getByTestId('current-executions-list').scrollTo(0, 0); + executionsTab.getters.executionListItems().first().should('be.visible'); + executionsTab.getters.executionListItems().eq(14).should('not.be.visible'); + }); }); const createMockExecutions = () => { diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue index d178273ca53a0..fc4907745fc4b 100644 --- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue +++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue @@ -110,6 +110,7 @@ export default defineComponent({ default: false, }, }, + emits: ['retryExecution', 'mounted'], setup() { const executionHelpers = useExecutionHelpers(); @@ -147,6 +148,9 @@ export default defineComponent({ return VIEWS.EXECUTION_PREVIEW; }, }, + mounted() { + this.$emit('mounted', this.execution.id); + }, methods: { onRetryMenuItemSelect(action: string): void { this.$emit('retryExecution', { execution: this.execution, command: action }); diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue index d0f5fc7e41317..1b5553259ed49 100644 --- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue +++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue @@ -53,6 +53,7 @@ :execution="execution" :data-test-id="`execution-details-${execution.id}`" @retry-execution="onRetryExecution" + @mounted="onItemMounted" />
@@ -80,6 +81,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import type { ExecutionFilterType } from '@/Interface'; type WorkflowExecutionsCardRef = InstanceType; +type AutoScrollDeps = { activeExecutionSet: boolean; cardsMounted: boolean; scroll: boolean }; export default defineComponent({ name: 'WorkflowExecutionsSidebar', @@ -117,6 +119,12 @@ export default defineComponent({ data() { return { filter: {} as ExecutionFilterType, + mountedItems: [] as string[], + autoScrollDeps: { + activeExecutionSet: false, + cardsMounted: false, + scroll: true, + } as AutoScrollDeps, }; }, computed: { @@ -129,16 +137,35 @@ export default defineComponent({ this.$router.go(-1); } }, - }, - mounted() { - // On larger screens, we need to load more then first page of executions - // for the scroll bar to appear and infinite scrolling is enabled - this.checkListSize(); - setTimeout(() => { - this.scrollToActiveCard(); - }, 1000); + 'executionsStore.activeExecution'( + newValue: ExecutionSummary | null, + oldValue: ExecutionSummary | null, + ) { + if (newValue && newValue.id !== oldValue?.id) { + this.autoScrollDeps.activeExecutionSet = true; + } + }, + autoScrollDeps: { + handler(updatedDeps: AutoScrollDeps) { + if (Object.values(updatedDeps).every(Boolean)) { + this.scrollToActiveCard(); + } + }, + deep: true, + }, }, methods: { + onItemMounted(id: string): void { + this.mountedItems.push(id); + if (this.mountedItems.length === this.executions.length) { + this.autoScrollDeps.cardsMounted = true; + this.checkListSize(); + } + + if (this.executionsStore.activeExecution?.id === id) { + this.autoScrollDeps.activeExecutionSet = true; + } + }, loadMore(limit = 20): void { if (!this.loading) { const executionsListRef = this.$refs.executionList as HTMLElement | undefined; @@ -167,7 +194,7 @@ export default defineComponent({ checkListSize(): void { const sidebarContainerRef = this.$refs.container as HTMLElement | undefined; const currentWorkflowExecutionsCardRefs = this.$refs[ - `execution-${this.executionsStore.activeExecution?.id}` + `execution-${this.mountedItems[this.mountedItems.length - 1]}` ] as WorkflowExecutionsCardRef[] | undefined; // Find out how many execution card can fit into list @@ -196,7 +223,11 @@ export default defineComponent({ const cardRect = cardElement.getBoundingClientRect(); const LIST_HEADER_OFFSET = 200; if (cardRect.top > executionsListRef.offsetHeight) { - executionsListRef.scrollTo({ top: cardRect.top - LIST_HEADER_OFFSET }); + this.autoScrollDeps.scroll = false; + executionsListRef.scrollTo({ + top: cardRect.top - LIST_HEADER_OFFSET, + behavior: 'smooth', + }); } } }, From 49b5bd70f0d1c0dce46ea85d23deb75dbea6c51c Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Mon, 27 May 2024 12:44:25 +0200 Subject: [PATCH 06/20] fix(editor): Send only execution id in postMessage when previewing an execution (#9514) --- packages/editor-ui/src/components/WorkflowPreview.vue | 2 +- .../src/components/__tests__/WorkflowPreview.test.ts | 2 +- packages/editor-ui/src/views/NodeView.vue | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/components/WorkflowPreview.vue b/packages/editor-ui/src/components/WorkflowPreview.vue index 106706262d20c..3b8c9d5c2f42a 100644 --- a/packages/editor-ui/src/components/WorkflowPreview.vue +++ b/packages/editor-ui/src/components/WorkflowPreview.vue @@ -119,7 +119,7 @@ const loadExecution = () => { iframeRef.value?.contentWindow?.postMessage?.( JSON.stringify({ command: 'setActiveExecution', - execution: executionsStore.activeExecution, + executionId: executionsStore.activeExecution.id, }), '*', ); diff --git a/packages/editor-ui/src/components/__tests__/WorkflowPreview.test.ts b/packages/editor-ui/src/components/__tests__/WorkflowPreview.test.ts index c2505912a4326..ad91cd3a24153 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowPreview.test.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowPreview.test.ts @@ -179,7 +179,7 @@ describe('WorkflowPreview', () => { expect(postMessageSpy).toHaveBeenCalledWith( JSON.stringify({ command: 'setActiveExecution', - execution: { id: 'abc' }, + executionId: 'abc', }), '*', ); diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 34c74bb1b2141..fc56368401996 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -4765,7 +4765,9 @@ export default defineComponent({ }); } } else if (json?.command === 'setActiveExecution') { - this.executionsStore.activeExecution = json.execution; + this.executionsStore.activeExecution = (await this.executionsStore.fetchExecution( + json.executionId, + )) as ExecutionSummary; } } catch (e) {} }, From 008f62aaf41eb1bbe797cd48ff46b3641df036c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 27 May 2024 13:55:52 +0200 Subject: [PATCH 07/20] refactor: Delete dead crash recovery code (no-changelog) (#9512) --- .../src/eventbus/EventMessageClasses/index.ts | 8 ---- .../MessageEventBus/MessageEventBus.ts | 48 +------------------ .../cli/src/eventbus/eventBus.controller.ts | 31 +----------- packages/cli/src/permissions/global-roles.ts | 2 - packages/editor-ui/src/api/eventbus.ee.ts | 7 --- 5 files changed, 3 insertions(+), 93 deletions(-) diff --git a/packages/cli/src/eventbus/EventMessageClasses/index.ts b/packages/cli/src/eventbus/EventMessageClasses/index.ts index cecfcdbaf5188..65c323b873ab3 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/index.ts +++ b/packages/cli/src/eventbus/EventMessageClasses/index.ts @@ -66,11 +66,3 @@ export type EventMessageTypes = | EventMessageAudit | EventMessageNode | EventMessageAiNode; - -export interface FailedEventSummary { - lastNodeExecuted: string; - executionId: string; - name: string; - event: string; - timestamp: string; -} diff --git a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts b/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts index 2f8626f6e6cec..6bc7d7b6a7d9c 100644 --- a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts +++ b/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts @@ -13,11 +13,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { OrchestrationService } from '@/services/orchestration.service'; import { Logger } from '@/Logger'; -import type { - EventMessageTypes, - EventNamesTypes, - FailedEventSummary, -} from '../EventMessageClasses/'; +import type { EventMessageTypes } from '../EventMessageClasses/'; import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee'; import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventBusLogWriter'; import { messageEventBusDestinationFromDb } from '../MessageEventBusDestination/MessageEventBusDestinationFromDb'; @@ -361,48 +357,6 @@ export class MessageEventBus extends EventEmitter { ); } - async getEventsFailed(amount = 5): Promise { - const result: FailedEventSummary[] = []; - try { - const queryResult = await this.logWriter?.getMessagesAll(); - const uniques = uniqby(queryResult, 'id'); - const filteredExecutionIds = uniques - .filter((e) => - (['n8n.workflow.crashed', 'n8n.workflow.failed'] as EventNamesTypes[]).includes( - e.eventName, - ), - ) - .map((e) => ({ - executionId: e.payload.executionId as string, - name: e.payload.workflowName, - timestamp: e.ts, - event: e.eventName, - })) - .filter((e) => e) - .sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)) - .slice(-amount); - - for (const execution of filteredExecutionIds) { - const data = await this.recoveryService.recoverExecutionData( - execution.executionId, - queryResult, - false, - ); - if (data) { - const lastNodeExecuted = data.resultData.lastNodeExecuted; - result.push({ - lastNodeExecuted: lastNodeExecuted ?? '', - executionId: execution.executionId, - name: execution.name as string, - event: execution.event, - timestamp: execution.timestamp.toISO(), - }); - } - } - } catch {} - return result; - } - async getEventsAll(): Promise { const queryResult = await this.logWriter?.getMessagesAll(); const filtered = uniqby(queryResult, 'id'); diff --git a/packages/cli/src/eventbus/eventBus.controller.ts b/packages/cli/src/eventbus/eventBus.controller.ts index 179d44da41668..3f73227e47b6a 100644 --- a/packages/cli/src/eventbus/eventBus.controller.ts +++ b/packages/cli/src/eventbus/eventBus.controller.ts @@ -1,5 +1,4 @@ import express from 'express'; -import type { IRunExecutionData } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; import { RestController, Get, Post, GlobalScope } from '@/decorators'; @@ -11,13 +10,12 @@ import type { EventMessageWorkflowOptions } from './EventMessageClasses/EventMes import { EventMessageWorkflow } from './EventMessageClasses/EventMessageWorkflow'; import type { EventMessageReturnMode } from './MessageEventBus/MessageEventBus'; import { MessageEventBus } from './MessageEventBus/MessageEventBus'; -import type { EventMessageTypes, FailedEventSummary } from './EventMessageClasses'; +import type { EventMessageTypes } from './EventMessageClasses'; import { eventNamesAll } from './EventMessageClasses'; import type { EventMessageAuditOptions } from './EventMessageClasses/EventMessageAudit'; import { EventMessageAudit } from './EventMessageClasses/EventMessageAudit'; import type { EventMessageNodeOptions } from './EventMessageClasses/EventMessageNode'; import { EventMessageNode } from './EventMessageClasses/EventMessageNode'; -import { ExecutionDataRecoveryService } from './executionDataRecovery.service'; // ---------------------------------------- // TypeGuards @@ -35,10 +33,7 @@ const isWithQueryString = (candidate: unknown): candidate is { query: string } = @RestController('/eventbus') export class EventBusController { - constructor( - private readonly eventBus: MessageEventBus, - private readonly recoveryService: ExecutionDataRecoveryService, - ) {} + constructor(private readonly eventBus: MessageEventBus) {} // ---------------------------------------- // Events @@ -65,13 +60,6 @@ export class EventBusController { } } - @Get('/failed') - @GlobalScope('eventBusEvent:list') - async getFailedEvents(req: express.Request): Promise { - const amount = parseInt(req.query?.amount as string) ?? 5; - return await this.eventBus.getEventsFailed(amount); - } - @Get('/execution/:id') @GlobalScope('eventBusEvent:read') async getEventForExecutionId(req: express.Request): Promise { @@ -85,21 +73,6 @@ export class EventBusController { return; } - @Get('/execution-recover/:id') - @GlobalScope('eventBusEvent:read') - async getRecoveryForExecutionId(req: express.Request): Promise { - const { id } = req.params; - if (req.params?.id) { - const logHistory = parseInt(req.query.logHistory as string, 10) || undefined; - const applyToDb = req.query.applyToDb !== undefined ? !!req.query.applyToDb : true; - const messages = await this.eventBus.getEventsByExecutionId(id, logHistory); - if (messages.length > 0) { - return await this.recoveryService.recoverExecutionData(id, messages, applyToDb); - } - } - return; - } - @Post('/event') @GlobalScope('eventBusEvent:create') async postEvent(req: express.Request): Promise { diff --git a/packages/cli/src/permissions/global-roles.ts b/packages/cli/src/permissions/global-roles.ts index 17303d2af1737..9a138d1f3817e 100644 --- a/packages/cli/src/permissions/global-roles.ts +++ b/packages/cli/src/permissions/global-roles.ts @@ -17,7 +17,6 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [ 'eventBusEvent:read', 'eventBusEvent:update', 'eventBusEvent:delete', - 'eventBusEvent:list', 'eventBusEvent:query', 'eventBusEvent:create', 'eventBusDestination:create', @@ -80,7 +79,6 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [ export const GLOBAL_ADMIN_SCOPES = GLOBAL_OWNER_SCOPES.concat(); export const GLOBAL_MEMBER_SCOPES: Scope[] = [ - 'eventBusEvent:list', 'eventBusEvent:read', 'eventBusDestination:list', 'eventBusDestination:test', diff --git a/packages/editor-ui/src/api/eventbus.ee.ts b/packages/editor-ui/src/api/eventbus.ee.ts index fc2b7f760e683..da9d798530095 100644 --- a/packages/editor-ui/src/api/eventbus.ee.ts +++ b/packages/editor-ui/src/api/eventbus.ee.ts @@ -45,10 +45,3 @@ export async function getDestinationsFromBackend( export async function getExecutionEvents(context: IRestApiContext, executionId: string) { return await makeRestApiRequest(context, 'GET', `/eventbus/execution/${executionId}`); } - -export async function recoverExecutionDataFromEvents( - context: IRestApiContext, - executionId: string, -) { - return await makeRestApiRequest(context, 'GET', `/eventbus/execution-recover/${executionId}`); -} From 1abb26e2dacc2891417ea66f6a5f3dccc4b784cd Mon Sep 17 00:00:00 2001 From: Giulio Andreini Date: Mon, 27 May 2024 15:30:29 +0200 Subject: [PATCH 08/20] fix(editor): Executions view popup in dark mode (#9517) --- packages/design-system/src/css/_tokens.dark.scss | 5 ++++- packages/design-system/src/css/_tokens.scss | 5 ++++- .../components/executions/global/GlobalExecutionsList.vue | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/design-system/src/css/_tokens.dark.scss b/packages/design-system/src/css/_tokens.dark.scss index cce44df6c6679..6ca311c0ccf89 100644 --- a/packages/design-system/src/css/_tokens.dark.scss +++ b/packages/design-system/src/css/_tokens.dark.scss @@ -154,8 +154,11 @@ // Notification --color-notification-background: var(--prim-gray-740); - // Execution card + // Execution --execution-card-background-hover: var(--color-foreground-base); + --execution-selector-background: var(--prim-gray-740); + --execution-selector-text: var(--color-text-base); + --execution-select-all-text: var(--color-text-base); // NDV --color-run-data-background: var(--prim-gray-800); diff --git a/packages/design-system/src/css/_tokens.scss b/packages/design-system/src/css/_tokens.scss index d40e4d8ae0c98..dadfe51043007 100644 --- a/packages/design-system/src/css/_tokens.scss +++ b/packages/design-system/src/css/_tokens.scss @@ -201,13 +201,16 @@ // Notification --color-notification-background: var(--color-background-xlight); - // Execution card + // Execution --execution-card-border-success: var(--prim-color-alt-a-tint-300); --execution-card-border-error: var(--prim-color-alt-c-tint-250); --execution-card-border-waiting: var(--prim-color-secondary-tint-300); --execution-card-border-running: var(--prim-color-alt-b-tint-250); --execution-card-border-unknown: var(--prim-gray-120); --execution-card-background-hover: var(--color-foreground-light); + --execution-selector-background: var(--color-background-dark); + --execution-selector-text: var(--color-text-xlight); + --execution-select-all-text: var(--color-danger); // NDV --color-run-data-background: var(--color-background-base); diff --git a/packages/editor-ui/src/components/executions/global/GlobalExecutionsList.vue b/packages/editor-ui/src/components/executions/global/GlobalExecutionsList.vue index 32731bfb80be1..0572fafe53307 100644 --- a/packages/editor-ui/src/components/executions/global/GlobalExecutionsList.vue +++ b/packages/editor-ui/src/components/executions/global/GlobalExecutionsList.vue @@ -454,9 +454,9 @@ async function onAutoRefreshToggle(value: boolean) { left: 50%; transform: translateX(-50%); bottom: var(--spacing-3xl); - background: var(--color-background-dark); + background: var(--execution-selector-background); border-radius: var(--border-radius-base); - color: var(--color-text-xlight); + color: var(--execution-selector-text); font-size: var(--font-size-2xs); button { @@ -534,7 +534,7 @@ async function onAutoRefreshToggle(value: boolean) { .selectAll { display: inline-block; margin: 0 0 var(--spacing-s) var(--spacing-s); - color: var(--color-danger); + color: var(--execution-select-all-text); } .filterLoader { From 6ed9ef0b60dbc257b1ac6deceae5f3f7c0b593c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 27 May 2024 16:52:17 +0200 Subject: [PATCH 09/20] refactor(core): Prevent reporting to Sentry IMAP server error (no-changelog) (#9515) --- .../workflow/src/errors/workflow-activation.error.ts | 9 ++++++++- .../test/errors/workflow-activation.error.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/workflow/src/errors/workflow-activation.error.ts b/packages/workflow/src/errors/workflow-activation.error.ts index b1816ba86101a..db24672775df8 100644 --- a/packages/workflow/src/errors/workflow-activation.error.ts +++ b/packages/workflow/src/errors/workflow-activation.error.ts @@ -41,7 +41,14 @@ export class WorkflowActivationError extends ExecutionBaseError { return; } - if (['ETIMEDOUT', 'ECONNREFUSED', 'EAUTH'].some((code) => this.message.includes(code))) { + if ( + [ + 'etimedout', // Node.js + 'econnrefused', // Node.js + 'eauth', // OAuth + 'temporary authentication failure', // IMAP server + ].some((str) => this.message.toLowerCase().includes(str)) + ) { this.level = 'warning'; return; } diff --git a/packages/workflow/test/errors/workflow-activation.error.test.ts b/packages/workflow/test/errors/workflow-activation.error.test.ts index 19aaca6de2520..77c3b73a24229 100644 --- a/packages/workflow/test/errors/workflow-activation.error.test.ts +++ b/packages/workflow/test/errors/workflow-activation.error.test.ts @@ -18,8 +18,8 @@ describe('WorkflowActivationError', () => { expect(secondError.level).toBe('error'); }); - test.each(['ETIMEDOUT', 'ECONNREFUSED', 'EAUTH'])( - 'should set `level` to `warning` for %s', + test.each(['ETIMEDOUT', 'ECONNREFUSED', 'EAUTH', 'Temporary authentication failure'])( + 'should set `level` to `warning` for `%s`', (code) => { const error = new WorkflowActivationError(code, { cause }); From e07de837b944627005500e52a5b2f686dd4f48cd Mon Sep 17 00:00:00 2001 From: Danny Martini Date: Mon, 27 May 2024 20:41:34 +0200 Subject: [PATCH 10/20] test(core): Align test names with route names (no-changelog) (#9518) --- .../workflows/workflows.controller.ee.test.ts | 50 +++++++++---------- .../workflows/workflows.controller.test.ts | 8 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts index 25d11148983e9..daca15808d8bf 100644 --- a/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts +++ b/packages/cli/test/integration/workflows/workflows.controller.ee.test.ts @@ -104,8 +104,8 @@ describe('router should switch based on flag', () => { }); }); -describe('PUT /workflows/:id', () => { - test('PUT /workflows/:id/share should save sharing with new users', async () => { +describe('PUT /workflows/:workflowId/share', () => { + test('should save sharing with new users', async () => { const workflow = await createWorkflow({}, owner); const response = await authOwnerAgent @@ -126,7 +126,7 @@ describe('PUT /workflows/:id', () => { ); }); - test('PUT /workflows/:id/share should succeed when sharing with invalid user-id', async () => { + test('should succeed when sharing with invalid user-id', async () => { const workflow = await createWorkflow({}, owner); const response = await authOwnerAgent @@ -139,7 +139,7 @@ describe('PUT /workflows/:id', () => { expect(sharedWorkflows).toHaveLength(1); }); - test('PUT /workflows/:id/share should allow sharing with pending users', async () => { + test('should allow sharing with pending users', async () => { const workflow = await createWorkflow({}, owner); const memberShell = await createUserShell('global:member'); const memberShellPersonalProject = await projectRepository.getPersonalProjectForUserOrFail( @@ -157,7 +157,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(1); }); - test('PUT /workflows/:id/share should allow sharing with multiple users', async () => { + test('should allow sharing with multiple users', async () => { const workflow = await createWorkflow({}, owner); const response = await authOwnerAgent @@ -171,7 +171,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(1); }); - test('PUT /workflows/:id/share should override sharing', async () => { + test('should override sharing', async () => { const workflow = await createWorkflow({}, owner); const response = await authOwnerAgent @@ -193,7 +193,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(2); }); - test('PUT /workflows/:id/share should allow sharing by the owner of the workflow', async () => { + test('should allow sharing by the owner of the workflow', async () => { const workflow = await createWorkflow({}, member); const response = await authMemberAgent @@ -207,7 +207,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(1); }); - test('PUT /workflows/:id/share should allow sharing by the instance owner', async () => { + test('should allow sharing by the instance owner', async () => { const workflow = await createWorkflow({}, member); const response = await authOwnerAgent @@ -221,7 +221,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(1); }); - test('PUT /workflows/:id/share should not allow sharing by another shared member', async () => { + test('should not allow sharing by another shared member', async () => { const workflow = await createWorkflow({}, member); await shareWorkflowWithUsers(workflow, [anotherMember]); @@ -237,7 +237,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(0); }); - test('PUT /workflows/:id/share should not allow sharing with self by another non-shared member', async () => { + test('should not allow sharing with self by another non-shared member', async () => { const workflow = await createWorkflow({}, member); const response = await authAnotherMemberAgent @@ -251,7 +251,7 @@ describe('PUT /workflows/:id', () => { expect(mailer.notifyWorkflowShared).toHaveBeenCalledTimes(0); }); - test('PUT /workflows/:id/share should not allow sharing by another non-shared member', async () => { + test('should not allow sharing by another non-shared member', async () => { const workflow = await createWorkflow({}, member); const tempUser = await createUser({ role: 'global:member' }); @@ -302,20 +302,20 @@ describe('GET /workflows/new', () => { }); }); -describe('GET /workflows/:id', () => { - test('GET should fail with invalid id due to route rule', async () => { +describe('GET /workflows/:workflowId', () => { + test('should fail with invalid id due to route rule', async () => { const response = await authOwnerAgent.get('/workflows/potatoes'); expect(response.statusCode).toBe(404); }); - test('GET should return 404 for non existing workflow', async () => { + test('should return 404 for non existing workflow', async () => { const response = await authOwnerAgent.get('/workflows/9001'); expect(response.statusCode).toBe(404); }); - test('GET should return a workflow with owner', async () => { + test('should return a workflow with owner', async () => { const workflow = await createWorkflow({}, owner); const response = await authOwnerAgent.get(`/workflows/${workflow.id}`).expect(200); @@ -343,7 +343,7 @@ describe('GET /workflows/:id', () => { }); }); - test('GET should return shared workflow with user data', async () => { + test('should return shared workflow with user data', async () => { const workflow = await createWorkflow({}, owner); await shareWorkflowWithUsers(workflow, [member]); @@ -364,7 +364,7 @@ describe('GET /workflows/:id', () => { }); }); - test('GET should return all sharees', async () => { + test('should return all sharees', async () => { const workflow = await createWorkflow({}, owner); await shareWorkflowWithUsers(workflow, [member, anotherMember]); @@ -380,7 +380,7 @@ describe('GET /workflows/:id', () => { expect(responseWorkflow.sharedWithProjects).toHaveLength(2); }); - test('GET should return workflow with credentials owned by user', async () => { + test('should return workflow with credentials owned by user', async () => { const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner }); const workflowPayload = makeWorkflow({ @@ -404,7 +404,7 @@ describe('GET /workflows/:id', () => { expect(responseWorkflow.sharedWithProjects).toHaveLength(0); }); - test('GET should return workflow with credentials saying owner does not have access when not shared', async () => { + test('should return workflow with credentials saying owner does not have access when not shared', async () => { const savedCredential = await saveCredential(randomCredentialPayload(), { user: member }); const workflowPayload = makeWorkflow({ @@ -427,7 +427,7 @@ describe('GET /workflows/:id', () => { expect(responseWorkflow.sharedWithProjects).toHaveLength(0); }); - test('GET should return workflow with credentials for all users with or without access', async () => { + test('should return workflow with credentials for all users with or without access', async () => { const savedCredential = await saveCredential(randomCredentialPayload(), { user: member }); const workflowPayload = makeWorkflow({ @@ -464,7 +464,7 @@ describe('GET /workflows/:id', () => { expect(member2Workflow.sharedWithProjects).toHaveLength(1); }); - test('GET should return workflow with credentials for all users with access', async () => { + test('should return workflow with credentials for all users with access', async () => { const savedCredential = await saveCredential(randomCredentialPayload(), { user: member }); // Both users have access to the credential (none is owner) await shareCredentialWithUsers(savedCredential, [anotherMember]); @@ -659,7 +659,7 @@ describe('POST /workflows', () => { }); }); -describe('PATCH /workflows/:id - validate credential permissions to user', () => { +describe('PATCH /workflows/:workflowId - validate credential permissions to user', () => { it('Should succeed when saving unchanged workflow nodes', async () => { const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner }); const workflow = { @@ -891,7 +891,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () => }); }); -describe('PATCH /workflows/:id - validate interim updates', () => { +describe('PATCH /workflows/:workflowId - validate interim updates', () => { it('should block owner updating workflow nodes on interim update by member', async () => { // owner creates and shares workflow @@ -1080,7 +1080,7 @@ describe('PATCH /workflows/:id - validate interim updates', () => { }); }); -describe('PATCH /workflows/:id - workflow history', () => { +describe('PATCH /workflows/:workflowId - workflow history', () => { test('Should create workflow history version when licensed', async () => { license.enable('feat:workflowHistory'); const workflow = await createWorkflow({}, owner); @@ -1190,7 +1190,7 @@ describe('PATCH /workflows/:id - workflow history', () => { }); }); -describe('PATCH /workflows/:id - activate workflow', () => { +describe('PATCH /workflows/:workflowId - activate workflow', () => { test('should activate workflow without changing version ID', async () => { license.disable('feat:workflowHistory'); const workflow = await createWorkflow({}, owner); diff --git a/packages/cli/test/integration/workflows/workflows.controller.test.ts b/packages/cli/test/integration/workflows/workflows.controller.test.ts index 0c1a22ae8a35f..c0ba5e4917643 100644 --- a/packages/cli/test/integration/workflows/workflows.controller.test.ts +++ b/packages/cli/test/integration/workflows/workflows.controller.test.ts @@ -359,7 +359,7 @@ describe('POST /workflows', () => { }); }); -describe('GET /workflows/:id', () => { +describe('GET /workflows/:workflowId', () => { test('should return pin data', async () => { const workflow = makeWorkflow({ withPinData: true }); const workflowCreationResponse = await authOwnerAgent.post('/workflows').send(workflow); @@ -823,7 +823,7 @@ describe('GET /workflows', () => { }); }); -describe('PATCH /workflows/:id', () => { +describe('PATCH /workflows/:workflowId', () => { test('should create workflow history version when licensed', async () => { license.enable('feat:workflowHistory'); const workflow = await createWorkflow({}, owner); @@ -997,7 +997,7 @@ describe('PATCH /workflows/:id', () => { }); }); -describe('POST /workflows/run', () => { +describe('POST /workflows/:workflowId/run', () => { let sharingSpy: jest.SpyInstance; let tamperingSpy: jest.SpyInstance; let workflow: WorkflowEntity; @@ -1028,7 +1028,7 @@ describe('POST /workflows/run', () => { }); }); -describe('DELETE /workflows/:id', () => { +describe('DELETE /workflows/:workflowId', () => { test('deletes a workflow owned by the user', async () => { const workflow = await createWorkflow({}, owner); From ac4e0fbb47b818973958e37e6b80201ad2ffed6f 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: Tue, 28 May 2024 14:43:22 +0200 Subject: [PATCH 11/20] fix(core): Block Public API related REST calls when Public API is not enabled (#9521) --- packages/cli/src/PublicApi/index.ts | 7 +--- packages/cli/src/controllers/me.controller.ts | 17 ++++++--- packages/cli/src/services/frontend.service.ts | 3 +- packages/cli/test/integration/me.api.test.ts | 36 ++++++++++++++++--- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/PublicApi/index.ts b/packages/cli/src/PublicApi/index.ts index 104f92bd1843d..95b0e3e5c4e25 100644 --- a/packages/cli/src/PublicApi/index.ts +++ b/packages/cli/src/PublicApi/index.ts @@ -153,11 +153,6 @@ export const loadPublicApiVersions = async ( }; }; -function isApiEnabledByLicense(): boolean { - const license = Container.get(License); - return !license.isAPIDisabled(); -} - export function isApiEnabled(): boolean { - return !config.get('publicApi.disabled') && isApiEnabledByLicense(); + return !config.get('publicApi.disabled') && !Container.get(License).isAPIDisabled(); } diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 3a3149d4e955c..9afe583d56eaa 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -1,6 +1,6 @@ import validator from 'validator'; import { plainToInstance } from 'class-transformer'; -import { Response } from 'express'; +import { type RequestHandler, Response } from 'express'; import { randomBytes } from 'crypto'; import { AuthService } from '@/auth/auth.service'; @@ -22,6 +22,15 @@ import { ExternalHooks } from '@/ExternalHooks'; import { InternalHooks } from '@/InternalHooks'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UserRepository } from '@/databases/repositories/user.repository'; +import { isApiEnabled } from '@/PublicApi'; + +export const isApiEnabledMiddleware: RequestHandler = (_, res, next) => { + if (isApiEnabled()) { + next(); + } else { + res.status(404).end(); + } +}; @RestController('/me') export class MeController { @@ -185,7 +194,7 @@ export class MeController { /** * Creates an API Key */ - @Post('/api-key') + @Post('/api-key', { middlewares: [isApiEnabledMiddleware] }) async createAPIKey(req: AuthenticatedRequest) { const apiKey = `n8n_api_${randomBytes(40).toString('hex')}`; @@ -202,7 +211,7 @@ export class MeController { /** * Get an API Key */ - @Get('/api-key') + @Get('/api-key', { middlewares: [isApiEnabledMiddleware] }) async getAPIKey(req: AuthenticatedRequest) { return { apiKey: req.user.apiKey }; } @@ -210,7 +219,7 @@ export class MeController { /** * Deletes an API Key */ - @Delete('/api-key') + @Delete('/api-key', { middlewares: [isApiEnabledMiddleware] }) async deleteAPIKey(req: AuthenticatedRequest) { await this.userService.update(req.user.id, { apiKey: null }); diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 65f0330b53d6e..340fcb6390b83 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -31,6 +31,7 @@ import type { CommunityPackagesService } from '@/services/communityPackages.serv import { Logger } from '@/Logger'; import { UrlService } from './url.service'; import { InternalHooks } from '@/InternalHooks'; +import { isApiEnabled } from '@/PublicApi'; @Service() export class FrontendService { @@ -143,7 +144,7 @@ export class FrontendService { }, }, publicApi: { - enabled: !config.get('publicApi.disabled') && !this.license.isAPIDisabled(), + enabled: isApiEnabled(), latestVersion: 1, path: config.getEnv('publicApi.path'), swaggerUi: { diff --git a/packages/cli/test/integration/me.api.test.ts b/packages/cli/test/integration/me.api.test.ts index 1a7eb1b4ff734..4e6a6be1fe2ad 100644 --- a/packages/cli/test/integration/me.api.test.ts +++ b/packages/cli/test/integration/me.api.test.ts @@ -1,7 +1,12 @@ +import { Container } from 'typedi'; import type { SuperAgentTest } from 'supertest'; import { IsNull } from '@n8n/typeorm'; import validator from 'validator'; + import type { User } from '@db/entities/User'; +import { UserRepository } from '@db/repositories/user.repository'; +import { ProjectRepository } from '@db/repositories/project.repository'; + import { SUCCESS_RESPONSE_BODY } from './shared/constants'; import { randomApiKey, @@ -12,15 +17,38 @@ import { } from './shared/random'; import * as testDb from './shared/testDb'; import * as utils from './shared/utils/'; -import { addApiKey, createUser, createUserShell } from './shared/db/users'; -import Container from 'typedi'; -import { UserRepository } from '@db/repositories/user.repository'; -import { ProjectRepository } from '@/databases/repositories/project.repository'; +import { addApiKey, createOwner, createUser, createUserShell } from './shared/db/users'; +import config from '@/config'; const testServer = utils.setupTestServer({ endpointGroups: ['me'] }); beforeEach(async () => { await testDb.truncate(['User']); + config.set('publicApi.disabled', false); +}); + +describe('When public API is disabled', () => { + let owner: User; + let authAgent: SuperAgentTest; + + beforeEach(async () => { + owner = await createOwner(); + await addApiKey(owner); + authAgent = testServer.authAgentFor(owner); + config.set('publicApi.disabled', true); + }); + + test('POST /me/api-key should 404', async () => { + await authAgent.post('/me/api-key').expect(404); + }); + + test('GET /me/api-key should 404', async () => { + await authAgent.get('/me/api-key').expect(404); + }); + + test('DELETE /me/api-key should 404', async () => { + await authAgent.delete('/me/api-key').expect(404); + }); }); describe('Owner shell', () => { From ec0373f666ed7d5c416fdef44afd8dd748755c9f Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Tue, 28 May 2024 16:58:44 +0200 Subject: [PATCH 12/20] feat(editor): Show expression infobox on hover and cursor position (#9507) Co-authored-by: Giulio Andreini --- .../completions/jsonField.completions.ts | 2 - .../ExpressionEditorModalInput.vue | 2 + .../InlineExpressionEditorInput.vue | 9 +- .../src/composables/useExpressionEditor.ts | 16 +- .../completions/__tests__/completions.test.ts | 12 +- .../codemirror/completions/constants.ts | 10 +- .../completions/datatype.completions.ts | 33 +- .../completions/dollar.completions.ts | 29 +- .../codemirror/completions/infoBoxRenderer.ts | 64 ++-- .../plugins/codemirror/completions/utils.ts | 12 + .../codemirror/tooltips/InfoBoxTooltip.ts | 317 ++++++++++++++++++ .../tooltips/__tests__/infoBoxTooltip.test.ts | 152 +++++++++ .../src/plugins/i18n/locales/en.json | 2 +- .../src/styles/plugins/_codemirror.scss | 133 +++++--- 14 files changed, 657 insertions(+), 136 deletions(-) create mode 100644 packages/editor-ui/src/plugins/codemirror/tooltips/InfoBoxTooltip.ts create mode 100644 packages/editor-ui/src/plugins/codemirror/tooltips/__tests__/infoBoxTooltip.test.ts diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts index a28c5d2e2cabf..7f2c890ca3540 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts @@ -73,7 +73,6 @@ function useJsonFieldCompletions() { * - Complete `$input.item.json[` to `['field']`. */ const inputJsonFieldCompletions = (context: CompletionContext): CompletionResult | null => { - console.log('🚀 ~ inputJsonFieldCompletions ~ context:', context); const patterns = { first: /\$input\.first\(\)\.json(\[|\.).*/, last: /\$input\.last\(\)\.json(\[|\.).*/, @@ -158,7 +157,6 @@ function useJsonFieldCompletions() { if (name === 'all') { const regexMatch = preCursor.text.match(regex); - console.log('🚀 ~ selectorJsonFieldCompletions ~ regexMatch:', regexMatch); if (!regexMatch?.groups?.index) continue; const { index } = regexMatch.groups; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue index 1f9b7b29305a1..84ebd578969d4 100644 --- a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue +++ b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue @@ -24,6 +24,7 @@ import { } from '@/plugins/codemirror/keymap'; import type { Segment } from '@/types/expressions'; import { removeExpressionPrefix } from '@/utils/expressions'; +import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip'; type Props = { modelValue: string; @@ -68,6 +69,7 @@ const extensions = computed(() => [ expressionInputHandler(), EditorView.lineWrapping, EditorView.domEventHandlers({ scroll: forceParse }), + infoBoxTooltips(), ]); const editorValue = ref(removeExpressionPrefix(props.modelValue)); const { diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue index 1e547c08ac2b7..c6ed0e1e44f23 100644 --- a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue +++ b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue @@ -20,6 +20,7 @@ import { removeExpressionPrefix } from '@/utils/expressions'; import { createEventBus, type EventBus } from 'n8n-design-system/utils'; import type { IDataObject } from 'n8n-workflow'; import { inputTheme } from './theme'; +import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip'; type Props = { modelValue: string; @@ -56,6 +57,7 @@ const extensions = computed(() => [ history(), expressionInputHandler(), EditorView.lineWrapping, + infoBoxTooltips(), ]); const editorValue = ref(removeExpressionPrefix(props.modelValue)); const { @@ -138,7 +140,12 @@ onBeforeUnmount(() => {