From 22bdb0568e2bc38082f20fbbea3c425fca7fb4b2 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 10 Jun 2024 09:23:06 -0400 Subject: [PATCH] refactor(editor): Fix remaining FE type check errors (no-changelog) (#9607) Co-authored-by: Alex Grozav --- .../src/components/N8nFormInput/FormInput.vue | 2 +- packages/design-system/src/locale/format.ts | 19 ++++--- packages/design-system/src/locale/index.ts | 6 +-- packages/design-system/src/mixins/locale.ts | 2 +- ...ms-markdown-it.d.ts => shims-modules.d.ts} | 0 packages/design-system/src/shims-types.d.ts | 7 --- packages/design-system/src/shims-vue.d.ts | 13 +++-- packages/design-system/src/types/form.ts | 8 +-- packages/design-system/src/types/i18n.ts | 4 +- packages/editor-ui/src/Interface.ts | 24 +-------- .../editor-ui/src/__tests__/data/projects.ts | 20 ++++++- .../__tests__/server/factories/variable.ts | 2 +- packages/editor-ui/src/api/eventbus.ee.ts | 2 +- packages/editor-ui/src/api/nodeTypes.ts | 2 +- packages/editor-ui/src/api/templates.ts | 4 +- .../CredentialEdit/CredentialEdit.vue | 4 +- .../CredentialEdit/CredentialSharing.ee.vue | 27 +++++++--- .../src/components/HoverableNodeIcon.vue | 4 +- .../components/MainHeader/WorkflowDetails.vue | 6 ++- .../Node/NodeCreator/Modes/NodesMode.vue | 4 +- .../Node/NodeCreator/__tests__/utils.ts | 2 +- .../NodeCreator/composables/useViewStacks.ts | 4 +- .../components/Node/NodeCreator/viewsData.ts | 4 +- .../editor-ui/src/components/NodeIcon.vue | 4 +- .../editor-ui/src/components/NodeSettings.vue | 3 +- .../src/components/NodeSettingsTabs.vue | 2 +- .../components/Projects/ProjectTabs.test.ts | 35 ++++++------ .../ResourceLocator/ResourceLocator.vue | 2 +- .../ResourceMapper/ResourceMapper.vue | 9 +++- .../EventDestinationSettingsModal.ee.vue | 32 +++++++---- packages/editor-ui/src/components/Sticky.vue | 2 +- .../__tests__/WorkflowLMChatModal.test.ts | 32 +++++------ .../layouts/ResourcesListLayout.vue | 8 +-- .../__tests__/useCanvasPanning.test.ts | 2 +- .../composables/useCanvasOperations.spec.ts | 21 ++++---- .../src/composables/useCanvasPanning.ts | 2 +- .../src/composables/useExecutionDebugging.ts | 3 +- .../src/composables/useNodeHelpers.ts | 2 +- .../src/composables/usePushConnection.ts | 1 + .../src/composables/useWorkflowHelpers.ts | 7 +-- .../hooks/utils/hooksAddFakeDoorFeatures.ts | 3 +- packages/editor-ui/src/main.ts | 3 +- packages/editor-ui/src/mixins/nodeBase.ts | 53 +++++++++++++------ packages/editor-ui/src/mixins/userHelpers.ts | 3 +- .../codemirror/completions/__tests__/mock.ts | 7 +-- .../__tests__/variables.completions.test.ts | 8 +-- packages/editor-ui/src/plugins/components.ts | 4 +- .../plugins/connectors/N8nCustomConnector.ts | 4 +- packages/editor-ui/src/plugins/directives.ts | 2 +- packages/editor-ui/src/plugins/i18n/index.ts | 16 +++--- packages/editor-ui/src/plugins/icons/index.ts | 2 +- .../plugins/jsplumb/N8nPlusEndpointType.ts | 4 -- .../editor-ui/src/plugins/jsplumb/index.ts | 6 ++- .../editor-ui/src/plugins/telemetry/index.ts | 8 +-- packages/editor-ui/src/router.ts | 14 ++--- .../src/{jsplumb.d.ts => shims-jsplumb.d.ts} | 14 +++-- ...finite-loading.d.ts => shims-modules.d.ts} | 18 +++++++ packages/editor-ui/src/shims-vue.d.ts | 36 +++++++++++-- packages/editor-ui/src/shims.d.ts | 18 ++----- packages/editor-ui/src/stores/canvas.store.ts | 5 +- .../editor-ui/src/stores/credentials.store.ts | 3 -- .../src/stores/logStreaming.store.ts | 2 +- packages/editor-ui/src/stores/ndv.store.ts | 1 + .../editor-ui/src/stores/posthog.store.ts | 2 +- packages/editor-ui/src/stores/rbac.store.ts | 1 + .../editor-ui/src/stores/settings.store.ts | 2 +- .../editor-ui/src/stores/templates.store.ts | 12 +++-- packages/editor-ui/src/types/router.ts | 20 ------- .../editor-ui/src/utils/nodeTypesUtils.ts | 7 +-- packages/editor-ui/src/utils/nodeViewUtils.ts | 8 +-- .../__tests__/templateTransforms.test.ts | 2 + .../src/utils/testData/templateTestData.ts | 3 +- packages/editor-ui/src/utils/typeGuards.ts | 8 ++- .../editor-ui/src/views/CredentialsView.vue | 11 +++- packages/editor-ui/src/views/NodeView.vue | 10 ++-- .../__tests__/setupTemplate.store.test.ts | 1 + .../__tests__/useCredentialSetupState.test.ts | 3 ++ .../src/views/TemplatesSearchView.vue | 25 ++++----- .../editor-ui/src/views/VariablesView.vue | 2 +- .../views/__tests__/SettingsUsersView.test.ts | 15 +++--- packages/editor-ui/tsconfig.json | 6 +-- packages/editor-ui/vite.config.mts | 8 ++- packages/workflow/src/Interfaces.ts | 32 ++++++----- packages/workflow/src/MessageEventBus.ts | 7 +++ 84 files changed, 438 insertions(+), 318 deletions(-) rename packages/design-system/src/{shims-markdown-it.d.ts => shims-modules.d.ts} (100%) delete mode 100644 packages/design-system/src/shims-types.d.ts rename packages/editor-ui/src/{jsplumb.d.ts => shims-jsplumb.d.ts} (69%) rename packages/editor-ui/src/{v3-infinite-loading.d.ts => shims-modules.d.ts} (61%) diff --git a/packages/design-system/src/components/N8nFormInput/FormInput.vue b/packages/design-system/src/components/N8nFormInput/FormInput.vue index b6bff9c144..e52bd299b0 100644 --- a/packages/design-system/src/components/N8nFormInput/FormInput.vue +++ b/packages/design-system/src/components/N8nFormInput/FormInput.vue @@ -240,7 +240,7 @@ const validationError = computed(() => { if (error) { if ('messageKey' in error) { - return t(error.messageKey, error.options as object); + return t(error.messageKey, error.options); } else if ('message' in error) { return error.message; } diff --git a/packages/design-system/src/locale/format.ts b/packages/design-system/src/locale/format.ts index a400633f36..1e8cf0a0d7 100644 --- a/packages/design-system/src/locale/format.ts +++ b/packages/design-system/src/locale/format.ts @@ -6,6 +6,9 @@ const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g; * https://github.com/Matt-Esch/string-template/index.js */ export default function () { + const isReplacementGroup = (target: object, key: string): target is Record => + key in target; + function template( value: string | ((...args: unknown[]) => string), ...args: Array @@ -15,21 +18,23 @@ export default function () { } const str = value; + let replacements: object = args; if (args.length === 1 && typeof args[0] === 'object') { - args = args[0] as unknown as Array; + replacements = args[0]; } - if (!args?.hasOwnProperty) { - args = {} as unknown as Array; + if (!replacements?.hasOwnProperty) { + replacements = {}; } - return str.replace(RE_NARGS, (match, _, i, index: number) => { - let result: string | object | null; + return str.replace(RE_NARGS, (match, _, group: string, index: number): string => { + let result: string | null; if (str[index - 1] === '{' && str[index + match.length] === '}') { - return i; + return `${group}`; } else { - result = Object.hasOwn(args, i) ? args[i] : null; + result = isReplacementGroup(replacements, group) ? `${replacements[group]}` : null; + if (result === null || result === undefined) { return ''; } diff --git a/packages/design-system/src/locale/index.ts b/packages/design-system/src/locale/index.ts index 2d830ea62b..325b1d0dcd 100644 --- a/packages/design-system/src/locale/index.ts +++ b/packages/design-system/src/locale/index.ts @@ -26,7 +26,7 @@ export const t = function ( // only support flat keys if (lang[path] !== undefined) { - return format(lang[path], options); + return format(lang[path], ...(options ? [options] : [])); } return ''; @@ -44,8 +44,8 @@ export async function use(l: string) { } catch (e) {} } -export const i18n = function (fn: N8nLocaleTranslateFn) { +export function i18n(fn: N8nLocaleTranslateFn) { i18nHandler = fn || i18nHandler; -}; +} export default { use, t, i18n }; diff --git a/packages/design-system/src/mixins/locale.ts b/packages/design-system/src/mixins/locale.ts index 7c4ab37a6b..181df45dd5 100644 --- a/packages/design-system/src/mixins/locale.ts +++ b/packages/design-system/src/mixins/locale.ts @@ -2,7 +2,7 @@ import { t } from '../locale'; export default { methods: { - t(path: string, options: object) { + t(path: string, options: string[]) { return t.call(this, path, options); }, }, diff --git a/packages/design-system/src/shims-markdown-it.d.ts b/packages/design-system/src/shims-modules.d.ts similarity index 100% rename from packages/design-system/src/shims-markdown-it.d.ts rename to packages/design-system/src/shims-modules.d.ts diff --git a/packages/design-system/src/shims-types.d.ts b/packages/design-system/src/shims-types.d.ts deleted file mode 100644 index 310838d645..0000000000 --- a/packages/design-system/src/shims-types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as Vue from 'vue'; - -declare module 'vue/types/vue' { - interface Vue { - $style: Record; - } -} diff --git a/packages/design-system/src/shims-vue.d.ts b/packages/design-system/src/shims-vue.d.ts index b3a21c6cdb..0a8e4af75a 100644 --- a/packages/design-system/src/shims-vue.d.ts +++ b/packages/design-system/src/shims-vue.d.ts @@ -1,4 +1,11 @@ -declare module '*.vue' { - import Vue from 'vue'; - export default Vue; +export {}; + +/** + * @docs https://vuejs.org/guide/typescript/options-api.html#augmenting-global-properties + */ + +declare module 'vue' { + interface ComponentCustomProperties { + $style: Record; + } } diff --git a/packages/design-system/src/types/form.ts b/packages/design-system/src/types/form.ts index 0ab3ec5b01..45a054a012 100644 --- a/packages/design-system/src/types/form.ts +++ b/packages/design-system/src/types/form.ts @@ -1,8 +1,10 @@ +import type { N8nLocaleTranslateFnOptions } from 'n8n-design-system/types/i18n'; + export type Rule = { name: string; config?: unknown }; export type RuleGroup = { rules: Array; - defaultError?: { messageKey: string; options?: unknown }; + defaultError?: { messageKey: string; options?: N8nLocaleTranslateFnOptions }; }; export type Validatable = string | number | boolean | null | undefined; @@ -13,8 +15,8 @@ export type IValidator = { config: T, ) => | false - | { message: string; options?: unknown } - | { messageKey: string; options?: unknown } + | { message: string; options?: N8nLocaleTranslateFnOptions } + | { messageKey: string; options?: N8nLocaleTranslateFnOptions } | null; }; diff --git a/packages/design-system/src/types/i18n.ts b/packages/design-system/src/types/i18n.ts index 7e79f4037c..307a754de6 100644 --- a/packages/design-system/src/types/i18n.ts +++ b/packages/design-system/src/types/i18n.ts @@ -1,3 +1,5 @@ -export type N8nLocaleTranslateFn = (path: string, options: object) => string; +export type N8nLocaleTranslateFnOptions = string[] | Record; + +export type N8nLocaleTranslateFn = (path: string, options?: N8nLocaleTranslateFnOptions) => string; export type N8nLocale = Record string)>; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index a8dc46c158..2d2c37559a 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -134,16 +134,6 @@ export type EndpointStyle = { hoverMessage?: string; }; -export type EndpointMeta = { - __meta?: { - nodeName: string; - nodeId: string; - index: number; - totalEndpoints: number; - endpointLabelLength: number; - }; -}; - export interface IUpdateInformation { name: string; key?: string; @@ -410,19 +400,6 @@ export interface IExecutionResponse extends IExecutionBase { executedNode?: string; } -export interface IExecutionShortResponse { - id: string; - workflowData: { - id: string; - name: string; - }; - mode: WorkflowExecuteMode; - finished: boolean; - startedAt: Date; - stoppedAt: Date; - executionTime?: number; -} - export interface IExecutionsListResponse { count: number; results: ExecutionSummary[]; @@ -432,6 +409,7 @@ export interface IExecutionsListResponse { export interface IExecutionsCurrentSummaryExtended { id: string; finished?: boolean; + status: ExecutionStatus; mode: WorkflowExecuteMode; retryOf?: string | null; retrySuccessId?: string | null; diff --git a/packages/editor-ui/src/__tests__/data/projects.ts b/packages/editor-ui/src/__tests__/data/projects.ts index 620a396925..1870803cbf 100644 --- a/packages/editor-ui/src/__tests__/data/projects.ts +++ b/packages/editor-ui/src/__tests__/data/projects.ts @@ -1,5 +1,10 @@ import { faker } from '@faker-js/faker'; -import type { ProjectListItem, ProjectSharingData, ProjectType } from '@/types/projects.types'; +import type { + Project, + ProjectListItem, + ProjectSharingData, + ProjectType, +} from '@/types/projects.types'; import { ProjectTypes } from '@/types/projects.types'; export const createProjectSharingData = (projectType?: ProjectType): ProjectSharingData => ({ @@ -19,3 +24,16 @@ export const createProjectListItem = (projectType?: ProjectType): ProjectListIte updatedAt: faker.date.recent().toISOString(), }; }; + +export function createTestProject(data: Partial): Project { + return { + id: faker.string.uuid(), + name: faker.lorem.words({ min: 1, max: 3 }), + createdAt: faker.date.past().toISOString(), + updatedAt: faker.date.recent().toISOString(), + type: 'team', + relations: [], + scopes: [], + ...data, + }; +} diff --git a/packages/editor-ui/src/__tests__/server/factories/variable.ts b/packages/editor-ui/src/__tests__/server/factories/variable.ts index 948e257888..f7e9c44a27 100644 --- a/packages/editor-ui/src/__tests__/server/factories/variable.ts +++ b/packages/editor-ui/src/__tests__/server/factories/variable.ts @@ -4,7 +4,7 @@ import type { EnvironmentVariable } from '@/Interface'; export const variableFactory = Factory.extend({ id(i: number) { - return i; + return `${i}`; }, key() { return `${faker.lorem.word()}`.toUpperCase(); diff --git a/packages/editor-ui/src/api/eventbus.ee.ts b/packages/editor-ui/src/api/eventbus.ee.ts index a89fdb4643..99a8bf480d 100644 --- a/packages/editor-ui/src/api/eventbus.ee.ts +++ b/packages/editor-ui/src/api/eventbus.ee.ts @@ -31,7 +31,7 @@ export async function deleteDestinationFromDb(context: IRestApiContext, destinat export async function sendTestMessageToDestination( context: IRestApiContext, destination: ApiMessageEventBusDestinationOptions, -) { +): Promise { const data: IDataObject = { ...destination, }; diff --git a/packages/editor-ui/src/api/nodeTypes.ts b/packages/editor-ui/src/api/nodeTypes.ts index 240159f964..300b25390d 100644 --- a/packages/editor-ui/src/api/nodeTypes.ts +++ b/packages/editor-ui/src/api/nodeTypes.ts @@ -5,9 +5,9 @@ import type { INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion, + ResourceMapperFields, } from 'n8n-workflow'; import axios from 'axios'; -import type { ResourceMapperFields } from 'n8n-workflow/src/Interfaces'; export async function getNodeTypes(baseUrl: string) { const { data } = await axios.get(baseUrl + 'types/nodes.json', { withCredentials: true }); diff --git a/packages/editor-ui/src/api/templates.ts b/packages/editor-ui/src/api/templates.ts index 8fa3f41aca..3e14b98a13 100644 --- a/packages/editor-ui/src/api/templates.ts +++ b/packages/editor-ui/src/api/templates.ts @@ -11,7 +11,7 @@ import type { } from '@/Interface'; import { get } from '@/utils/apiUtils'; -function stringifyArray(arr: number[]) { +function stringifyArray(arr: string[]) { return arr.join(','); } @@ -41,7 +41,7 @@ export async function getCollections( export async function getWorkflows( apiEndpoint: string, - query: { page: number; limit: number; categories: number[]; search: string }, + query: { page: number; limit: number; categories: string[]; search: string }, headers?: RawAxiosRequestHeaders, ): Promise<{ totalWorkflows: number; diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index 7f852767e9..c19133e3de 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -1115,8 +1115,6 @@ export default defineComponent({ oauthTokenData: {} as CredentialInformation, }; - this.credentialsStore.enableOAuthCredential(credential); - // Close the window if (oauthPopup) { oauthPopup.close(); @@ -1164,7 +1162,7 @@ export default defineComponent({ this.credentialData = { ...this.credentialData, - scopes, + scopes: scopes as unknown as CredentialInformation, homeProject, }; }, diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 1a526164ba..7c3381b9cc 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -72,7 +72,7 @@ diff --git a/packages/editor-ui/src/components/Sticky.vue b/packages/editor-ui/src/components/Sticky.vue index 51be857a40..b00012a6cf 100644 --- a/packages/editor-ui/src/components/Sticky.vue +++ b/packages/editor-ui/src/components/Sticky.vue @@ -184,7 +184,7 @@ export default defineComponent({ }, isSelected(): boolean { return ( - this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !== + this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data?.name) !== undefined ); }, diff --git a/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts b/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts index ea080df00b..e98838caf3 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts @@ -12,6 +12,8 @@ import { useUsersStore } from '@/stores/users.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { testingNodeTypes, mockNodeTypesToArray } from '@/__tests__/defaults'; import { setupServer } from '@/__tests__/server'; +import { NodeConnectionType } from 'n8n-workflow'; +import type { IConnections } from 'n8n-workflow'; const renderComponent = createComponentRenderer(WorkflowLMChatModal, { props: { @@ -23,25 +25,25 @@ const renderComponent = createComponentRenderer(WorkflowLMChatModal, { async function createPiniaWithAINodes(options = { withConnections: true, withAgentNode: true }) { const { withConnections, withAgentNode } = options; const workflowId = uuid(); + const connections: IConnections = { + 'Chat Trigger': { + main: [ + [ + { + node: 'Agent', + type: NodeConnectionType.Main, + index: 0, + }, + ], + ], + }, + }; + const workflow = createTestWorkflow({ id: workflowId, name: 'Test Workflow', - connections: withConnections - ? { - 'Chat Trigger': { - main: [ - [ - { - node: 'Agent', - type: 'main', - index: 0, - }, - ], - ], - }, - } - : {}, active: true, + ...(withConnections ? { connections } : {}), nodes: [ createTestNode({ name: 'Chat Trigger', diff --git a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue index 0150a84c22..5305935b7c 100644 --- a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue +++ b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue @@ -160,14 +160,14 @@ import { useRoute } from 'vue-router'; // eslint-disable-next-line unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars import type { BaseTextKey } from '@/plugins/i18n'; -export interface IResource { +export type IResource = { id: string; name: string; value: string; updatedAt?: string; createdAt?: string; homeProject?: ProjectSharingData; -} +}; interface IFilters { search: string; @@ -291,11 +291,11 @@ export default defineComponent({ case 'lastUpdated': return props.sortFns.lastUpdated ? props.sortFns.lastUpdated(a, b) - : new Date(b.updatedAt).valueOf() - new Date(a.updatedAt).valueOf(); + : new Date(b.updatedAt ?? '').valueOf() - new Date(a.updatedAt ?? '').valueOf(); case 'lastCreated': return props.sortFns.lastCreated ? props.sortFns.lastCreated(a, b) - : new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf(); + : new Date(b.createdAt ?? '').valueOf() - new Date(a.createdAt ?? '').valueOf(); case 'nameAsc': return props.sortFns.nameAsc ? props.sortFns.nameAsc(a, b) diff --git a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts index e45a3e6463..be826a1b17 100644 --- a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts @@ -69,7 +69,7 @@ describe('useCanvasPanning()', () => { const { onMouseDown, onMouseMove, onMouseUp } = useCanvasPanning(elementRef); onMouseDown(new MouseEvent('mousedown', { button: 1 }), true); - onMouseUp(new MouseEvent('mouseup')); + onMouseUp(); expect(removeEventListenerSpy).toHaveBeenCalledWith('mousemove', onMouseMove); }); diff --git a/packages/editor-ui/src/composables/useCanvasOperations.spec.ts b/packages/editor-ui/src/composables/useCanvasOperations.spec.ts index cb234b4249..8d19d30b6b 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.spec.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.spec.ts @@ -9,6 +9,7 @@ import { createPinia, setActivePinia } from 'pinia'; import { createTestNode } from '@/__tests__/mocks'; import type { Connection } from '@vue-flow/core'; import type { IConnection } from 'n8n-workflow'; +import { NodeConnectionType } from 'n8n-workflow'; describe('useCanvasOperations', () => { let workflowsStore: ReturnType; @@ -200,9 +201,9 @@ describe('useCanvasOperations', () => { const connection: Connection = { source: nodeA.id, - sourceHandle: 'outputs/main/0', + sourceHandle: `outputs/${NodeConnectionType.Main}/0`, target: nodeB.id, - targetHandle: 'inputs/main/0', + targetHandle: `inputs/${NodeConnectionType.Main}/0`, }; vi.spyOn(workflowsStore, 'getNodeById').mockReturnValueOnce(nodeA).mockReturnValueOnce(nodeB); @@ -211,8 +212,8 @@ describe('useCanvasOperations', () => { expect(addConnectionSpy).toHaveBeenCalledWith({ connection: [ - { index: 0, node: nodeA.name, type: 'main' }, - { index: 0, node: nodeB.name, type: 'main' }, + { index: 0, node: nodeA.name, type: NodeConnectionType.Main }, + { index: 0, node: nodeB.name, type: NodeConnectionType.Main }, ], }); expect(uiStore.stateIsDirty).toBe(true); @@ -269,9 +270,9 @@ describe('useCanvasOperations', () => { const connection: Connection = { source: nodeA.id, - sourceHandle: 'outputs/main/0', + sourceHandle: `outputs/${NodeConnectionType.Main}/0`, target: nodeB.id, - targetHandle: 'inputs/main/0', + targetHandle: `inputs/${NodeConnectionType.Main}/0`, }; vi.spyOn(workflowsStore, 'getNodeById').mockReturnValueOnce(nodeA).mockReturnValueOnce(nodeB); @@ -280,8 +281,8 @@ describe('useCanvasOperations', () => { expect(removeConnectionSpy).toHaveBeenCalledWith({ connection: [ - { index: 0, node: nodeA.name, type: 'main' }, - { index: 0, node: nodeB.name, type: 'main' }, + { index: 0, node: nodeA.name, type: NodeConnectionType.Main }, + { index: 0, node: nodeB.name, type: NodeConnectionType.Main }, ], }); }); @@ -294,8 +295,8 @@ describe('useCanvasOperations', () => { .mockImplementation(() => {}); const connection: [IConnection, IConnection] = [ - { node: 'sourceNode', type: 'type', index: 1 }, - { node: 'targetNode', type: 'type', index: 2 }, + { node: 'sourceNode', type: NodeConnectionType.Main, index: 1 }, + { node: 'targetNode', type: NodeConnectionType.Main, index: 2 }, ]; canvasOperations.revertDeleteConnection(connection); diff --git a/packages/editor-ui/src/composables/useCanvasPanning.ts b/packages/editor-ui/src/composables/useCanvasPanning.ts index b1bf79a025..4bf97cc809 100644 --- a/packages/editor-ui/src/composables/useCanvasPanning.ts +++ b/packages/editor-ui/src/composables/useCanvasPanning.ts @@ -24,7 +24,7 @@ export function useCanvasPanning( /** * Updates the canvas offset position based on the mouse movement */ - function panCanvas(e: MouseEvent) { + function panCanvas(e: MouseEvent | TouchEvent) { const offsetPosition = uiStore.nodeViewOffsetPosition; const [x, y] = getMousePosition(e); diff --git a/packages/editor-ui/src/composables/useExecutionDebugging.ts b/packages/editor-ui/src/composables/useExecutionDebugging.ts index b0f9a60cf9..74966a612f 100644 --- a/packages/editor-ui/src/composables/useExecutionDebugging.ts +++ b/packages/editor-ui/src/composables/useExecutionDebugging.ts @@ -15,6 +15,7 @@ import { useSettingsStore } from '@/stores/settings.store'; import { useUIStore } from '@/stores/ui.store'; import { useTelemetry } from './useTelemetry'; import { useRootStore } from '@/stores/n8nRoot.store'; +import { isFullExecutionResponse } from '@/utils/typeGuards'; export const useExecutionDebugging = () => { const telemetry = useTelemetry(); @@ -131,7 +132,7 @@ export const useExecutionDebugging = () => { telemetry.track('User clicked debug execution button', { instance_id: useRootStore().instanceId, - exec_status: execution.status, + exec_status: isFullExecutionResponse(execution) ? execution.status : '', override_pinned_data: pinnableNodes.length === pinnings, all_exec_data_imported: missingNodeNames.length === 0, }); diff --git a/packages/editor-ui/src/composables/useNodeHelpers.ts b/packages/editor-ui/src/composables/useNodeHelpers.ts index cf4fd31f47..003fda4848 100644 --- a/packages/editor-ui/src/composables/useNodeHelpers.ts +++ b/packages/editor-ui/src/composables/useNodeHelpers.ts @@ -271,7 +271,7 @@ export function useNodeHelpers() { } } - function updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void { + function updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription | null): void { const localNodeType = nodeType ?? nodeTypesStore.getNodeType(node.type, node.typeVersion); if (localNodeType === null) { diff --git a/packages/editor-ui/src/composables/usePushConnection.ts b/packages/editor-ui/src/composables/usePushConnection.ts index 4762b8aba9..416b21b7f8 100644 --- a/packages/editor-ui/src/composables/usePushConnection.ts +++ b/packages/editor-ui/src/composables/usePushConnection.ts @@ -530,6 +530,7 @@ export function usePushConnection({ router }: { router: ReturnType, - connections: IConnections, - copyData?: boolean, -): Workflow { +function getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow { return useWorkflowsStore().getWorkflow(nodes, connections, copyData); } diff --git a/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts b/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts index e768983c90..814f8679a3 100644 --- a/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts +++ b/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts @@ -1,6 +1,7 @@ import { useUIStore } from '@/stores/ui.store'; import type { IFakeDoor } from '@/Interface'; import { FAKE_DOOR_FEATURES } from '@/constants'; +import type { BaseTextKey } from '@/plugins/i18n'; export function compileFakeDoorFeatures(): IFakeDoor[] { const store = useUIStore(); @@ -20,7 +21,7 @@ export function compileFakeDoorFeatures(): IFakeDoor[] { if (loggingFeature) { loggingFeature.actionBoxTitle += '.cloud'; loggingFeature.linkURL += '&edition=cloud'; - loggingFeature.infoText = ''; + loggingFeature.infoText = '' as BaseTextKey; } return fakeDoorFeatures; diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index db8cc15077..9a51382f7e 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -50,7 +50,8 @@ app.mount('#app'); if (!import.meta.env.PROD) { // Make sure that we get all error messages properly displayed // as long as we are not in production mode - window.onerror = (message, source, lineno, colno, error) => { + window.onerror = (message, _source, _lineno, _colno, error) => { + // eslint-disable-next-line @typescript-eslint/no-base-to-string if (message.toString().includes('ResizeObserver')) { // That error can apparently be ignored and can probably // not do anything about it anyway diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 2852021bb0..58e191177c 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -29,6 +29,7 @@ import { useCanvasStore } from '@/stores/canvas.store'; import type { EndpointSpec } from '@jsplumb/common'; import { useDeviceSupport } from 'n8n-design-system'; import type { N8nEndpointLabelLength } from '@/plugins/jsplumb/N8nPlusEndpointType'; +import { isValidNodeConnectionType } from '@/utils/typeGuards'; const createAddInputEndpointSpec = ( connectionName: NodeConnectionType, @@ -119,9 +120,9 @@ export const nodeBase = defineComponent({ }, methods: { __addEndpointTestingData(endpoint: Endpoint, type: string, inputIndex: number) { - if (window?.Cypress && 'canvas' in endpoint.endpoint) { + if (window?.Cypress && 'canvas' in endpoint.endpoint && this.instance) { const canvas = endpoint.endpoint.canvas; - this.instance.setAttribute(canvas, 'data-endpoint-name', this.data.name); + this.instance.setAttribute(canvas, 'data-endpoint-name', this.data?.name ?? ''); this.instance.setAttribute(canvas, 'data-input-index', inputIndex.toString()); this.instance.setAttribute(canvas, 'data-endpoint-type', type); } @@ -216,7 +217,11 @@ export const nodeBase = defineComponent({ spacerIndexes, )[rootTypeIndex]; - const scope = NodeViewUtils.getEndpointScope(inputName as NodeConnectionType); + if (!isValidNodeConnectionType(inputName)) { + return; + } + + const scope = NodeViewUtils.getEndpointScope(inputName); const newEndpointData: EndpointOptions = { uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, inputName, typeIndex), @@ -252,15 +257,15 @@ export const nodeBase = defineComponent({ }; const endpoint = this.instance?.addEndpoint( - this.$refs[this.data.name] as Element, + this.$refs[this.data?.name ?? ''] as Element, newEndpointData, ) as Endpoint; this.__addEndpointTestingData(endpoint, 'input', typeIndex); - if (inputConfiguration.displayName || nodeTypeData.inputNames?.[i]) { + if (inputConfiguration.displayName ?? nodeTypeData.inputNames?.[i]) { // Apply input names if they got set endpoint.addOverlay( NodeViewUtils.getInputNameOverlay( - inputConfiguration.displayName || nodeTypeData.inputNames[i], + inputConfiguration.displayName ?? nodeTypeData.inputNames?.[i] ?? '', inputName, inputConfiguration.required, ), @@ -288,7 +293,7 @@ export const nodeBase = defineComponent({ // } }); if (sortedInputs.length === 0) { - this.instance.manage(this.$refs[this.data.name] as Element); + this.instance?.manage(this.$refs[this.data?.name ?? ''] as Element); } }, getSpacerIndexes( @@ -349,6 +354,10 @@ export const nodeBase = defineComponent({ [key: string]: number; } = {}; + if (!this.data) { + return; + } + this.outputs = NodeHelpers.getNodeOutputs(this.workflow, this.data, nodeTypeData) || []; // TODO: There are still a lot of references of "main" in NodesView and @@ -380,7 +389,7 @@ export const nodeBase = defineComponent({ const endpointLabelLength = getEndpointLabelLength(maxLabelLength); - this.outputs.forEach((value, i) => { + this.outputs.forEach((_value, i) => { const outputConfiguration = outputConfigurations[i]; const outputName: ConnectionTypes = outputConfiguration.type; @@ -419,7 +428,11 @@ export const nodeBase = defineComponent({ outputsOfSameRootType.length, )[rootTypeIndex]; - const scope = NodeViewUtils.getEndpointScope(outputName as NodeConnectionType); + if (!isValidNodeConnectionType(outputName)) { + return; + } + + const scope = NodeViewUtils.getEndpointScope(outputName); const newEndpointData: EndpointOptions = { uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, outputName, typeIndex), @@ -448,13 +461,17 @@ export const nodeBase = defineComponent({ ...this.__getOutputConnectionStyle(outputName, outputConfiguration, nodeTypeData), }; - const endpoint = this.instance.addEndpoint( - this.$refs[this.data.name] as Element, + const endpoint = this.instance?.addEndpoint( + this.$refs[this.data?.name ?? ''] as Element, newEndpointData, ); + if (!endpoint) { + return; + } + this.__addEndpointTestingData(endpoint, 'output', typeIndex); - if (outputConfiguration.displayName) { + if (outputConfiguration.displayName && isValidNodeConnectionType(outputName)) { // Apply output names if they got set const overlaySpec = NodeViewUtils.getOutputNameOverlay( outputConfiguration.displayName, @@ -514,6 +531,10 @@ export const nodeBase = defineComponent({ plusEndpointData.cssClass = `${plusEndpointData.cssClass} ${outputConfiguration?.category}`; } + if (!this.instance || !this.data) { + return; + } + const plusEndpoint = this.instance.addEndpoint( this.$refs[this.data.name] as Element, plusEndpointData, @@ -556,7 +577,7 @@ export const nodeBase = defineComponent({ }; } - if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) { + if (!isValidNodeConnectionType(connectionType)) { return {}; } @@ -614,13 +635,13 @@ export const nodeBase = defineComponent({ }; } - if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) { + if (!isValidNodeConnectionType(connectionType)) { return {}; } return createSupplementalConnectionType(connectionType); }, - touchEnd(e: MouseEvent) { + touchEnd(_e: MouseEvent) { const deviceSupport = useDeviceSupport(); if (deviceSupport.isTouchDevice) { if (this.uiStore.isActionActive('dragActive')) { @@ -651,7 +672,7 @@ export const nodeBase = defineComponent({ this.$emit('deselectAllNodes'); } - if (this.uiStore.isNodeSelected(this.data.name)) { + if (this.uiStore.isNodeSelected(this.data?.name ?? '')) { this.$emit('deselectNode', this.name); } else { this.$emit('nodeSelected', this.name); diff --git a/packages/editor-ui/src/mixins/userHelpers.ts b/packages/editor-ui/src/mixins/userHelpers.ts index eba9375a5a..6013154e4f 100644 --- a/packages/editor-ui/src/mixins/userHelpers.ts +++ b/packages/editor-ui/src/mixins/userHelpers.ts @@ -1,7 +1,6 @@ import { defineComponent } from 'vue'; import type { RouteLocation } from 'vue-router'; import { hasPermission } from '@/utils/rbac/permissions'; -import type { RouteConfig } from '@/types/router'; import type { PermissionTypeOptions } from '@/types/rbac'; export const userHelpers = defineComponent({ @@ -16,7 +15,7 @@ export const userHelpers = defineComponent({ return this.canUserAccessRoute(this.$route); }, - canUserAccessRoute(route: RouteLocation & RouteConfig) { + canUserAccessRoute(route: RouteLocation) { const middleware = route.meta?.middleware; const middlewareOptions = route.meta?.middlewareOptions; diff --git a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts index 5218ea2c3a..20268b67eb 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts @@ -6,6 +6,7 @@ import type { IExecuteData, INodeTypeData, } from 'n8n-workflow'; +import { NodeConnectionType } from 'n8n-workflow'; import { WorkflowDataProxy } from 'n8n-workflow'; import { createTestWorkflowObject } from '@/__tests__/mocks'; @@ -91,7 +92,7 @@ const connections: IConnections = { [ { node: 'Function', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -102,7 +103,7 @@ const connections: IConnections = { [ { node: 'Rename', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -113,7 +114,7 @@ const connections: IConnections = { [ { node: 'End', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], diff --git a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/variables.completions.test.ts b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/variables.completions.test.ts index 681709737f..09864a7bd3 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/variables.completions.test.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/variables.completions.test.ts @@ -14,8 +14,8 @@ beforeEach(() => { describe('variablesCompletions', () => { test('should return completions for $vars prefix', () => { environmentsStore.variables = [ - { key: 'VAR1', value: 'Value1', id: 1 }, - { key: 'VAR2', value: 'Value2', id: 2 }, + { key: 'VAR1', value: 'Value1', id: '1' }, + { key: 'VAR2', value: 'Value2', id: '2' }, ]; const state = EditorState.create({ doc: '$vars.', selection: { anchor: 6 } }); @@ -39,7 +39,7 @@ describe('variablesCompletions', () => { }); test('should escape special characters in matcher', () => { - environmentsStore.variables = [{ key: 'VAR1', value: 'Value1', id: 1 }]; + environmentsStore.variables = [{ key: 'VAR1', value: 'Value1', id: '1' }]; const state = EditorState.create({ doc: '$vars.', selection: { anchor: 6 } }); const context = new CompletionContext(state, 6, true); @@ -49,7 +49,7 @@ describe('variablesCompletions', () => { }); test('should return completions for custom matcher', () => { - environmentsStore.variables = [{ key: 'VAR1', value: 'Value1', id: 1 }]; + environmentsStore.variables = [{ key: 'VAR1', value: 'Value1', id: '1' }]; const state = EditorState.create({ doc: '$custom.', selection: { anchor: 8 } }); const context = new CompletionContext(state, 8, true); diff --git a/packages/editor-ui/src/plugins/components.ts b/packages/editor-ui/src/plugins/components.ts index 24ed30d3b7..d87d7f0ac6 100644 --- a/packages/editor-ui/src/plugins/components.ts +++ b/packages/editor-ui/src/plugins/components.ts @@ -9,7 +9,7 @@ import EnterpriseEdition from '@/components/EnterpriseEdition.ee.vue'; import RBAC from '@/components/RBAC.vue'; import ParameterInputList from '@/components/ParameterInputList.vue'; -export const GlobalComponentsPlugin: Plugin<{}> = { +export const GlobalComponentsPlugin: Plugin = { install(app) { const messageService = useMessage(); @@ -18,7 +18,7 @@ export const GlobalComponentsPlugin: Plugin<{}> = { app.component('ParameterInputList', ParameterInputList); app.use(ElementPlus); - app.use(N8nPlugin); + app.use(N8nPlugin, {}); // app.use(ElLoading); // app.use(ElNotification); diff --git a/packages/editor-ui/src/plugins/connectors/N8nCustomConnector.ts b/packages/editor-ui/src/plugins/connectors/N8nCustomConnector.ts index 34561d23cd..07bd3609d0 100644 --- a/packages/editor-ui/src/plugins/connectors/N8nCustomConnector.ts +++ b/packages/editor-ui/src/plugins/connectors/N8nCustomConnector.ts @@ -169,7 +169,7 @@ export class N8nConnector extends AbstractConnector { targetGap: number; - overrideTargetEndpoint: Endpoint | null; + overrideTargetEndpoint: Endpoint; getEndpointOffset?: (e: Endpoint) => number | null; @@ -517,7 +517,7 @@ export class N8nConnector extends AbstractConnector { } resetTargetEndpoint() { - this.overrideTargetEndpoint = null; + this.overrideTargetEndpoint = null as unknown as Endpoint; } _computeBezier(paintInfo: N8nConnectorPaintGeometry) { diff --git a/packages/editor-ui/src/plugins/directives.ts b/packages/editor-ui/src/plugins/directives.ts index 9302499e31..8b092b4c4f 100644 --- a/packages/editor-ui/src/plugins/directives.ts +++ b/packages/editor-ui/src/plugins/directives.ts @@ -2,7 +2,7 @@ import type { Plugin } from 'vue'; import VueTouchEvents from 'vue3-touch-events'; import { vOnClickOutside } from '@vueuse/components'; -export const GlobalDirectivesPlugin: Plugin<{}> = { +export const GlobalDirectivesPlugin: Plugin = { install(app) { app.use(VueTouchEvents); app.directive('on-click-outside', vOnClickOutside); diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 36df6c6c8b..227c0d3a59 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -1,7 +1,7 @@ import type { Plugin } from 'vue'; import axios from 'axios'; import { createI18n } from 'vue-i18n'; -import { locale } from 'n8n-design-system'; +import { locale, type N8nLocaleTranslateFn } from 'n8n-design-system'; import type { INodeProperties, INodePropertyCollection, INodePropertyOptions } from 'n8n-workflow'; import type { INodeTranslationHeaders } from '@/Interface'; @@ -22,6 +22,8 @@ export const i18nInstance = createI18n({ messages: { en: englishBaseText }, }); +type BaseTextOptions = { adjustToNumber?: number; interpolate?: Record }; + export class I18nClass { private baseTextCache = new Map(); @@ -48,10 +50,7 @@ export class I18nClass { /** * Render a string of base text, i.e. a string with a fixed path to the localized value. Optionally allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces. */ - baseText( - key: BaseTextKey, - options?: { adjustToNumber?: number; interpolate?: Record }, - ): string { + baseText(key: BaseTextKey, options?: BaseTextOptions): string { // Create a unique cache key const cacheKey = `${key}-${JSON.stringify(options)}`; @@ -438,11 +437,10 @@ export function addHeaders(headers: INodeTranslationHeaders, language: string) { export const i18n: I18nClass = new I18nClass(); -export const I18nPlugin: Plugin<{}> = { +export const I18nPlugin: Plugin = { async install(app) { - locale.i18n((key: string, options?: { interpolate: Record }) => - i18nInstance.global.t(key, options?.interpolate || {}), - ); + locale.i18n(((key: string, options?: BaseTextOptions) => + i18nInstance.global.t(key, options?.interpolate ?? {})) as N8nLocaleTranslateFn); app.config.globalProperties.$locale = i18n; diff --git a/packages/editor-ui/src/plugins/icons/index.ts b/packages/editor-ui/src/plugins/icons/index.ts index da674121b6..4f86547847 100644 --- a/packages/editor-ui/src/plugins/icons/index.ts +++ b/packages/editor-ui/src/plugins/icons/index.ts @@ -166,7 +166,7 @@ function addIcon(icon: IconDefinition) { library.add(icon); } -export const FontAwesomePlugin: Plugin<{}> = { +export const FontAwesomePlugin: Plugin = { install: (app) => { addIcon(faAngleDoubleLeft); addIcon(faAngleDown); diff --git a/packages/editor-ui/src/plugins/jsplumb/N8nPlusEndpointType.ts b/packages/editor-ui/src/plugins/jsplumb/N8nPlusEndpointType.ts index 5e2fd290a6..fc19318549 100644 --- a/packages/editor-ui/src/plugins/jsplumb/N8nPlusEndpointType.ts +++ b/packages/editor-ui/src/plugins/jsplumb/N8nPlusEndpointType.ts @@ -32,8 +32,6 @@ export class N8nPlusEndpoint extends EndpointRepresentation = { +export const JsPlumbPlugin: Plugin = { install: () => { - Connectors.register(N8nConnector.type, N8nConnector); + Connectors.register(N8nConnector.type, N8nConnector as Constructable); N8nPlusEndpointRenderer.register(); EndpointFactory.registerHandler(N8nPlusEndpointHandler); diff --git a/packages/editor-ui/src/plugins/telemetry/index.ts b/packages/editor-ui/src/plugins/telemetry/index.ts index 4cb2c1fb2e..60aa37b128 100644 --- a/packages/editor-ui/src/plugins/telemetry/index.ts +++ b/packages/editor-ui/src/plugins/telemetry/index.ts @@ -120,7 +120,7 @@ export class Telemetry { } } - page(route: Route) { + page(route: RouteLocation) { if (this.rudderStack) { if (route.path === this.previousPath) { // avoid duplicate requests query is changed for example on search page @@ -128,8 +128,8 @@ export class Telemetry { } this.previousPath = route.path; - const pageName = route.name; - let properties: { [key: string]: string } = {}; + const pageName = String(route.name); + let properties: Record = {}; if (route.meta?.telemetry && typeof route.meta.telemetry.getProperties === 'function') { properties = route.meta.telemetry.getProperties(route); } @@ -330,7 +330,7 @@ export class Telemetry { export const telemetry = new Telemetry(); -export const TelemetryPlugin: Plugin<{}> = { +export const TelemetryPlugin: Plugin = { install(app) { app.config.globalProperties.$telemetry = telemetry; }, diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index 3b91acafea..74c15162ef 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -14,7 +14,7 @@ import { useSSOStore } from '@/stores/sso.store'; import { EnterpriseEditionFeature, VIEWS, EDITABLE_CANVAS_VIEWS } from '@/constants'; import { useTelemetry } from '@/composables/useTelemetry'; import { middleware } from '@/utils/rbac/middleware'; -import type { RouteConfig, RouterMiddleware } from '@/types/router'; +import type { RouterMiddleware } from '@/types/router'; import { initializeCore } from '@/init'; import { tryToParseNumber } from '@/utils/typesUtils'; import { projectsRoutes } from '@/routes/projects.routes'; @@ -60,17 +60,17 @@ const WorkerView = async () => await import('./views/WorkerView.vue'); const WorkflowHistory = async () => await import('@/views/WorkflowHistory.vue'); const WorkflowOnboardingView = async () => await import('@/views/WorkflowOnboardingView.vue'); -function getTemplatesRedirect(defaultRedirect: VIEWS[keyof VIEWS]) { +function getTemplatesRedirect(defaultRedirect: VIEWS[keyof VIEWS]): { name: string } | false { const settingsStore = useSettingsStore(); const isTemplatesEnabled: boolean = settingsStore.isTemplatesEnabled; if (!isTemplatesEnabled) { - return { name: defaultRedirect || VIEWS.NOT_FOUND }; + return { name: `${defaultRedirect}` || VIEWS.NOT_FOUND }; } return false; } -export const routes = [ +export const routes: RouteRecordRaw[] = [ { path: '/', redirect: '/home/workflows', @@ -742,7 +742,7 @@ export const routes = [ }, }, }, -] as Array; +]; function withCanvasReadOnlyMeta(route: RouteRecordRaw) { if (!route.meta) { @@ -759,7 +759,7 @@ function withCanvasReadOnlyMeta(route: RouteRecordRaw) { const router = createRouter({ history: createWebHistory(import.meta.env.DEV ? '/' : window.BASE_PATH ?? '/'), - scrollBehavior(to: RouteLocationNormalized & RouteConfig, from, savedPosition) { + scrollBehavior(to: RouteLocationNormalized, _, savedPosition) { // saved position == null means the page is NOT visited from history (back button) if (savedPosition === null && to.name === VIEWS.TEMPLATES && to.meta?.setScrollPosition) { // for templates view, reset scroll position in this case @@ -769,7 +769,7 @@ const router = createRouter({ routes: routes.map(withCanvasReadOnlyMeta), }); -router.beforeEach(async (to: RouteLocationNormalized & RouteConfig, from, next) => { +router.beforeEach(async (to: RouteLocationNormalized, from, next) => { try { /** * Initialize application core diff --git a/packages/editor-ui/src/jsplumb.d.ts b/packages/editor-ui/src/shims-jsplumb.d.ts similarity index 69% rename from packages/editor-ui/src/jsplumb.d.ts rename to packages/editor-ui/src/shims-jsplumb.d.ts index 05d2c328ec..1bd6f5aedd 100644 --- a/packages/editor-ui/src/jsplumb.d.ts +++ b/packages/editor-ui/src/shims-jsplumb.d.ts @@ -1,5 +1,12 @@ -import type { Connection, Endpoint, EndpointRepresentation, AbstractConnector, Overlay } from '@jsplumb/core'; +import type { + Connection, + Endpoint, + EndpointRepresentation, + AbstractConnector, + Overlay, +} from '@jsplumb/core'; import type { NodeConnectionType } from 'n8n-workflow'; +import type { N8nEndpointLabelLength } from '@/plugins/jsplumb/N8nPlusEndpointType'; declare module '@jsplumb/core' { interface EndpointRepresentation { @@ -27,8 +34,9 @@ declare module '@jsplumb/core' { nodeName: string; nodeId: string; index: number; + nodeType?: string; totalEndpoints: number; - endpointLabelLength: number; + endpointLabelLength?: N8nEndpointLabelLength; }; - }; + } } diff --git a/packages/editor-ui/src/v3-infinite-loading.d.ts b/packages/editor-ui/src/shims-modules.d.ts similarity index 61% rename from packages/editor-ui/src/v3-infinite-loading.d.ts rename to packages/editor-ui/src/shims-modules.d.ts index 1fa589586f..7191a5a3ab 100644 --- a/packages/editor-ui/src/v3-infinite-loading.d.ts +++ b/packages/editor-ui/src/shims-modules.d.ts @@ -1,3 +1,21 @@ +/** + * Modules + */ + +declare module 'vue-agile'; + +/** + * File types + */ + +declare module '*.json'; +declare module '*.svg'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.gif'; +declare module '*.webp'; + declare module 'v3-infinite-loading' { import { Plugin, DefineComponent } from 'vue'; diff --git a/packages/editor-ui/src/shims-vue.d.ts b/packages/editor-ui/src/shims-vue.d.ts index 6d9f9c7309..99bb947c46 100644 --- a/packages/editor-ui/src/shims-vue.d.ts +++ b/packages/editor-ui/src/shims-vue.d.ts @@ -1,6 +1,16 @@ +import 'vue-router'; import type { I18nClass } from '@/plugins/i18n'; -import type { Route } from 'vue-router'; +import type { Route, RouteLocation } from 'vue-router'; import type { Telemetry } from '@/plugins/telemetry'; +import type { VIEWS } from '@/constants'; +import type { IPermissions } from '@/Interface'; +import type { MiddlewareOptions, RouterMiddlewareType } from '@/types/router'; + +export {}; + +/** + * @docs https://vuejs.org/guide/typescript/options-api.html#augmenting-global-properties + */ declare module 'vue' { interface ComponentCustomOptions { @@ -17,6 +27,26 @@ declare module 'vue' { } /** - * @docs https://vuejs.org/guide/typescript/options-api.html#augmenting-global-properties + * @docs https://router.vuejs.org/guide/advanced/meta */ -export {}; + +declare module 'vue-router' { + interface RouteMeta { + nodeView?: boolean; + templatesEnabled?: boolean; + getRedirect?: + | (() => { name: string } | false) + | ((defaultRedirect: VIEWS[keyof VIEWS]) => { name: string } | false); + permissions?: IPermissions; + middleware?: RouterMiddlewareType[]; + middlewareOptions?: Partial; + telemetry?: { + disabled?: true; + pageCategory?: string; + getProperties?: (route: RouteLocation) => Record; + }; + scrollOffset?: number; + setScrollPosition?: (position: number) => void; + readOnlyCanvas?: boolean; + } +} diff --git a/packages/editor-ui/src/shims.d.ts b/packages/editor-ui/src/shims.d.ts index 643d63b91f..8121b15d89 100644 --- a/packages/editor-ui/src/shims.d.ts +++ b/packages/editor-ui/src/shims.d.ts @@ -1,11 +1,8 @@ -import { VNode, ComponentPublicInstance } from 'vue'; -import { PartialDeep } from 'type-fest'; -import { ExternalHooks } from '@/types/externalHooks'; +import type { VNode, ComponentPublicInstance } from 'vue'; +import type { PartialDeep } from 'type-fest'; +import type { ExternalHooks } from '@/types/externalHooks'; -declare module 'markdown-it-link-attributes'; -declare module 'markdown-it-emoji'; -declare module 'markdown-it-task-lists'; -declare module 'vue-agile'; +export {}; declare global { interface ImportMeta { @@ -37,10 +34,3 @@ declare global { findLast(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T; } } - -declare module '*.svg'; -declare module '*.png'; -declare module '*.jpg'; -declare module '*.jpeg'; -declare module '*.gif'; -declare module '*.webp'; diff --git a/packages/editor-ui/src/stores/canvas.store.ts b/packages/editor-ui/src/stores/canvas.store.ts index d3fb870901..d022e3bce8 100644 --- a/packages/editor-ui/src/stores/canvas.store.ts +++ b/packages/editor-ui/src/stores/canvas.store.ts @@ -19,6 +19,7 @@ import { MANUAL_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants'; import type { BeforeStartEventParams, BrowserJsPlumbInstance, + ConstrainFunction, DragStopEventParams, } from '@jsplumb/browser-ui'; import { newInstance } from '@jsplumb/browser-ui'; @@ -307,14 +308,14 @@ export const useCanvasStore = defineStore('canvas', () => { filter: '.node-description, .node-description .node-name, .node-description .node-subtitle', }, }); - jsPlumbInstanceRef.value?.setDragConstrainFunction((pos: PointXY) => { + jsPlumbInstanceRef.value?.setDragConstrainFunction(((pos: PointXY) => { const isReadOnly = uiStore.isReadOnlyView; if (isReadOnly) { // Do not allow to move nodes in readOnly mode return null; } return pos; - }); + }) as ConstrainFunction); } const jsPlumbInstance = computed(() => jsPlumbInstanceRef.value as BrowserJsPlumbInstance); diff --git a/packages/editor-ui/src/stores/credentials.store.ts b/packages/editor-ui/src/stores/credentials.store.ts index aa3c4d747d..44454c0f34 100644 --- a/packages/editor-ui/src/stores/credentials.store.ts +++ b/packages/editor-ui/src/stores/credentials.store.ts @@ -236,9 +236,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, { }; } }, - enableOAuthCredential(credential: ICredentialsResponse): void { - // enable oauth event to track change between modals - }, async fetchCredentialTypes(forceFetch: boolean): Promise { if (this.allCredentialTypes.length > 0 && !forceFetch) { return; diff --git a/packages/editor-ui/src/stores/logStreaming.store.ts b/packages/editor-ui/src/stores/logStreaming.store.ts index 1ebe04bfec..6eb2573cbe 100644 --- a/packages/editor-ui/src/stores/logStreaming.store.ts +++ b/packages/editor-ui/src/stores/logStreaming.store.ts @@ -201,7 +201,7 @@ export const useLogStreamingStore = defineStore('logStreaming', { return false; } }, - async sendTestMessage(destination: MessageEventBusDestinationOptions) { + async sendTestMessage(destination: MessageEventBusDestinationOptions): Promise { if (!hasDestinationId(destination)) { return false; } diff --git a/packages/editor-ui/src/stores/ndv.store.ts b/packages/editor-ui/src/stores/ndv.store.ts index 1073cc44ff..0c5ae38685 100644 --- a/packages/editor-ui/src/stores/ndv.store.ts +++ b/packages/editor-ui/src/stores/ndv.store.ts @@ -233,6 +233,7 @@ export const useNDVStore = defineStore(STORES.NDV, { isDragging: false, type: '', data: '', + dimensions: null, activeTarget: null, }; }, diff --git a/packages/editor-ui/src/stores/posthog.store.ts b/packages/editor-ui/src/stores/posthog.store.ts index 8596c142c1..0026668688 100644 --- a/packages/editor-ui/src/stores/posthog.store.ts +++ b/packages/editor-ui/src/stores/posthog.store.ts @@ -155,7 +155,7 @@ export const usePostHog = defineStore('posthog', () => { trackExperimentsDebounced(featureFlags.value); } else { // depend on client side evaluation if serverside evaluation fails - window.posthog?.onFeatureFlags?.((keys: string[], map: FeatureFlags) => { + window.posthog?.onFeatureFlags?.((_, map: FeatureFlags) => { featureFlags.value = map; // must be debounced because it is called multiple times by posthog diff --git a/packages/editor-ui/src/stores/rbac.store.ts b/packages/editor-ui/src/stores/rbac.store.ts index 11a17846f1..caba9e8634 100644 --- a/packages/editor-ui/src/stores/rbac.store.ts +++ b/packages/editor-ui/src/stores/rbac.store.ts @@ -32,6 +32,7 @@ export const useRBACStore = defineStore(STORES.RBAC, () => { license: {}, logStreaming: {}, saml: {}, + securityAudit: {}, }); function addGlobalRole(role: IRole) { diff --git a/packages/editor-ui/src/stores/settings.store.ts b/packages/editor-ui/src/stores/settings.store.ts index a1c4c97a6e..e545dfdc1e 100644 --- a/packages/editor-ui/src/stores/settings.store.ts +++ b/packages/editor-ui/src/stores/settings.store.ts @@ -274,7 +274,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { this.setSettings(settings); this.settings.communityNodesEnabled = settings.communityNodesEnabled; - this.setAllowedModules(settings.allowedModules as { builtIn?: string; external?: string }); + this.setAllowedModules(settings.allowedModules); this.setSaveDataErrorExecution(settings.saveDataErrorExecution); this.setSaveDataSuccessExecution(settings.saveDataSuccessExecution); this.setSaveManualExecutions(settings.saveManualExecutions); diff --git a/packages/editor-ui/src/stores/templates.store.ts b/packages/editor-ui/src/stores/templates.store.ts index c67a603aea..64542b404b 100644 --- a/packages/editor-ui/src/stores/templates.store.ts +++ b/packages/editor-ui/src/stores/templates.store.ts @@ -63,7 +63,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { return (id: string): null | ITemplatesCollection => this.collections[id]; }, getCategoryById() { - return (id: string): null | ITemplatesCategory => this.categories[id]; + return (id: string): null | ITemplatesCategory => this.categories[id as unknown as number]; }, getSearchedCollections() { return (query: ITemplatesQuery) => { @@ -121,7 +121,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { * Constructs URLSearchParams object based on the default parameters for the template repository * and provided additional parameters */ - websiteTemplateRepositoryParameters(roleOverride?: string) { + websiteTemplateRepositoryParameters(_roleOverride?: string) { const rootStore = useRootStore(); const userStore = useUsersStore(); const workflowsStore = useWorkflowsStore(); @@ -131,8 +131,12 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { utm_n8n_version: rootStore.versionCli, utm_awc: String(workflowsStore.activeWorkflows.length), }; - const userRole: string | undefined = - userStore.currentUserCloudInfo?.role ?? userStore.currentUser?.personalizationAnswers?.role; + const userRole: string | null | undefined = + userStore.currentUserCloudInfo?.role ?? + (userStore.currentUser?.personalizationAnswers && + 'role' in userStore.currentUser.personalizationAnswers + ? userStore.currentUser.personalizationAnswers.role + : undefined); if (userRole) { defaultParameters.utm_user_role = userRole; diff --git a/packages/editor-ui/src/types/router.ts b/packages/editor-ui/src/types/router.ts index 15d1e120db..b4205ebec0 100644 --- a/packages/editor-ui/src/types/router.ts +++ b/packages/editor-ui/src/types/router.ts @@ -2,9 +2,7 @@ import type { NavigationGuardNext, NavigationGuardWithThis, RouteLocationNormalized, - RouteLocation, } from 'vue-router'; -import type { IPermissions } from '@/Interface'; import type { AuthenticatedPermissionOptions, CustomPermissionOptions, @@ -32,24 +30,6 @@ export type MiddlewareOptions = { role: RolePermissionOptions; }; -export interface RouteConfig { - meta: { - nodeView?: boolean; - templatesEnabled?: boolean; - getRedirect?: () => { name: string } | false; - permissions?: IPermissions; - middleware?: RouterMiddlewareType[]; - middlewareOptions?: Partial; - telemetry?: { - disabled?: true; - getProperties: (route: RouteLocation) => object; - }; - scrollOffset?: number; - setScrollPosition?: (position: number) => void; - readOnlyCanvas?: boolean; - }; -} - export type RouterMiddlewareReturnType = ReturnType>; export interface RouterMiddleware { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 16ef6db925..6f319418c6 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -5,6 +5,7 @@ import type { ITemplatesNode, IVersionNode, NodeAuthenticationOption, + SimplifiedNodeType, } from '@/Interface'; import { CORE_NODES_CATEGORY, @@ -451,21 +452,21 @@ export const getThemedValue = ( }; export const getNodeIcon = ( - nodeType: INodeTypeDescription | IVersionNode, + nodeType: INodeTypeDescription | SimplifiedNodeType | IVersionNode, theme: AppliedThemeOption = 'light', ): string | null => { return getThemedValue(nodeType.icon, theme); }; export const getNodeIconUrl = ( - nodeType: INodeTypeDescription | IVersionNode, + nodeType: INodeTypeDescription | SimplifiedNodeType | IVersionNode, theme: AppliedThemeOption = 'light', ): string | null => { return getThemedValue(nodeType.iconUrl, theme); }; export const getBadgeIconUrl = ( - nodeType: INodeTypeDescription, + nodeType: INodeTypeDescription | SimplifiedNodeType, theme: AppliedThemeOption = 'light', ): string | null => { return getThemedValue(nodeType.badgeIconUrl, theme); diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index d8491765f2..e268d9ae57 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -1,6 +1,6 @@ import { isNumber, isValidNodeConnectionType } from '@/utils/typeGuards'; import { NODE_OUTPUT_DEFAULT_KEY, STICKY_NODE_TYPE } from '@/constants'; -import type { EndpointMeta, EndpointStyle, IBounds, INodeUi, XYPosition } from '@/Interface'; +import type { EndpointStyle, IBounds, INodeUi, XYPosition } from '@/Interface'; import type { ArrayAnchorSpec, ConnectorSpec, OverlaySpec, PaintStyle } from '@jsplumb/common'; import type { Connection, Endpoint, SelectOptions } from '@jsplumb/core'; import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector'; @@ -73,7 +73,7 @@ export const CONNECTOR_FLOWCHART_TYPE: ConnectorSpec = { alwaysRespectStubs: false, loopbackVerticalLength: NODE_SIZE + GRID_SIZE, // height of vertical segment when looping loopbackMinimum: LOOPBACK_MINIMUM, // minimum length before flowchart loops around - getEndpointOffset(endpoint: Endpoint & EndpointMeta) { + getEndpointOffset(endpoint: Endpoint) { const indexOffset = 10; // stub offset between different endpoints of same node const index = endpoint?.__meta ? endpoint.__meta.index : 0; const totalEndpoints = endpoint?.__meta ? endpoint.__meta.totalEndpoints : 0; @@ -320,7 +320,7 @@ export const getOutputNameOverlay = ( options: { id: OVERLAY_OUTPUT_NAME_LABEL, visible: true, - create: (ep: Endpoint & EndpointMeta) => { + create: (ep: Endpoint) => { const label = document.createElement('div'); label.innerHTML = labelText; label.classList.add('node-output-endpoint-label'); @@ -1120,7 +1120,7 @@ export const getPlusEndpoint = ( ): Endpoint | undefined => { const endpoints = getJSPlumbEndpoints(node, instance); return endpoints.find( - (endpoint: Endpoint & EndpointMeta) => + (endpoint: Endpoint) => endpoint.endpoint.type === 'N8nPlus' && endpoint?.__meta?.index === outputIndex, ); }; diff --git a/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts b/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts index d91bb9b66f..117c955ef2 100644 --- a/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts +++ b/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts @@ -11,6 +11,7 @@ describe('templateTransforms', () => { getNodeType: vitest.fn(), }; const node = newWorkflowTemplateNode({ + id: 'twitter', type: 'n8n-nodes-base.twitter', credentials: { twitterOAuth1Api: 'old1', @@ -40,6 +41,7 @@ describe('templateTransforms', () => { getNodeType: vitest.fn(), }; const node = newWorkflowTemplateNode({ + id: 'twitter', type: 'n8n-nodes-base.twitter', }); const toReplaceWith = { diff --git a/packages/editor-ui/src/utils/testData/templateTestData.ts b/packages/editor-ui/src/utils/testData/templateTestData.ts index 28a3c83952..255104459e 100644 --- a/packages/editor-ui/src/utils/testData/templateTestData.ts +++ b/packages/editor-ui/src/utils/testData/templateTestData.ts @@ -7,6 +7,7 @@ export const newWorkflowTemplateNode = ({ type, ...optionalOpts }: Pick & + Pick & Partial): IWorkflowTemplateNode => ({ type, name: faker.commerce.productName(), @@ -306,7 +307,6 @@ export const fullSaveEmailAttachmentsToNextCloudTemplate = { export const fullCreateApiEndpointTemplate = { id: 1750, name: 'Creating an API endpoint', - recentViews: 9899, totalViews: 13265, createdAt: '2022-07-06T14:45:19.659Z', description: @@ -393,7 +393,6 @@ export const fullCreateApiEndpointTemplate = { }, }, }, - lastUpdatedBy: 1, workflowInfo: { nodeCount: 2, nodeTypes: {}, diff --git a/packages/editor-ui/src/utils/typeGuards.ts b/packages/editor-ui/src/utils/typeGuards.ts index 90268b88a6..9cff433220 100644 --- a/packages/editor-ui/src/utils/typeGuards.ts +++ b/packages/editor-ui/src/utils/typeGuards.ts @@ -5,7 +5,7 @@ import type { TriggerPanelDefinition, } from 'n8n-workflow'; import { nodeConnectionTypes } from 'n8n-workflow'; -import type { ICredentialsResponse, NewCredentialsModal } from '@/Interface'; +import type { IExecutionResponse, ICredentialsResponse, NewCredentialsModal } from '@/Interface'; import type { jsPlumbDOMElement } from '@jsplumb/browser-ui'; import type { Connection } from '@jsplumb/core'; @@ -73,3 +73,9 @@ export function isTriggerPanelObject( ): triggerPanel is TriggerPanelDefinition { return triggerPanel !== undefined && typeof triggerPanel === 'object' && triggerPanel !== null; } + +export function isFullExecutionResponse( + execution: IExecutionResponse | null, +): execution is IExecutionResponse { + return !!execution && 'status' in execution; +} diff --git a/packages/editor-ui/src/views/CredentialsView.vue b/packages/editor-ui/src/views/CredentialsView.vue index 7f22979a04..b2322f2226 100644 --- a/packages/editor-ui/src/views/CredentialsView.vue +++ b/packages/editor-ui/src/views/CredentialsView.vue @@ -63,6 +63,7 @@ import type { ICredentialsResponse, ICredentialTypeMap } from '@/Interface'; import { defineComponent } from 'vue'; +import type { IResource } from '@/components/layouts/ResourcesListLayout.vue'; import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue'; import CredentialCard from '@/components/CredentialCard.vue'; import type { ICredentialType } from 'n8n-workflow'; @@ -106,8 +107,14 @@ export default defineComponent({ useExternalSecretsStore, useProjectsStore, ), - allCredentials(): ICredentialsResponse[] { - return this.credentialsStore.allCredentials; + allCredentials(): IResource[] { + return this.credentialsStore.allCredentials.map((credential) => ({ + id: credential.id, + name: credential.name, + value: '', + updatedAt: credential.updatedAt, + createdAt: credential.createdAt, + })); }, allCredentialTypes(): ICredentialType[] { return this.credentialsStore.allCredentialTypes; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 57b10a1156..3cd2039176 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2178,7 +2178,11 @@ export default defineComponent({ } } - return await this.importWorkflowData(workflowData!, 'paste', false); + if (!workflowData) { + return; + } + + return await this.importWorkflowData(workflowData, 'paste', false); } }, @@ -2205,7 +2209,7 @@ export default defineComponent({ // Imports the given workflow data into the current workflow async importWorkflowData( - workflowData: IWorkflowToShare, + workflowData: IWorkflowDataUpdate, source: string, importTags = true, ): Promise { @@ -2342,7 +2346,7 @@ export default defineComponent({ } }, - removeUnknownCredentials(workflow: IWorkflowToShare) { + removeUnknownCredentials(workflow: IWorkflowDataUpdate) { if (!workflow?.nodes) return; for (const node of workflow.nodes) { diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.test.ts b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.test.ts index dd6f18d4d3..1e907a1dd6 100644 --- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.test.ts +++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.test.ts @@ -93,6 +93,7 @@ describe('SetupWorkflowFromTemplateView store', () => { const templatesStore = useTemplatesStore(); const workflow = testData.newFullOneNodeTemplate({ + id: 'workflow', name: 'Test', type: 'n8n-nodes-base.httpRequest', typeVersion: 1, diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts index 96c9162b9b..28299b475d 100644 --- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts +++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts @@ -15,6 +15,7 @@ const objToMap = (obj: Record) => { describe('useCredentialSetupState', () => { const nodesByName = { Twitter: { + id: 'twitter', name: 'Twitter', type: 'n8n-nodes-base.twitter', position: [720, -220], @@ -58,12 +59,14 @@ describe('useCredentialSetupState', () => { it('returns credentials grouped when the credential names are the same', () => { const [node1, node2] = [ newWorkflowTemplateNode({ + id: 'twitter', type: 'n8n-nodes-base.twitter', credentials: { twitterOAuth1Api: 'credential', }, }) as IWorkflowTemplateNodeWithCredentials, newWorkflowTemplateNode({ + id: 'telegram', type: 'n8n-nodes-base.telegram', credentials: { telegramApi: 'credential', diff --git a/packages/editor-ui/src/views/TemplatesSearchView.vue b/packages/editor-ui/src/views/TemplatesSearchView.vue index 55cfdcf37c..05a2359ea5 100644 --- a/packages/editor-ui/src/views/TemplatesSearchView.vue +++ b/packages/editor-ui/src/views/TemplatesSearchView.vue @@ -122,6 +122,16 @@ export default defineComponent({ TemplateList, TemplatesView, }, + beforeRouteLeave(_to, _from, next) { + const contentArea = document.getElementById('content'); + if (contentArea) { + // When leaving this page, store current scroll position in route data + this.$route.meta?.setScrollPosition?.(contentArea.scrollTop); + } + + this.trackSearch(); + next(); + }, setup() { const { callDebounced } = useDebounce(); @@ -406,21 +416,6 @@ export default defineComponent({ }, 0); }, }, - beforeRouteLeave(to, from, next) { - const contentArea = document.getElementById('content'); - if (contentArea) { - // When leaving this page, store current scroll position in route data - if ( - this.$route.meta?.setScrollPosition && - typeof this.$route.meta.setScrollPosition === 'function' - ) { - this.$route.meta.setScrollPosition(contentArea.scrollTop); - } - } - - this.trackSearch(); - next(); - }, }); diff --git a/packages/editor-ui/src/views/VariablesView.vue b/packages/editor-ui/src/views/VariablesView.vue index d218afed9b..734ab7280b 100644 --- a/packages/editor-ui/src/views/VariablesView.vue +++ b/packages/editor-ui/src/views/VariablesView.vue @@ -122,7 +122,7 @@ const resourceToEnvironmentVariable = (data: IResource): EnvironmentVariable => return { id: data.id, key: data.name, - value: data.value, + value: 'value' in data ? data.value : '', }; }; diff --git a/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts b/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts index 39f07116cc..d8b9834b86 100644 --- a/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts +++ b/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts @@ -12,7 +12,7 @@ import { createUser } from '@/__tests__/data/users'; import { createProjectListItem } from '@/__tests__/data/projects'; import { useRBACStore } from '@/stores/rbac.store'; import { DELETE_USER_MODAL_KEY } from '@/constants'; -import { expect } from 'vitest'; +import * as usersApi from '@/api/users'; const wrapperComponentWithModal = { components: { SettingsUsersView, ModalRoot, DeleteUserModal }, @@ -34,31 +34,34 @@ const loggedInUser = createUser(); const users = Array.from({ length: 3 }, createUser); const personalProjects = Array.from({ length: 3 }, createProjectListItem); +let pinia: ReturnType; let projectsStore: ReturnType; let usersStore: ReturnType; let rbacStore: ReturnType; describe('SettingsUsersView', () => { beforeEach(() => { - setActivePinia(createPinia()); + pinia = createPinia(); + setActivePinia(pinia); projectsStore = useProjectsStore(); usersStore = useUsersStore(); rbacStore = useRBACStore(); vi.spyOn(rbacStore, 'hasScope').mockReturnValue(true); - vi.spyOn(usersStore, 'fetchUsers').mockImplementation(async () => await Promise.resolve()); + vi.spyOn(usersApi, 'getUsers').mockResolvedValue(users); vi.spyOn(usersStore, 'allUsers', 'get').mockReturnValue(users); - vi.spyOn(usersStore, 'getUserById', 'get').mockReturnValue(() => loggedInUser); vi.spyOn(projectsStore, 'getAllProjects').mockImplementation( async () => await Promise.resolve(), ); vi.spyOn(projectsStore, 'personalProjects', 'get').mockReturnValue(personalProjects); + + usersStore.currentUserId = loggedInUser.id; }); it('should show confirmation modal before deleting user and delete with transfer', async () => { const deleteUserSpy = vi.spyOn(usersStore, 'deleteUser').mockImplementation(async () => {}); - const { getByTestId } = renderComponent(); + const { getByTestId } = renderComponent({ pinia }); const userListItem = getByTestId(`user-list-item-${users[0].email}`); expect(userListItem).toBeInTheDocument(); @@ -98,7 +101,7 @@ describe('SettingsUsersView', () => { it('should show confirmation modal before deleting user and delete without transfer', async () => { const deleteUserSpy = vi.spyOn(usersStore, 'deleteUser').mockImplementation(async () => {}); - const { getByTestId } = renderComponent(); + const { getByTestId } = renderComponent({ pinia }); const userListItem = getByTestId(`user-list-item-${users[0].email}`); expect(userListItem).toBeInTheDocument(); diff --git a/packages/editor-ui/tsconfig.json b/packages/editor-ui/tsconfig.json index 621157cd8a..68bae33584 100644 --- a/packages/editor-ui/tsconfig.json +++ b/packages/editor-ui/tsconfig.json @@ -14,11 +14,7 @@ "types": [ "vitest/globals", "unplugin-icons/types/vue", - "./src/shims.d.ts", - "./src/shims-vue.d.ts", - "./src/v3-infinite-loading.d.ts", - "../workflow/src/types.d.ts", - "../design-system/src/shims-markdown-it.d.ts" + "../design-system/src/shims-modules.d.ts" ], "paths": { "@/*": ["./src/*"], diff --git a/packages/editor-ui/vite.config.mts b/packages/editor-ui/vite.config.mts index 6fc8bc12b9..7689d93af1 100644 --- a/packages/editor-ui/vite.config.mts +++ b/packages/editor-ui/vite.config.mts @@ -77,8 +77,12 @@ const plugins = [ }), vue(), ]; -if (process.env.ENABLE_TYPE_CHECKING === 'true') { - plugins.push(checker({ vueTsc: true })); + +if (!process.env.VITEST) { + plugins.push({ + ...checker({ vueTsc: true }), + apply: 'build' + }); } const { SENTRY_AUTH_TOKEN: authToken, RELEASE: release } = process.env; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index e78179e269..45aaefb285 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -116,21 +116,21 @@ export interface IUser { lastName: string; } +export type ProjectSharingData = { + id: string; + name: string | null; + type: 'personal' | 'team' | 'public'; + createdAt: string; + updatedAt: string; +}; + export interface ICredentialsDecrypted { id: string; name: string; type: string; data?: ICredentialDataDecryptedObject; - homeProject?: { - id: string; - name: string | null; - type: 'personal' | 'team' | 'public'; - }; - sharedWithProjects?: Array<{ - id: string; - name: string | null; - type: 'personal' | 'team' | 'public'; - }>; + homeProject?: ProjectSharingData; + sharedWithProjects?: ProjectSharingData[]; } export interface ICredentialsEncrypted { @@ -340,7 +340,13 @@ export interface ICredentialData { } // The encrypted credentials which the nodes can access -export type CredentialInformation = string | number | boolean | IDataObject | IDataObject[]; +export type CredentialInformation = + | string + | string[] + | number + | boolean + | IDataObject + | IDataObject[]; // The encrypted credentials which the nodes can access export interface ICredentialDataDecryptedObject { @@ -1530,7 +1536,7 @@ export interface INodeIssueObjectProperty { export interface INodeIssueData { node: string; type: INodeIssueTypes; - value: boolean | string | string[] | INodeIssueObjectProperty; + value: null | boolean | string | string[] | INodeIssueObjectProperty; } export interface INodeIssues { @@ -1571,7 +1577,7 @@ export interface INodeTypeBaseDescription { icon?: Themed; iconColor?: NodeIconColor; iconUrl?: Themed; - badgeIconUrl?: string; + badgeIconUrl?: Themed; group: string[]; description: string; documentationUrl?: string; diff --git a/packages/workflow/src/MessageEventBus.ts b/packages/workflow/src/MessageEventBus.ts index 97ca4116b4..a6d44ced9f 100644 --- a/packages/workflow/src/MessageEventBus.ts +++ b/packages/workflow/src/MessageEventBus.ts @@ -21,6 +21,13 @@ export const enum MessageEventBusDestinationTypeNames { syslog = '$$MessageEventBusDestinationSyslog', } +export const messageEventBusDestinationTypeNames = [ + MessageEventBusDestinationTypeNames.abstract, + MessageEventBusDestinationTypeNames.webhook, + MessageEventBusDestinationTypeNames.sentry, + MessageEventBusDestinationTypeNames.syslog, +]; + // =============================== // Event Message Interfaces // ===============================