diff --git a/packages/editor-ui/src/__tests__/defaults.ts b/packages/editor-ui/src/__tests__/defaults.ts index b3b18bdb5f1904..b1a0e0dec8057f 100644 --- a/packages/editor-ui/src/__tests__/defaults.ts +++ b/packages/editor-ui/src/__tests__/defaults.ts @@ -1,59 +1,4 @@ -import type { INodeTypeData, INodeTypeDescription, IN8nUISettings } from 'n8n-workflow'; -import { - AGENT_NODE_TYPE, - SET_NODE_TYPE, - CHAT_TRIGGER_NODE_TYPE, - MANUAL_TRIGGER_NODE_TYPE, -} from '@/constants'; -import nodeTypesJson from '../../../nodes-base/dist/types/nodes.json'; -import aiNodeTypesJson from '../../../@n8n/nodes-langchain/dist/types/nodes.json'; - -const allNodeTypes = [...nodeTypesJson, ...aiNodeTypesJson]; - -export function findNodeTypeDescriptionByName(name: string): INodeTypeDescription { - return allNodeTypes.find((node) => node.name === name) as INodeTypeDescription; -} - -export const testingNodeTypes: INodeTypeData = { - [MANUAL_TRIGGER_NODE_TYPE]: { - sourcePath: '', - type: { - description: findNodeTypeDescriptionByName(MANUAL_TRIGGER_NODE_TYPE), - }, - }, - [SET_NODE_TYPE]: { - sourcePath: '', - type: { - description: findNodeTypeDescriptionByName(SET_NODE_TYPE), - }, - }, - [CHAT_TRIGGER_NODE_TYPE]: { - sourcePath: '', - type: { - description: findNodeTypeDescriptionByName(CHAT_TRIGGER_NODE_TYPE), - }, - }, - [AGENT_NODE_TYPE]: { - sourcePath: '', - type: { - description: findNodeTypeDescriptionByName(AGENT_NODE_TYPE), - }, - }, -}; - -export const defaultMockNodeTypes: INodeTypeData = { - [MANUAL_TRIGGER_NODE_TYPE]: testingNodeTypes[MANUAL_TRIGGER_NODE_TYPE], - [SET_NODE_TYPE]: testingNodeTypes[SET_NODE_TYPE], -}; - -export function mockNodeTypesToArray(nodeTypes: INodeTypeData): INodeTypeDescription[] { - return Object.values(nodeTypes).map( - (nodeType) => nodeType.type.description as INodeTypeDescription, - ); -} - -export const defaultMockNodeTypesArray: INodeTypeDescription[] = - mockNodeTypesToArray(defaultMockNodeTypes); +import type { IN8nUISettings } from 'n8n-workflow'; export const defaultSettings: IN8nUISettings = { allowedModules: {}, diff --git a/packages/editor-ui/src/__tests__/mocks.ts b/packages/editor-ui/src/__tests__/mocks.ts index 65de1a99c4f8e5..d8916bbc6f3c46 100644 --- a/packages/editor-ui/src/__tests__/mocks.ts +++ b/packages/editor-ui/src/__tests__/mocks.ts @@ -2,48 +2,76 @@ import type { INodeType, INodeTypeData, INodeTypes, - IVersionedNodeType, IConnections, IDataObject, INode, IPinData, IWorkflowSettings, + LoadedClass, + INodeTypeDescription, } from 'n8n-workflow'; import { NodeHelpers, Workflow } from 'n8n-workflow'; import { uuid } from '@jsplumb/util'; -import { defaultMockNodeTypes } from '@/__tests__/defaults'; -import type { INodeUi, ITag, IUsedCredential, IWorkflowDb, WorkflowMetadata } from '@/Interface'; -import type { ProjectSharingData } from '@/types/projects.types'; -import type { RouteLocationNormalized } from 'vue-router'; +import { mock } from 'vitest-mock-extended'; -export function createTestNodeTypes(data: INodeTypeData = {}): INodeTypes { - const nodeTypes = { - ...defaultMockNodeTypes, - ...Object.keys(data).reduce((acc, key) => { - acc[key] = data[key]; +import { + AGENT_NODE_TYPE, + CHAT_TRIGGER_NODE_TYPE, + CODE_NODE_TYPE, + EXECUTABLE_TRIGGER_NODE_TYPES, + MANUAL_TRIGGER_NODE_TYPE, + NO_OP_NODE_TYPE, + SET_NODE_TYPE, +} from '@/constants'; - return acc; - }, {}), - }; +const mockNode = (name: string, type: string, props: Partial = {}) => + mock({ name, type, ...props }); - function getKnownTypes(): IDataObject { - return {}; - } +const mockLoadedClass = (name: string) => + mock>({ + type: mock({ + // @ts-expect-error + description: mock({ + name, + displayName: name, + version: 1, + properties: [], + group: EXECUTABLE_TRIGGER_NODE_TYPES.includes(name) ? ['trigger'] : [], + inputs: ['main'], + outputs: ['main'], + documentationUrl: 'https://docs', + webhooks: undefined, + }), + }), + }); - function getByName(nodeType: string): INodeType | IVersionedNodeType { - return nodeTypes[nodeType].type; - } +export const mockNodes = [ + mockNode('Manual Trigger', MANUAL_TRIGGER_NODE_TYPE), + mockNode('Set', SET_NODE_TYPE), + mockNode('Code', CODE_NODE_TYPE), + mockNode('Rename', SET_NODE_TYPE), + mockNode('Chat Trigger', CHAT_TRIGGER_NODE_TYPE), + mockNode('Agent', AGENT_NODE_TYPE), + mockNode('End', NO_OP_NODE_TYPE), +]; - function getByNameAndVersion(nodeType: string, version?: number): INodeType { - return NodeHelpers.getVersionedNodeType(getByName(nodeType), version); - } +export const defaultNodeTypes = mockNodes.reduce((acc, { type }) => { + acc[type] = mockLoadedClass(type); + return acc; +}, {}); - return { - getKnownTypes, - getByName, - getByNameAndVersion, - }; -} +export const defaultNodeDescriptions = Object.values(defaultNodeTypes).map( + ({ type }) => type.description, +) as INodeTypeDescription[]; + +const nodeTypes = mock({ + getByName(nodeType) { + return defaultNodeTypes[nodeType].type; + }, + getByNameAndVersion(nodeType: string, version?: number): INodeType { + return NodeHelpers.getVersionedNodeType(defaultNodeTypes[nodeType].type, version); + }, +}); export function createTestWorkflowObject({ id = uuid(), @@ -51,7 +79,6 @@ export function createTestWorkflowObject({ nodes = [], connections = {}, active = false, - nodeTypes = {}, staticData = {}, settings = {}, pinData = {}, @@ -61,7 +88,6 @@ export function createTestWorkflowObject({ nodes?: INode[]; connections?: IConnections; active?: boolean; - nodeTypes?: INodeTypeData; staticData?: IDataObject; settings?: IWorkflowSettings; pinData?: IPinData; @@ -75,38 +101,10 @@ export function createTestWorkflowObject({ staticData, settings, pinData, - nodeTypes: createTestNodeTypes(nodeTypes), + nodeTypes, }); } -export function createTestWorkflow(options: { - id?: string; - name: string; - active?: boolean; - createdAt?: number | string; - updatedAt?: number | string; - nodes?: INodeUi[]; - connections?: IConnections; - settings?: IWorkflowSettings; - tags?: ITag[] | string[]; - pinData?: IPinData; - sharedWithProjects?: ProjectSharingData[]; - homeProject?: ProjectSharingData; - versionId?: string; - usedCredentials?: IUsedCredential[]; - meta?: WorkflowMetadata; -}): IWorkflowDb { - return { - ...options, - createdAt: options.createdAt ?? '', - updatedAt: options.updatedAt ?? '', - versionId: options.versionId ?? '', - id: options.id ?? uuid(), - active: options.active ?? false, - connections: options.connections ?? {}, - } as IWorkflowDb; -} - export function createTestNode(node: Partial = {}): INode { return { id: uuid(), @@ -118,27 +116,3 @@ export function createTestNode(node: Partial = {}): INode { ...node, }; } - -export function createTestRouteLocation({ - path = '', - params = {}, - fullPath = path, - hash = '', - matched = [], - redirectedFrom = undefined, - name = path, - meta = {}, - query = {}, -}: Partial = {}): RouteLocationNormalized { - return { - path, - params, - fullPath, - hash, - matched, - redirectedFrom, - name, - meta, - query, - }; -} diff --git a/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts b/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts index 3ebf73f93d0a81..0eb04df3968ec0 100644 --- a/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts +++ b/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts @@ -1,20 +1,33 @@ -import { createComponentRenderer } from '@/__tests__/render'; +import { createTestingPinia } from '@pinia/testing'; +import { mock } from 'vitest-mock-extended'; +import type { INodeTypeDescription } from 'n8n-workflow'; + import CredentialIcon from '@/components/CredentialIcon.vue'; import { STORES } from '@/constants'; -import { createTestingPinia } from '@pinia/testing'; -import * as testNodeTypes from './testData/nodeTypesTestData'; -import merge from 'lodash-es/merge'; import { groupNodeTypesByNameAndType } from '@/utils/nodeTypes/nodeTypeTransforms'; -const defaultState = { +import { createComponentRenderer } from '@/__tests__/render'; + +const twitterV1 = mock({ + version: 1, + credentials: [{ name: 'twitterOAuth1Api', required: true }], + iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', +}); + +const twitterV2 = mock({ + version: 2, + credentials: [{ name: 'twitterOAuth2Api', required: true }], + iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', +}); + +const nodeTypes = groupNodeTypesByNameAndType([twitterV1, twitterV2]); +const initialState = { [STORES.CREDENTIALS]: {}, - [STORES.NODE_TYPES]: {}, + [STORES.NODE_TYPES]: { nodeTypes }, }; const renderComponent = createComponentRenderer(CredentialIcon, { - pinia: createTestingPinia({ - initialState: defaultState, - }), + pinia: createTestingPinia({ initialState }), global: { stubs: ['n8n-tooltip'], }, @@ -25,17 +38,7 @@ describe('CredentialIcon', () => { it('shows correct icon for credential type that is for the latest node type version', () => { const { baseElement } = renderComponent({ - pinia: createTestingPinia({ - initialState: merge(defaultState, { - [STORES.CREDENTIALS]: {}, - [STORES.NODE_TYPES]: { - nodeTypes: groupNodeTypesByNameAndType([ - testNodeTypes.twitterV1, - testNodeTypes.twitterV2, - ]), - }, - }), - }), + pinia: createTestingPinia({ initialState }), props: { credentialTypeName: 'twitterOAuth2Api', }, @@ -49,17 +52,7 @@ describe('CredentialIcon', () => { it('shows correct icon for credential type that is for an older node type version', () => { const { baseElement } = renderComponent({ - pinia: createTestingPinia({ - initialState: merge(defaultState, { - [STORES.CREDENTIALS]: {}, - [STORES.NODE_TYPES]: { - nodeTypes: groupNodeTypesByNameAndType([ - testNodeTypes.twitterV1, - testNodeTypes.twitterV2, - ]), - }, - }), - }), + pinia: createTestingPinia({ initialState }), props: { credentialTypeName: 'twitterOAuth1Api', }, diff --git a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts index 19381df410e10c..d6fb0fb61984a0 100644 --- a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts +++ b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts @@ -1,24 +1,23 @@ +import { createPinia, setActivePinia } from 'pinia'; +import { waitFor } from '@testing-library/vue'; +import { mock } from 'vitest-mock-extended'; + import NodeDetailsView from '@/components/NodeDetailsView.vue'; import { VIEWS } from '@/constants'; -import { createComponentRenderer } from '@/__tests__/render'; -import { waitFor } from '@testing-library/vue'; -import { uuid } from '@jsplumb/util'; -import type { INode } from 'n8n-workflow'; -import { createTestNode, createTestWorkflow } from '@/__tests__/mocks'; +import type { IWorkflowDb } from '@/Interface'; import { useSettingsStore } from '@/stores/settings.store'; import { useUsersStore } from '@/stores/users.store'; import { useNDVStore } from '@/stores/ndv.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { createPinia, setActivePinia } from 'pinia'; -import { defaultMockNodeTypesArray } from '@/__tests__/defaults'; + +import { createComponentRenderer } from '@/__tests__/render'; import { setupServer } from '@/__tests__/server'; +import { defaultNodeDescriptions, mockNodes } from '@/__tests__/mocks'; -async function createPiniaWithActiveNode(node: INode) { - const workflowId = uuid(); - const workflow = createTestWorkflow({ - id: workflowId, - name: 'Test Workflow', +async function createPiniaWithActiveNode() { + const node = mockNodes[0]; + const workflow = mock({ connections: {}, active: true, nodes: [node], @@ -31,7 +30,7 @@ async function createPiniaWithActiveNode(node: INode) { const nodeTypesStore = useNodeTypesStore(); const ndvStore = useNDVStore(); - nodeTypesStore.setNodeTypes(defaultMockNodeTypesArray); + nodeTypesStore.setNodeTypes(defaultNodeDescriptions); workflowsStore.workflow = workflow; ndvStore.activeNodeName = node.name; @@ -72,12 +71,7 @@ describe('NodeDetailsView', () => { it('should render correctly', async () => { const wrapper = renderComponent({ - pinia: await createPiniaWithActiveNode( - createTestNode({ - name: 'Manual Trigger', - type: 'manualTrigger', - }), - ), + pinia: await createPiniaWithActiveNode(), }); await waitFor(() => diff --git a/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts b/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts index e98838caf312b0..a8dea7e34789cb 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowLMChatModal.test.ts @@ -1,19 +1,35 @@ -import WorkflowLMChatModal from '@/components/WorkflowLMChat.vue'; -import { AGENT_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE, WORKFLOW_LM_CHAT_MODAL_KEY } from '@/constants'; -import { createComponentRenderer } from '@/__tests__/render'; -import { fireEvent, waitFor } from '@testing-library/vue'; -import { uuid } from '@jsplumb/util'; -import { createTestNode, createTestWorkflow } from '@/__tests__/mocks'; import { createPinia, setActivePinia } from 'pinia'; +import { fireEvent, waitFor } from '@testing-library/vue'; +import { mock } from 'vitest-mock-extended'; +import { NodeConnectionType } from 'n8n-workflow'; +import type { IConnections, INode } from 'n8n-workflow'; + +import WorkflowLMChatModal from '@/components/WorkflowLMChat.vue'; +import { WORKFLOW_LM_CHAT_MODAL_KEY } from '@/constants'; +import type { IWorkflowDb } from '@/Interface'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useUIStore } from '@/stores/ui.store'; import { useUsersStore } from '@/stores/users.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { testingNodeTypes, mockNodeTypesToArray } from '@/__tests__/defaults'; + +import { createComponentRenderer } from '@/__tests__/render'; import { setupServer } from '@/__tests__/server'; -import { NodeConnectionType } from 'n8n-workflow'; -import type { IConnections } from 'n8n-workflow'; +import { defaultNodeDescriptions, mockNodes } from '@/__tests__/mocks'; + +const connections: IConnections = { + 'Chat Trigger': { + main: [ + [ + { + node: 'Agent', + type: NodeConnectionType.Main, + index: 0, + }, + ], + ], + }, +}; const renderComponent = createComponentRenderer(WorkflowLMChatModal, { props: { @@ -24,40 +40,14 @@ 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', - active: true, + const chatTriggerNode = mockNodes[4]; + const agentNode = mockNodes[5]; + const nodes: INode[] = [chatTriggerNode]; + if (withAgentNode) nodes.push(agentNode); + const workflow = mock({ + nodes, ...(withConnections ? { connections } : {}), - nodes: [ - createTestNode({ - name: 'Chat Trigger', - type: CHAT_TRIGGER_NODE_TYPE, - }), - ...(withAgentNode - ? [ - createTestNode({ - name: 'Agent', - type: AGENT_NODE_TYPE, - }), - ] - : []), - ], }); const pinia = createPinia(); @@ -67,12 +57,7 @@ async function createPiniaWithAINodes(options = { withConnections: true, withAge const nodeTypesStore = useNodeTypesStore(); const uiStore = useUIStore(); - nodeTypesStore.setNodeTypes( - mockNodeTypesToArray({ - [CHAT_TRIGGER_NODE_TYPE]: testingNodeTypes[CHAT_TRIGGER_NODE_TYPE], - [AGENT_NODE_TYPE]: testingNodeTypes[AGENT_NODE_TYPE], - }), - ); + nodeTypesStore.setNodeTypes(defaultNodeDescriptions); workflowsStore.workflow = workflow; await useSettingsStore().getSettings(); diff --git a/packages/editor-ui/src/components/__tests__/testData/nodeTypesTestData.ts b/packages/editor-ui/src/components/__tests__/testData/nodeTypesTestData.ts deleted file mode 100644 index c5c07eb767a29a..00000000000000 --- a/packages/editor-ui/src/components/__tests__/testData/nodeTypesTestData.ts +++ /dev/null @@ -1,964 +0,0 @@ -import type { INodeTypeDescription } from 'n8n-workflow'; - -export const twitterV2: INodeTypeDescription = { - displayName: 'X (Formerly Twitter)', - name: 'n8n-nodes-base.twitter', - group: ['output'], - subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', - description: 'Post, like, and search tweets, send messages, search users, and add users to lists', - defaultVersion: 2, - version: 2, - defaults: { name: 'X' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'twitterOAuth2Api', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Direct Message', - value: 'directMessage', - description: 'Send a direct message to a user', - }, - { name: 'List', value: 'list', description: 'Add a user to a list' }, - { name: 'Tweet', value: 'tweet', description: 'Create, like, search, or delete a tweet' }, - { name: 'User', value: 'user', description: 'Search users by username' }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'tweet', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['directMessage'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Send a direct message to a user', - action: 'Create Direct Message', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to send the message to', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - typeOptions: { rows: 2 }, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: - 'The text of the direct message. URL encoding is required. Max length of 10,000 characters.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - options: [ - { - displayName: 'Attachment ID', - name: 'attachments', - type: 'string', - default: '', - placeholder: '1664279886239010824', - description: 'The attachment ID to associate with the message', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['list'] } }, - options: [ - { - name: 'Add Member', - value: 'add', - description: 'Add a member to a list', - action: 'Add Member to List', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'add', - }, - { - displayName: 'List', - name: 'list', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The list you want to add the user to', - displayOptions: { show: { operation: ['add'], resource: ['list'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 99923132', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/i/lists/99923132', - url: '', - }, - ], - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to add to the list', - displayOptions: { show: { operation: ['add'], resource: ['list'] } }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['tweet'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create, quote, or reply to a tweet', - action: 'Create Tweet', - }, - { name: 'Delete', value: 'delete', description: 'Delete a tweet', action: 'Delete Tweet' }, - { name: 'Like', value: 'like', description: 'Like a tweet', action: 'Like Tweet' }, - { - name: 'Retweet', - value: 'retweet', - description: 'Retweet a tweet', - action: 'Retweet Tweet', - }, - { - name: 'Search', - value: 'search', - description: 'Search for tweets from the last seven days', - action: 'Search Tweets', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - typeOptions: { rows: 2 }, - default: '', - required: true, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - description: - 'The text of the status update. URLs must be encoded. Links wrapped with the t.co shortener will affect character count', - }, - { - displayName: 'Options', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Location ID', - name: 'location', - type: 'string', - placeholder: '4e696bef7e24d378', - default: '', - description: 'Location information for the tweet', - }, - { - displayName: 'Media ID', - name: 'attachments', - type: 'string', - default: '', - placeholder: '1664279886239010824', - description: 'The attachment ID to associate with the message', - }, - { - displayName: 'Quote a Tweet', - name: 'inQuoteToStatusId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - description: 'The tweet being quoted', - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Reply to Tweet', - name: 'inReplyToStatusId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - description: 'The tweet being replied to', - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - ], - }, - { - displayName: 'Locations are not supported due to Twitter V2 API limitations', - name: 'noticeLocation', - type: 'notice', - displayOptions: { show: { '/additionalFields.location': [''] } }, - default: '', - }, - { - displayName: 'Attachements are not supported due to Twitter V2 API limitations', - name: 'noticeAttachments', - type: 'notice', - displayOptions: { show: { '/additionalFields.attachments': [''] } }, - default: '', - }, - { - displayName: 'Tweet', - name: 'tweetDeleteId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to delete', - displayOptions: { show: { resource: ['tweet'], operation: ['delete'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Tweet', - name: 'tweetId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to like', - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Search Term', - name: 'searchText', - type: 'string', - required: true, - default: '', - placeholder: 'e.g. automation', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - description: - 'A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity. Check the searching examples here.', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - default: false, - description: 'Whether to return all results or only up to a given limit', - displayOptions: { show: { resource: ['tweet'], operation: ['search'] } }, - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - default: 50, - description: 'Max number of results to return', - typeOptions: { minValue: 1 }, - displayOptions: { show: { resource: ['tweet'], operation: ['search'], returnAll: [false] } }, - }, - { - displayName: 'Options', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Sort Order', - name: 'sortOrder', - type: 'options', - options: [ - { name: 'Recent', value: 'recency' }, - { name: 'Relevant', value: 'relevancy' }, - ], - description: 'The order in which to return results', - default: 'recency', - }, - { - displayName: 'After', - name: 'startTime', - type: 'dateTime', - default: '', - description: - "Tweets before this date will not be returned. This date must be within the last 7 days if you don't have Academic Research access.", - }, - { - displayName: 'Before', - name: 'endTime', - type: 'dateTime', - default: '', - description: - "Tweets after this date will not be returned. This date must be within the last 7 days if you don't have Academic Research access.", - }, - { - displayName: 'Tweet Fields', - name: 'tweetFieldsObject', - type: 'multiOptions', - options: [ - { name: 'Attachments', value: 'attachments' }, - { name: 'Author ID', value: 'author_id' }, - { name: 'Context Annotations', value: 'context_annotations' }, - { name: 'Conversation ID', value: 'conversation_id' }, - { name: 'Created At', value: 'created_at' }, - { name: 'Edit Controls', value: 'edit_controls' }, - { name: 'Entities', value: 'entities' }, - { name: 'Geo', value: 'geo' }, - { name: 'ID', value: 'id' }, - { name: 'In Reply To User ID', value: 'in_reply_to_user_id' }, - { name: 'Lang', value: 'lang' }, - { name: 'Non Public Metrics', value: 'non_public_metrics' }, - { name: 'Public Metrics', value: 'public_metrics' }, - { name: 'Organic Metrics', value: 'organic_metrics' }, - { name: 'Promoted Metrics', value: 'promoted_metrics' }, - { name: 'Possibly Sensitive', value: 'possibly_sensitive' }, - { name: 'Referenced Tweets', value: 'referenced_tweets' }, - { name: 'Reply Settings', value: 'reply_settings' }, - { name: 'Source', value: 'source' }, - { name: 'Text', value: 'text' }, - { name: 'Withheld', value: 'withheld' }, - ], - default: [], - description: - 'The fields to add to each returned tweet object. Default fields are: ID, text, edit_history_tweet_ids.', - }, - ], - }, - { - displayName: 'Tweet', - name: 'tweetId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to retweet', - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['user'] } }, - options: [ - { - name: 'Get', - value: 'searchUser', - description: 'Retrieve a user by username', - action: 'Get User', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'searchUser', - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to search', - displayOptions: { - show: { operation: ['searchUser'], resource: ['user'] }, - hide: { me: [true] }, - }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Me', - name: 'me', - type: 'boolean', - displayOptions: { show: { operation: ['searchUser'], resource: ['user'] } }, - default: false, - description: 'Whether you want to search the authenticated user', - }, - ], - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', - codex: { - categories: ['Marketing & Content'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twitter/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/twitter' }], - }, - alias: ['Tweet', 'Twitter', 'X', 'X API'], - }, -}; - -export const twitterV1: INodeTypeDescription = { - displayName: 'X (Formerly Twitter)', - name: 'n8n-nodes-base.twitter', - group: ['output'], - subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', - description: 'Consume Twitter API', - defaultVersion: 2, - version: 1, - defaults: { name: 'Twitter' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'twitterOAuth1Api', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { name: 'Direct Message', value: 'directMessage' }, - { name: 'Tweet', value: 'tweet' }, - ], - default: 'tweet', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['directMessage'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a direct message', - action: 'Create a direct message', - }, - ], - default: 'create', - }, - { - displayName: 'User ID', - name: 'userId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: 'The ID of the user who should receive the direct message', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: - 'The text of your Direct Message. URL encode as necessary. Max length of 10,000 characters.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - options: [ - { - displayName: 'Attachment', - name: 'attachment', - type: 'string', - default: 'data', - description: - 'Name of the binary property which contain data that should be added to the direct message as attachment', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['tweet'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create or reply a tweet', - action: 'Create a tweet', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a tweet', - action: 'Delete a tweet', - }, - { name: 'Like', value: 'like', description: 'Like a tweet', action: 'Like a tweet' }, - { - name: 'Retweet', - value: 'retweet', - description: 'Retweet a tweet', - action: 'Retweet a tweet', - }, - { - name: 'Search', - value: 'search', - description: 'Search tweets', - action: 'Search for tweets', - }, - ], - default: 'create', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - description: - 'The text of the status update. URL encode as necessary. t.co link wrapping will affect character counts.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Attachments', - name: 'attachments', - type: 'string', - default: 'data', - description: - 'Name of the binary properties which contain data which should be added to tweet as attachment. Multiple ones can be comma-separated.', - }, - { - displayName: 'Display Coordinates', - name: 'displayCoordinates', - type: 'boolean', - default: false, - description: - 'Whether or not to put a pin on the exact coordinates a Tweet has been sent from', - }, - { - displayName: 'In Reply to Tweet', - name: 'inReplyToStatusId', - type: 'string', - default: '', - description: 'The ID of an existing status that the update is in reply to', - }, - { - displayName: 'Location', - name: 'locationFieldsUi', - type: 'fixedCollection', - placeholder: 'Add Location', - default: {}, - description: 'Subscriber location information.n', - options: [ - { - name: 'locationFieldsValues', - displayName: 'Location', - values: [ - { - displayName: 'Latitude', - name: 'latitude', - type: 'string', - required: true, - description: 'The location latitude', - default: '', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'string', - required: true, - description: 'The location longitude', - default: '', - }, - ], - }, - ], - }, - { - displayName: 'Possibly Sensitive', - name: 'possiblySensitive', - type: 'boolean', - default: false, - description: - 'Whether you are uploading Tweet media that might be considered sensitive content such as nudity, or medical procedures', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['delete'], resource: ['tweet'] } }, - description: 'The ID of the tweet to delete', - }, - { - displayName: 'Search Text', - name: 'searchText', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - description: - 'A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity. Check the searching examples here.', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - displayOptions: { show: { operation: ['search'], resource: ['tweet'], returnAll: [false] } }, - typeOptions: { minValue: 1 }, - default: 50, - description: 'Max number of results to return', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Include Entities', - name: 'includeEntities', - type: 'boolean', - default: false, - description: 'Whether the entities node will be included', - }, - { - displayName: 'Language Name or ID', - name: 'lang', - type: 'options', - typeOptions: { loadOptionsMethod: 'getLanguages' }, - default: '', - description: - 'Restricts tweets to the given language, given by an ISO 639-1 code. Language detection is best-effort. Choose from the list, or specify an ID using an expression.', - }, - { - displayName: 'Location', - name: 'locationFieldsUi', - type: 'fixedCollection', - placeholder: 'Add Location', - default: {}, - description: 'Subscriber location information.n', - options: [ - { - name: 'locationFieldsValues', - displayName: 'Location', - values: [ - { - displayName: 'Latitude', - name: 'latitude', - type: 'string', - required: true, - description: 'The location latitude', - default: '', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'string', - required: true, - description: 'The location longitude', - default: '', - }, - { - displayName: 'Radius', - name: 'radius', - type: 'options', - options: [ - { name: 'Milles', value: 'mi' }, - { name: 'Kilometers', value: 'km' }, - ], - required: true, - description: - 'Returns tweets by users located within a given radius of the given latitude/longitude', - default: '', - }, - { - displayName: 'Distance', - name: 'distance', - type: 'number', - typeOptions: { minValue: 0 }, - required: true, - default: '', - }, - ], - }, - ], - }, - { - displayName: 'Result Type', - name: 'resultType', - type: 'options', - options: [ - { - name: 'Mixed', - value: 'mixed', - description: 'Include both popular and real time results in the response', - }, - { - name: 'Recent', - value: 'recent', - description: 'Return only the most recent results in the response', - }, - { - name: 'Popular', - value: 'popular', - description: 'Return only the most popular results in the response', - }, - ], - default: 'mixed', - description: 'Specifies what type of search results you would prefer to receive', - }, - { - displayName: 'Tweet Mode', - name: 'tweetMode', - type: 'options', - options: [ - { name: 'Compatibility', value: 'compat' }, - { name: 'Extended', value: 'extended' }, - ], - default: 'compat', - description: - 'When the extended mode is selected, the response contains the entire untruncated text of the Tweet', - }, - { - displayName: 'Until', - name: 'until', - type: 'dateTime', - default: '', - description: 'Returns tweets created before the given date', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - description: 'The ID of the tweet', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Include Entities', - name: 'includeEntities', - type: 'boolean', - default: false, - description: 'Whether the entities will be omitted', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - description: 'The ID of the tweet', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Trim User', - name: 'trimUser', - type: 'boolean', - default: false, - description: - 'Whether each tweet returned in a timeline will include a user object including only the status authors numerical ID', - }, - ], - }, - ], - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', -}; diff --git a/packages/editor-ui/src/composables/__tests__/useActiveNode.test.ts b/packages/editor-ui/src/composables/__tests__/useActiveNode.test.ts index 883bab87d2d71a..b43b6e782a6602 100644 --- a/packages/editor-ui/src/composables/__tests__/useActiveNode.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useActiveNode.test.ts @@ -1,13 +1,12 @@ +import { computed } from 'vue'; import { describe, it, expect, vi } from 'vitest'; +import { mock } from 'vitest-mock-extended'; + import { useActiveNode } from '@/composables/useActiveNode'; import { useNodeType } from '@/composables/useNodeType'; -import { createTestNode } from '@/__tests__/mocks'; -import { MANUAL_TRIGGER_NODE_TYPE } from '@/constants'; -import { computed } from 'vue'; -import { defaultMockNodeTypes } from '@/__tests__/defaults'; -const node = computed(() => createTestNode({ name: 'Node', type: MANUAL_TRIGGER_NODE_TYPE })); -const nodeType = computed(() => defaultMockNodeTypes[MANUAL_TRIGGER_NODE_TYPE]); +const node = computed(() => mock()); +const nodeType = computed(() => mock()); vi.mock('@/stores/ndv.store', () => ({ useNDVStore: vi.fn(() => ({ diff --git a/packages/editor-ui/src/composables/useCanvasMapping.spec.ts b/packages/editor-ui/src/composables/__tests__/useCanvasMapping.spec.ts similarity index 63% rename from packages/editor-ui/src/composables/useCanvasMapping.spec.ts rename to packages/editor-ui/src/composables/__tests__/useCanvasMapping.spec.ts index 18ab57a19ed775..e1bbf0755b4109 100644 --- a/packages/editor-ui/src/composables/useCanvasMapping.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasMapping.spec.ts @@ -1,11 +1,13 @@ import type { Ref } from 'vue'; import { ref } from 'vue'; -import { useCanvasMapping } from '@/composables/useCanvasMapping'; -import { createTestNode, createTestWorkflow, createTestWorkflowObject } from '@/__tests__/mocks'; -import type { IConnections, Workflow } from 'n8n-workflow'; -import { createPinia, setActivePinia } from 'pinia'; -import { MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants'; import { NodeConnectionType } from 'n8n-workflow'; +import type { Workflow } from 'n8n-workflow'; +import { createPinia, setActivePinia } from 'pinia'; +import { mock } from 'vitest-mock-extended'; + +import { useCanvasMapping } from '@/composables/useCanvasMapping'; +import type { IWorkflowDb } from '@/Interface'; +import { createTestWorkflowObject, mockNodes } from '@/__tests__/mocks'; vi.mock('@/stores/nodeTypes.store', () => ({ useNodeTypesStore: vi.fn(() => ({ @@ -30,11 +32,8 @@ afterEach(() => { describe('useCanvasMapping', () => { it('should initialize with default props', () => { - const workflow = createTestWorkflow({ - id: '1', - name: 'Test Workflow', + const workflow = mock({ nodes: [], - connections: {}, }); const workflowObject = createTestWorkflowObject(workflow); @@ -49,14 +48,9 @@ describe('useCanvasMapping', () => { describe('elements', () => { it('should map nodes to canvas elements', () => { - const node = createTestNode({ - name: 'Node', - type: MANUAL_TRIGGER_NODE_TYPE, - }); - const workflow = createTestWorkflow({ - name: 'Test Workflow', - nodes: [node], - connections: {}, + const manualTriggerNode = mockNodes[0]; + const workflow = mock({ + nodes: [manualTriggerNode], }); const workflowObject = createTestWorkflowObject(workflow); @@ -67,14 +61,14 @@ describe('useCanvasMapping', () => { expect(elements.value).toEqual([ { - id: node.id, - label: node.name, + id: manualTriggerNode.id, + label: manualTriggerNode.name, type: 'canvas-node', - position: { x: 0, y: 0 }, + position: expect.anything(), data: { - id: node.id, - type: node.type, - typeVersion: 1, + id: manualTriggerNode.id, + type: manualTriggerNode.type, + typeVersion: expect.anything(), inputs: [], outputs: [], renderType: 'default', @@ -86,24 +80,16 @@ describe('useCanvasMapping', () => { describe('connections', () => { it('should map connections to canvas connections', () => { - const nodeA = createTestNode({ - name: 'Node A', - type: MANUAL_TRIGGER_NODE_TYPE, - }); - const nodeB = createTestNode({ - name: 'Node B', - type: SET_NODE_TYPE, - }); - const workflow = createTestWorkflow({ - name: 'Test Workflow', - nodes: [nodeA, nodeB], + const [manualTriggerNode, setNode] = mockNodes.slice(0, 2); + const workflow = mock({ + nodes: [manualTriggerNode, setNode], connections: { - [nodeA.name]: { + [manualTriggerNode.name]: { [NodeConnectionType.Main]: [ - [{ node: nodeB.name, type: NodeConnectionType.Main, index: 0 }], + [{ node: setNode.name, type: NodeConnectionType.Main, index: 0 }], ], }, - } as IConnections, + }, }); const workflowObject = createTestWorkflowObject(workflow); @@ -115,7 +101,7 @@ describe('useCanvasMapping', () => { expect(connections.value).toEqual([ { data: { - fromNodeName: nodeA.name, + fromNodeName: manualTriggerNode.name, source: { index: 0, type: NodeConnectionType.Main, @@ -125,11 +111,11 @@ describe('useCanvasMapping', () => { type: NodeConnectionType.Main, }, }, - id: `[${nodeA.id}/${NodeConnectionType.Main}/0][${nodeB.id}/${NodeConnectionType.Main}/0]`, + id: `[${manualTriggerNode.id}/${NodeConnectionType.Main}/0][${setNode.id}/${NodeConnectionType.Main}/0]`, label: '', - source: nodeA.id, + source: manualTriggerNode.id, sourceHandle: `outputs/${NodeConnectionType.Main}/0`, - target: nodeB.id, + target: setNode.id, targetHandle: `inputs/${NodeConnectionType.Main}/0`, type: 'canvas-edge', }, @@ -137,24 +123,16 @@ describe('useCanvasMapping', () => { }); it('should map multiple input types to canvas connections', () => { - const nodeA = createTestNode({ - name: 'Node A', - type: MANUAL_TRIGGER_NODE_TYPE, - }); - const nodeB = createTestNode({ - name: 'Node B', - type: SET_NODE_TYPE, - }); - const workflow = createTestWorkflow({ - name: 'Test Workflow', - nodes: [nodeA, nodeB], + const [manualTriggerNode, setNode] = mockNodes.slice(0, 2); + const workflow = mock({ + nodes: [manualTriggerNode, setNode], connections: { - 'Node A': { + [manualTriggerNode.name]: { [NodeConnectionType.AiTool]: [ - [{ node: nodeB.name, type: NodeConnectionType.AiTool, index: 0 }], + [{ node: setNode.name, type: NodeConnectionType.AiTool, index: 0 }], ], [NodeConnectionType.AiDocument]: [ - [{ node: nodeB.name, type: NodeConnectionType.AiDocument, index: 1 }], + [{ node: setNode.name, type: NodeConnectionType.AiDocument, index: 1 }], ], }, }, @@ -169,7 +147,7 @@ describe('useCanvasMapping', () => { expect(connections.value).toEqual([ { data: { - fromNodeName: nodeA.name, + fromNodeName: manualTriggerNode.name, source: { index: 0, type: NodeConnectionType.AiTool, @@ -179,17 +157,17 @@ describe('useCanvasMapping', () => { type: NodeConnectionType.AiTool, }, }, - id: `[${nodeA.id}/${NodeConnectionType.AiTool}/0][${nodeB.id}/${NodeConnectionType.AiTool}/0]`, + id: `[${manualTriggerNode.id}/${NodeConnectionType.AiTool}/0][${setNode.id}/${NodeConnectionType.AiTool}/0]`, label: '', - source: nodeA.id, + source: manualTriggerNode.id, sourceHandle: `outputs/${NodeConnectionType.AiTool}/0`, - target: nodeB.id, + target: setNode.id, targetHandle: `inputs/${NodeConnectionType.AiTool}/0`, type: 'canvas-edge', }, { data: { - fromNodeName: nodeA.name, + fromNodeName: manualTriggerNode.name, source: { index: 0, type: NodeConnectionType.AiDocument, @@ -199,11 +177,11 @@ describe('useCanvasMapping', () => { type: NodeConnectionType.AiDocument, }, }, - id: `[${nodeA.id}/${NodeConnectionType.AiDocument}/0][${nodeB.id}/${NodeConnectionType.AiDocument}/1]`, + id: `[${manualTriggerNode.id}/${NodeConnectionType.AiDocument}/0][${setNode.id}/${NodeConnectionType.AiDocument}/1]`, label: '', - source: nodeA.id, + source: manualTriggerNode.id, sourceHandle: `outputs/${NodeConnectionType.AiDocument}/0`, - target: nodeB.id, + target: setNode.id, targetHandle: `inputs/${NodeConnectionType.AiDocument}/1`, type: 'canvas-edge', }, diff --git a/packages/editor-ui/src/composables/useCanvasOperations.spec.ts b/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts similarity index 94% rename from packages/editor-ui/src/composables/useCanvasOperations.spec.ts rename to packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts index ca23d162e052f6..e95b9fa0076e12 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts @@ -1,3 +1,8 @@ +import { createPinia, setActivePinia } from 'pinia'; +import type { Connection } from '@vue-flow/core'; +import type { IConnection } from 'n8n-workflow'; +import { NodeConnectionType } from 'n8n-workflow'; + import { useCanvasOperations } from '@/composables/useCanvasOperations'; import type { CanvasElement } from '@/types'; import type { INodeUi } from '@/Interface'; @@ -5,12 +10,8 @@ import { RemoveNodeCommand } from '@/models/history'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useUIStore } from '@/stores/ui.store'; import { useHistoryStore } from '@/stores/history.store'; -import { createPinia, setActivePinia } from 'pinia'; -import { createTestNode, createTestWorkflowObject } from '@/__tests__/mocks'; -import type { Connection } from '@vue-flow/core'; -import type { IConnection } from 'n8n-workflow'; -import { NodeConnectionType } from 'n8n-workflow'; import { useNDVStore } from '@/stores/ndv.store'; +import { createTestNode, createTestWorkflowObject } from '@/__tests__/mocks'; describe('useCanvasOperations', () => { let workflowsStore: ReturnType; @@ -255,23 +256,6 @@ describe('useCanvasOperations', () => { expect(uiStore.stateIsDirty).toBe(false); }); - // @TODO Implement once the isConnectionAllowed method is implemented - it.skip('should not create a connection if connection is not allowed', () => { - const addConnectionSpy = vi - .spyOn(workflowsStore, 'addConnection') - .mockImplementation(() => {}); - const connection: Connection = { source: 'sourceNode', target: 'targetNode' }; - - vi.spyOn(workflowsStore, 'getNodeById') - .mockReturnValueOnce(createTestNode()) - .mockReturnValueOnce(createTestNode()); - - canvasOperations.createConnection(connection); - - expect(addConnectionSpy).not.toHaveBeenCalled(); - expect(uiStore.stateIsDirty).toBe(false); - }); - it('should create a connection if source and target nodes exist and connection is allowed', () => { const addConnectionSpy = vi .spyOn(workflowsStore, 'addConnection') diff --git a/packages/editor-ui/src/composables/useNodeBase.spec.ts b/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts similarity index 56% rename from packages/editor-ui/src/composables/useNodeBase.spec.ts rename to packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts index 377c0d64286d6f..697213bd3e6e2f 100644 --- a/packages/editor-ui/src/composables/useNodeBase.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts @@ -1,43 +1,39 @@ -import { useNodeBase } from '@/composables/useNodeBase'; -import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'; -import { createTestNode, createTestWorkflowObject } from '@/__tests__/mocks'; import { createPinia, setActivePinia } from 'pinia'; +import { mock, mockClear } from 'vitest-mock-extended'; +import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'; +import type { INode, INodeTypeDescription, Workflow } from 'n8n-workflow'; + +import { useNodeBase } from '@/composables/useNodeBase'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { findNodeTypeDescriptionByName } from '@/__tests__/defaults'; -import { SET_NODE_TYPE } from '@/constants'; import { useUIStore } from '@/stores/ui.store'; -import type { INodeTypeDescription, Workflow } from 'n8n-workflow'; -import type { INodeUi } from '@/Interface'; -import type { Mock } from '@vitest/spy'; describe('useNodeBase', () => { let pinia: ReturnType; let workflowsStore: ReturnType; let uiStore: ReturnType; - let instance: Record; - let workflowObject: Workflow; let emit: (event: string, ...args: unknown[]) => void; - let node: INodeUi; - let nodeTypeDescription: INodeTypeDescription; let nodeBase: ReturnType; + const jsPlumbInstance = mock(); + const nodeTypeDescription = mock({ + inputs: ['main'], + outputs: ['main'], + }); + const workflowObject = mock(); + const node = mock(); + beforeEach(() => { + mockClear(jsPlumbInstance); + pinia = createPinia(); setActivePinia(pinia); workflowsStore = useWorkflowsStore(); uiStore = useUIStore(); - - instance = { - addEndpoint: vi.fn().mockReturnValue({}), - }; - workflowObject = createTestWorkflowObject({ nodes: [], connections: {} }); - node = createTestNode(); - nodeTypeDescription = findNodeTypeDescriptionByName(SET_NODE_TYPE); emit = vi.fn(); nodeBase = useNodeBase({ - instance: instance as unknown as BrowserJsPlumbInstance, + instance: jsPlumbInstance, name: node.name, workflowObject, isReadOnly: false, @@ -54,83 +50,89 @@ describe('useNodeBase', () => { describe('addInputEndpoints', () => { it('should add input endpoints correctly', () => { - const { addInputEndpoints } = nodeBase; - + jsPlumbInstance.addEndpoint.mockReturnValue(mock()); vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(node); - addInputEndpoints(node, nodeTypeDescription); - - const addEndpointCall = instance.addEndpoint.mock.calls[0][1]; + nodeBase.addInputEndpoints(node, nodeTypeDescription); expect(workflowsStore.getNodeByName).toHaveBeenCalledWith(node.name); - expect(addEndpointCall.anchor).toEqual([0.01, 0.5, -1, 0]); - expect(addEndpointCall.cssClass).toEqual('rect-input-endpoint'); - expect(addEndpointCall.dragAllowedWhenFull).toEqual(true); - expect(addEndpointCall.enabled).toEqual(true); - expect(addEndpointCall.endpoint).toEqual('Rectangle'); - expect(addEndpointCall.hoverClass).toEqual('rect-input-endpoint-hover'); - expect(addEndpointCall.hoverPaintStyle).toMatchObject({ - fill: 'var(--color-primary)', - height: 20, - lineWidth: 0, - stroke: 'var(--color-primary)', - width: 8, - }); - expect(addEndpointCall.maxConnections).toEqual(-1); - expect(addEndpointCall.paintStyle).toMatchObject({ - fill: 'var(--node-type-main-color)', - height: 20, - lineWidth: 0, - stroke: 'var(--node-type-main-color)', - width: 8, - }); - expect(addEndpointCall.parameters).toMatchObject({ - connection: 'target', - index: 0, - type: 'main', + expect(jsPlumbInstance.addEndpoint).toHaveBeenCalledWith(undefined, { + anchor: [0.01, 0.5, -1, 0], + maxConnections: -1, + endpoint: 'Rectangle', + paintStyle: { + width: 8, + height: 20, + fill: 'var(--node-type-main-color)', + stroke: 'var(--node-type-main-color)', + lineWidth: 0, + }, + hoverPaintStyle: { + width: 8, + height: 20, + fill: 'var(--color-primary)', + stroke: 'var(--color-primary)', + lineWidth: 0, + }, + source: false, + target: false, + parameters: { + connection: 'target', + nodeId: node.id, + type: 'main', + index: 0, + }, + enabled: true, + cssClass: 'rect-input-endpoint', + dragAllowedWhenFull: true, + hoverClass: 'rect-input-endpoint-hover', + uuid: `${node.id}-input0`, }); - expect(addEndpointCall.scope).toBeUndefined(); - expect(addEndpointCall.source).toBeFalsy(); - expect(addEndpointCall.target).toBeFalsy(); }); }); describe('addOutputEndpoints', () => { it('should add output endpoints correctly', () => { - const { addOutputEndpoints } = nodeBase; const getNodeByNameSpy = vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(node); - addOutputEndpoints(node, nodeTypeDescription); - - const addEndpointCall = instance.addEndpoint.mock.calls[0][1]; + nodeBase.addOutputEndpoints(node, nodeTypeDescription); expect(getNodeByNameSpy).toHaveBeenCalledWith(node.name); - expect(addEndpointCall.anchor).toEqual([0.99, 0.5, 1, 0]); - expect(addEndpointCall.cssClass).toEqual('dot-output-endpoint'); - expect(addEndpointCall.dragAllowedWhenFull).toEqual(false); - expect(addEndpointCall.enabled).toEqual(true); - expect(addEndpointCall.endpoint).toEqual({ - options: { - radius: 9, + expect(jsPlumbInstance.addEndpoint).toHaveBeenCalledWith(undefined, { + anchor: [0.99, 0.5, 1, 0], + connectionsDirected: true, + cssClass: 'dot-output-endpoint', + dragAllowedWhenFull: false, + enabled: true, + endpoint: { + options: { + radius: 9, + }, + type: 'Dot', }, - type: 'Dot', - }); - expect(addEndpointCall.hoverClass).toEqual('dot-output-endpoint-hover'); - expect(addEndpointCall.hoverPaintStyle).toMatchObject({ - fill: 'var(--color-primary)', - }); - expect(addEndpointCall.maxConnections).toEqual(-1); - expect(addEndpointCall.paintStyle).toMatchObject({ - fill: 'var(--node-type-main-color)', - }); - expect(addEndpointCall.parameters).toMatchObject({ - connection: 'source', - index: 0, - type: 'main', + hoverClass: 'dot-output-endpoint-hover', + hoverPaintStyle: { + fill: 'var(--color-primary)', + outlineStroke: 'none', + strokeWidth: 9, + }, + maxConnections: -1, + paintStyle: { + fill: 'var(--node-type-main-color)', + outlineStroke: 'none', + strokeWidth: 9, + }, + parameters: { + connection: 'source', + index: 0, + nodeId: node.id, + type: 'main', + }, + scope: undefined, + source: true, + target: false, + uuid: `${node.id}-output0`, }); - expect(addEndpointCall.scope).toBeUndefined(); - expect(addEndpointCall.source).toBeTruthy(); - expect(addEndpointCall.target).toBeFalsy(); }); }); diff --git a/packages/editor-ui/src/composables/useNodeConnections.spec.ts b/packages/editor-ui/src/composables/__tests__/useNodeConnections.spec.ts similarity index 100% rename from packages/editor-ui/src/composables/useNodeConnections.spec.ts rename to packages/editor-ui/src/composables/__tests__/useNodeConnections.spec.ts diff --git a/packages/editor-ui/src/composables/usePushConnection.spec.ts b/packages/editor-ui/src/composables/__tests__/usePushConnection.spec.ts similarity index 100% rename from packages/editor-ui/src/composables/usePushConnection.spec.ts rename to packages/editor-ui/src/composables/__tests__/usePushConnection.spec.ts diff --git a/packages/editor-ui/src/composables/useRunWorkflow.test.ts b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts similarity index 99% rename from packages/editor-ui/src/composables/useRunWorkflow.test.ts rename to packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts index 57a1a98fa217f0..392ef09825178b 100644 --- a/packages/editor-ui/src/composables/useRunWorkflow.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts @@ -1,14 +1,15 @@ +import { setActivePinia } from 'pinia'; +import { createTestingPinia } from '@pinia/testing'; +import { useRouter } from 'vue-router'; +import type router from 'vue-router'; +import { ExpressionError, type IPinData, type IRunData, type Workflow } from 'n8n-workflow'; + import { useRootStore } from '@/stores/root.store'; import { useRunWorkflow } from '@/composables/useRunWorkflow'; -import { createTestingPinia } from '@pinia/testing'; -import { setActivePinia } from 'pinia'; import type { IStartRunData, IWorkflowData } from '@/Interface'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; -import { useRouter } from 'vue-router'; -import { ExpressionError, type IPinData, type IRunData, type Workflow } from 'n8n-workflow'; -import type * as router from 'vue-router'; vi.mock('@/stores/workflows.store', () => ({ useWorkflowsStore: vi.fn().mockReturnValue({ diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.test.ts b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts similarity index 100% rename from packages/editor-ui/src/composables/useWorkflowHelpers.test.ts rename to packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts diff --git a/packages/editor-ui/src/composables/useNodeBase.ts b/packages/editor-ui/src/composables/useNodeBase.ts index f77071c602124e..7cf0474881ebcb 100644 --- a/packages/editor-ui/src/composables/useNodeBase.ts +++ b/packages/editor-ui/src/composables/useNodeBase.ts @@ -231,7 +231,7 @@ export function useNodeBase({ const endpoint = instance?.addEndpoint( refs.value[data.value?.name ?? ''] as Element, newEndpointData, - ) as Endpoint; + ); addEndpointTestingData(endpoint, 'input', typeIndex); if (inputConfiguration.displayName ?? nodeTypeData.inputNames?.[i]) { // Apply input names if they got set diff --git a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts index e63e805e85ad2a..5fa1043493b81f 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts @@ -12,7 +12,7 @@ import { natives, } from '@/plugins/codemirror/completions/datatype.completions'; -import { mockNodes, mockProxy } from './mock'; +import { mockProxy } from './mock'; import type { CompletionSource, CompletionResult } from '@codemirror/autocomplete'; import { CompletionContext } from '@codemirror/autocomplete'; import { EditorState } from '@codemirror/state'; @@ -30,6 +30,7 @@ import { STRING_RECOMMENDED_OPTIONS, } from '../constants'; import { set, uniqBy } from 'lodash-es'; +import { mockNodes } from '@/__tests__/mocks'; let externalSecretsStore: ReturnType; let uiStore: ReturnType; 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 20268b67ebffdb..8d5d93da4e62d4 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/mock.ts @@ -1,126 +1,7 @@ -import { v4 as uuidv4 } from 'uuid'; -import type { - INode, - IConnections, - IRunExecutionData, - IExecuteData, - INodeTypeData, -} from 'n8n-workflow'; -import { NodeConnectionType } from 'n8n-workflow'; +import type { INode, IRunExecutionData, IExecuteData } from 'n8n-workflow'; import { WorkflowDataProxy } from 'n8n-workflow'; -import { createTestWorkflowObject } from '@/__tests__/mocks'; - -const nodeTypes: INodeTypeData = { - 'n8n-nodes-base.set': { - sourcePath: '', - type: { - description: { - displayName: 'Set', - name: 'n8n-nodes-base.set', - group: ['input'], - version: 1, - description: 'Sets a value', - defaults: { - name: 'Set', - color: '#0000FF', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Value1', - name: 'value1', - type: 'string', - default: 'default-value1', - }, - { - displayName: 'Value2', - name: 'value2', - type: 'string', - default: 'default-value2', - }, - ], - }, - }, - }, -}; - -const nodes: INode[] = [ - { - name: 'Start', - type: 'n8n-nodes-base.set', - parameters: {}, - typeVersion: 1, - id: 'uuid-1', - position: [100, 200], - }, - { - name: 'Function', - type: 'n8n-nodes-base.set', - parameters: { - functionCode: - '// Code here will run only once, no matter how many input items there are.\n// More info and help: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.function/\nconst { DateTime, Duration, Interval } = require("luxon");\n\nconst data = [\n {\n "length": 105\n },\n {\n "length": 160\n },\n {\n "length": 121\n },\n {\n "length": 275\n },\n {\n "length": 950\n },\n];\n\nreturn data.map(fact => ({json: fact}));', - }, - typeVersion: 1, - id: 'uuid-2', - position: [280, 200], - }, - { - name: 'Rename', - type: 'n8n-nodes-base.set', - parameters: { - value1: 'data', - value2: 'initialName', - }, - typeVersion: 1, - id: 'uuid-3', - position: [460, 200], - }, - { - name: 'End', - type: 'n8n-nodes-base.set', - parameters: {}, - typeVersion: 1, - id: 'uuid-4', - position: [640, 200], - }, -]; - -const connections: IConnections = { - Start: { - main: [ - [ - { - node: 'Function', - type: NodeConnectionType.Main, - index: 0, - }, - ], - ], - }, - Function: { - main: [ - [ - { - node: 'Rename', - type: NodeConnectionType.Main, - index: 0, - }, - ], - ], - }, - Rename: { - main: [ - [ - { - node: 'End', - type: NodeConnectionType.Main, - index: 0, - }, - ], - ], - }, -}; +import { createTestWorkflowObject, mockNodes } from '@/__tests__/mocks'; +import { mock } from 'vitest-mock-extended'; const runExecutionData: IRunExecutionData = { resultData: { @@ -265,20 +146,19 @@ const runExecutionData: IRunExecutionData = { const workflow = createTestWorkflowObject({ id: '123', name: 'test workflow', - nodes, - connections, + nodes: mockNodes, + connections: mock(), active: false, - nodeTypes, }); -const lastNodeName = 'End'; +const lastNodeName = mockNodes[mockNodes.length - 1].name; const lastNodeConnectionInputData = runExecutionData.resultData.runData[lastNodeName][0].data!.main[0]; const executeData: IExecuteData = { data: runExecutionData.resultData.runData[lastNodeName][0].data!, - node: nodes.find((node) => node.name === lastNodeName) as INode, + node: mockNodes.find((node) => node.name === lastNodeName) as INode, source: { main: runExecutionData.resultData.runData[lastNodeName][0].source, }, @@ -298,20 +178,3 @@ const dataProxy = new WorkflowDataProxy( ); export const mockProxy = dataProxy.getDataProxy(); - -export const mockNodes = [ - { - id: uuidv4(), - name: 'Manual', - position: [0, 0], - type: 'n8n-nodes-base.manualTrigger', - typeVersion: 1, - }, - { - id: uuidv4(), - name: 'Set', - position: [0, 0], - type: 'n8n-nodes-base.set', - typeVersion: 1, - }, -]; diff --git a/packages/editor-ui/src/utils/canvasUtilsV2.spec.ts b/packages/editor-ui/src/utils/__tests__/canvasUtilsV2.spec.ts similarity index 100% rename from packages/editor-ui/src/utils/canvasUtilsV2.spec.ts rename to packages/editor-ui/src/utils/__tests__/canvasUtilsV2.spec.ts diff --git a/packages/editor-ui/src/utils/projects.utils.test.ts b/packages/editor-ui/src/utils/__tests__/projects.utils.test.ts similarity index 100% rename from packages/editor-ui/src/utils/projects.utils.test.ts rename to packages/editor-ui/src/utils/__tests__/projects.utils.test.ts diff --git a/packages/editor-ui/src/utils/__tests__/rbacUtils.test.ts b/packages/editor-ui/src/utils/__tests__/rbacUtils.test.ts index c27a3922932559..0b156a46d0e978 100644 --- a/packages/editor-ui/src/utils/__tests__/rbacUtils.test.ts +++ b/packages/editor-ui/src/utils/__tests__/rbacUtils.test.ts @@ -1,20 +1,21 @@ +import { mock } from 'vitest-mock-extended'; +import type { RouteLocationNormalized } from 'vue-router'; import { inferProjectIdFromRoute, inferResourceTypeFromRoute, inferResourceIdFromRoute, -} from '../rbacUtils'; -import { createTestRouteLocation } from '@/__tests__/mocks'; +} from '@/utils/rbacUtils'; describe('rbacUtils', () => { describe('inferProjectIdFromRoute()', () => { it('should infer project ID from route correctly', () => { - const route = createTestRouteLocation({ path: '/dashboard/projects/123/settings' }); + const route = mock({ path: '/dashboard/projects/123/settings' }); const projectId = inferProjectIdFromRoute(route); expect(projectId).toBe('123'); }); it('should return undefined for project ID if not found', () => { - const route = createTestRouteLocation({ path: '/dashboard/settings' }); + const route = mock({ path: '/dashboard/settings' }); const projectId = inferProjectIdFromRoute(route); expect(projectId).toBeUndefined(); }); @@ -31,13 +32,13 @@ describe('rbacUtils', () => { ['/source-control', 'sourceControl'], ['/external-secrets', 'externalSecret'], ])('should infer resource type from %s correctly to %s', (path, type) => { - const route = createTestRouteLocation({ path }); + const route = mock({ path }); const resourceType = inferResourceTypeFromRoute(route); expect(resourceType).toBe(type); }); it('should return undefined for resource type if not found', () => { - const route = createTestRouteLocation({ path: '/dashboard/settings' }); + const route = mock({ path: '/dashboard/settings' }); const resourceType = inferResourceTypeFromRoute(route); expect(resourceType).toBeUndefined(); }); @@ -45,19 +46,21 @@ describe('rbacUtils', () => { describe('inferResourceIdFromRoute()', () => { it('should infer resource ID from params.id', () => { - const route = createTestRouteLocation({ params: { id: 'abc123' } }); + const route = mock({ params: { id: 'abc123' } }); const resourceId = inferResourceIdFromRoute(route); expect(resourceId).toBe('abc123'); }); it('should infer resource ID from params.name if id is not present', () => { - const route = createTestRouteLocation({ params: { name: 'my-resource' } }); + const route = mock(); + route.params = { name: 'my-resource' }; const resourceId = inferResourceIdFromRoute(route); expect(resourceId).toBe('my-resource'); }); it('should return undefined for resource ID if neither id nor name is present', () => { - const route = createTestRouteLocation({ params: {} }); + const route = mock(); + route.params = {}; const resourceId = inferResourceIdFromRoute(route); expect(resourceId).toBeUndefined(); }); diff --git a/packages/editor-ui/src/utils/templates/__tests__/templateActions.test.ts b/packages/editor-ui/src/utils/templates/__tests__/templateActions.test.ts index 6c48ca86837987..2b9622db2f0457 100644 --- a/packages/editor-ui/src/utils/templates/__tests__/templateActions.test.ts +++ b/packages/editor-ui/src/utils/templates/__tests__/templateActions.test.ts @@ -1,4 +1,11 @@ +import type { Router } from 'vue-router'; +import { setActivePinia } from 'pinia'; +import { createTestingPinia } from '@pinia/testing'; +import { vi } from 'vitest'; +import { mock } from 'vitest-mock-extended'; + import { VIEWS } from '@/constants'; +import type { ITemplatesWorkflowFull } from '@/Interface'; import { Telemetry } from '@/plugins/telemetry'; import type { NodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; @@ -7,23 +14,33 @@ import { usePostHog } from '@/stores/posthog.store'; import type { TemplatesStore } from '@/stores/templates.store'; import { useTemplatesStore } from '@/stores/templates.store'; import { useTemplateWorkflow } from '@/utils/templates/templateActions'; -import { - nodeTypeRespondToWebhookV1, - nodeTypeShopifyTriggerV1, - nodeTypeTelegramV1, - nodeTypeTwitterV1, - nodeTypeWebhookV1, - nodeTypeWebhookV1_1, - nodeTypesSet, -} from '@/utils/testData/nodeTypeTestData'; -import { - fullCreateApiEndpointTemplate, - fullShopifyTelegramTwitterTemplate, -} from '@/utils/testData/templateTestData'; -import { createTestingPinia } from '@pinia/testing'; -import { setActivePinia } from 'pinia'; -import { vi } from 'vitest'; -import type { Router } from 'vue-router'; +import { nodeTypeTelegram } from '@/utils/testData/nodeTypeTestData'; + +const testTemplate1 = mock({ + id: 1, + workflow: { + nodes: [], + }, + full: true, +}); + +export const testTemplate2 = mock({ + id: 2, + workflow: { + nodes: [ + { + name: 'Telegram', + type: 'n8n-nodes-base.telegram', + typeVersion: 1, + position: [0, 0], + credentials: { + telegramApi: 'telegram_habot', + }, + }, + ], + }, + full: true, +}); describe('templateActions', () => { describe('useTemplateWorkflow', () => { @@ -80,16 +97,12 @@ describe('templateActions', () => { }); describe('When feature flag is enabled and template has nodes requiring credentials', () => { - const templateId = fullShopifyTelegramTwitterTemplate.id.toString(); + const templateId = testTemplate2.id.toString(); beforeEach(async () => { posthogStore.isFeatureEnabled = vi.fn().mockReturnValue(true); - templatesStore.addWorkflows([fullShopifyTelegramTwitterTemplate]); - nodeTypesStore.setNodeTypes([ - nodeTypeTelegramV1, - nodeTypeTwitterV1, - nodeTypeShopifyTriggerV1, - ]); + templatesStore.addWorkflows([testTemplate2]); + nodeTypesStore.setNodeTypes([nodeTypeTelegram]); vi.spyOn(nodeTypesStore, 'loadNodeTypesIfNotLoaded').mockResolvedValue(); await useTemplateWorkflow({ @@ -113,17 +126,11 @@ describe('templateActions', () => { }); describe("When feature flag is enabled and template doesn't have nodes requiring credentials", () => { - const templateId = fullCreateApiEndpointTemplate.id.toString(); + const templateId = testTemplate1.id.toString(); beforeEach(async () => { posthogStore.isFeatureEnabled = vi.fn().mockReturnValue(true); - templatesStore.addWorkflows([fullCreateApiEndpointTemplate]); - nodeTypesStore.setNodeTypes([ - nodeTypeWebhookV1, - nodeTypeWebhookV1_1, - nodeTypeRespondToWebhookV1, - ...Object.values(nodeTypesSet), - ]); + templatesStore.addWorkflows([testTemplate1]); vi.spyOn(nodeTypesStore, 'loadNodeTypesIfNotLoaded').mockResolvedValue(); await useTemplateWorkflow({ 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 117c955ef20b05..0b9bcdb4937312 100644 --- a/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts +++ b/packages/editor-ui/src/utils/templates/__tests__/templateTransforms.test.ts @@ -1,8 +1,9 @@ +import { mock } from 'vitest-mock-extended'; +import type { IWorkflowTemplateNode } from '@/Interface'; import { keyFromCredentialTypeAndName, replaceAllTemplateNodeCredentials, } from '@/utils/templates/templateTransforms'; -import { newWorkflowTemplateNode } from '@/utils/testData/templateTestData'; describe('templateTransforms', () => { describe('replaceAllTemplateNodeCredentials', () => { @@ -10,7 +11,7 @@ describe('templateTransforms', () => { const nodeTypeProvider = { getNodeType: vitest.fn(), }; - const node = newWorkflowTemplateNode({ + const node = mock({ id: 'twitter', type: 'n8n-nodes-base.twitter', credentials: { @@ -40,7 +41,7 @@ describe('templateTransforms', () => { const nodeTypeProvider = { getNodeType: vitest.fn(), }; - const node = newWorkflowTemplateNode({ + const node = mock({ id: 'twitter', type: 'n8n-nodes-base.twitter', }); diff --git a/packages/editor-ui/src/utils/testData/credentialTypeTestData.ts b/packages/editor-ui/src/utils/testData/credentialTypeTestData.ts deleted file mode 100644 index f229fc9e020b48..00000000000000 --- a/packages/editor-ui/src/utils/testData/credentialTypeTestData.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Credential type test data - */ -import type { ICredentialType } from 'n8n-workflow'; - -export const newCredentialType = (name: string): ICredentialType => ({ - name, - displayName: name, - documentationUrl: name, - properties: [], -}); - -export const credentialTypeTelegram = { - name: 'telegramApi', - displayName: 'Telegram API', - documentationUrl: 'telegram', - properties: [ - { - displayName: 'Access Token', - name: 'accessToken', - type: 'string', - typeOptions: { - password: true, - }, - default: '', - description: - 'Chat with the bot father to obtain the access token', - }, - { - displayName: 'Base URL', - name: 'baseUrl', - type: 'string', - default: 'https://api.telegram.org', - description: 'Base URL for Telegram Bot API', - }, - ], - test: { - request: { - baseURL: '={{$credentials.baseUrl}}/bot{{$credentials.accessToken}}', - url: '/getMe', - }, - }, -} satisfies ICredentialType; diff --git a/packages/editor-ui/src/utils/testData/nodeTypeTestData.ts b/packages/editor-ui/src/utils/testData/nodeTypeTestData.ts index bd0b4ae440f884..f84f124aa0286b 100644 --- a/packages/editor-ui/src/utils/testData/nodeTypeTestData.ts +++ b/packages/editor-ui/src/utils/testData/nodeTypeTestData.ts @@ -1,4353 +1,65 @@ import type { INodeTypeDescription } from 'n8n-workflow'; +import { mock } from 'vitest-mock-extended'; -export const nodeTypeTwitterV1 = { +export const nodeTypeTwitter = mock({ displayName: 'X (Formerly Twitter)', name: 'n8n-nodes-base.twitter', - group: ['output'], - subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', - description: 'Consume Twitter API', - defaultVersion: 2, version: 1, - defaults: { name: 'Twitter' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'twitterOAuth1Api', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { name: 'Direct Message', value: 'directMessage' }, - { name: 'Tweet', value: 'tweet' }, - ], - default: 'tweet', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['directMessage'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a direct message', - action: 'Create a direct message', - }, - ], - default: 'create', - }, - { - displayName: 'User ID', - name: 'userId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: 'The ID of the user who should receive the direct message', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: - 'The text of your Direct Message. URL encode as necessary. Max length of 10,000 characters.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - options: [ - { - displayName: 'Attachment', - name: 'attachment', - type: 'string', - default: 'data', - description: - 'Name of the binary property which contain data that should be added to the direct message as attachment', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['tweet'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create or reply a tweet', - action: 'Create a tweet', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a tweet', - action: 'Delete a tweet', - }, - { name: 'Like', value: 'like', description: 'Like a tweet', action: 'Like a tweet' }, - { - name: 'Retweet', - value: 'retweet', - description: 'Retweet a tweet', - action: 'Retweet a tweet', - }, - { - name: 'Search', - value: 'search', - description: 'Search tweets', - action: 'Search for tweets', - }, - ], - default: 'create', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - description: - 'The text of the status update. URL encode as necessary. t.co link wrapping will affect character counts.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Attachments', - name: 'attachments', - type: 'string', - default: 'data', - description: - 'Name of the binary properties which contain data which should be added to tweet as attachment. Multiple ones can be comma-separated.', - }, - { - displayName: 'Display Coordinates', - name: 'displayCoordinates', - type: 'boolean', - default: false, - description: - 'Whether or not to put a pin on the exact coordinates a Tweet has been sent from', - }, - { - displayName: 'In Reply to Tweet', - name: 'inReplyToStatusId', - type: 'string', - default: '', - description: 'The ID of an existing status that the update is in reply to', - }, - { - displayName: 'Location', - name: 'locationFieldsUi', - type: 'fixedCollection', - placeholder: 'Add Location', - default: {}, - description: 'Subscriber location information.n', - options: [ - { - name: 'locationFieldsValues', - displayName: 'Location', - values: [ - { - displayName: 'Latitude', - name: 'latitude', - type: 'string', - required: true, - description: 'The location latitude', - default: '', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'string', - required: true, - description: 'The location longitude', - default: '', - }, - ], - }, - ], - }, - { - displayName: 'Possibly Sensitive', - name: 'possiblySensitive', - type: 'boolean', - default: false, - description: - 'Whether you are uploading Tweet media that might be considered sensitive content such as nudity, or medical procedures', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['delete'], resource: ['tweet'] } }, - description: 'The ID of the tweet to delete', - }, - { - displayName: 'Search Text', - name: 'searchText', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - description: - 'A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity. Check the searching examples here.', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - displayOptions: { - show: { operation: ['search'], resource: ['tweet'], returnAll: [false] }, - }, - typeOptions: { minValue: 1 }, - default: 50, - description: 'Max number of results to return', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Include Entities', - name: 'includeEntities', - type: 'boolean', - default: false, - description: 'Whether the entities node will be included', - }, - { - displayName: 'Language Name or ID', - name: 'lang', - type: 'options', - typeOptions: { loadOptionsMethod: 'getLanguages' }, - default: '', - description: - 'Restricts tweets to the given language, given by an ISO 639-1 code. Language detection is best-effort. Choose from the list, or specify an ID using an expression.', - }, - { - displayName: 'Location', - name: 'locationFieldsUi', - type: 'fixedCollection', - placeholder: 'Add Location', - default: {}, - description: 'Subscriber location information.n', - options: [ - { - name: 'locationFieldsValues', - displayName: 'Location', - values: [ - { - displayName: 'Latitude', - name: 'latitude', - type: 'string', - required: true, - description: 'The location latitude', - default: '', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'string', - required: true, - description: 'The location longitude', - default: '', - }, - { - displayName: 'Radius', - name: 'radius', - type: 'options', - options: [ - { name: 'Milles', value: 'mi' }, - { name: 'Kilometers', value: 'km' }, - ], - required: true, - description: - 'Returns tweets by users located within a given radius of the given latitude/longitude', - default: '', - }, - { - displayName: 'Distance', - name: 'distance', - type: 'number', - typeOptions: { minValue: 0 }, - required: true, - default: '', - }, - ], - }, - ], - }, - { - displayName: 'Result Type', - name: 'resultType', - type: 'options', - options: [ - { - name: 'Mixed', - value: 'mixed', - description: 'Include both popular and real time results in the response', - }, - { - name: 'Recent', - value: 'recent', - description: 'Return only the most recent results in the response', - }, - { - name: 'Popular', - value: 'popular', - description: 'Return only the most popular results in the response', - }, - ], - default: 'mixed', - description: 'Specifies what type of search results you would prefer to receive', - }, - { - displayName: 'Tweet Mode', - name: 'tweetMode', - type: 'options', - options: [ - { name: 'Compatibility', value: 'compat' }, - { name: 'Extended', value: 'extended' }, - ], - default: 'compat', - description: - 'When the extended mode is selected, the response contains the entire untruncated text of the Tweet', - }, - { - displayName: 'Until', - name: 'until', - type: 'dateTime', - default: '', - description: 'Returns tweets created before the given date', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - description: 'The ID of the tweet', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Include Entities', - name: 'includeEntities', - type: 'boolean', - default: false, - description: 'Whether the entities will be omitted', - }, - ], - }, - { - displayName: 'Tweet ID', - name: 'tweetId', - type: 'string', - required: true, - default: '', - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - description: 'The ID of the tweet', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Trim User', - name: 'trimUser', - type: 'boolean', - default: false, - description: - 'Whether each tweet returned in a timeline will include a user object including only the status authors numerical ID', - }, - ], - }, - ], - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', - codex: { - categories: ['Marketing'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twitter/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/twitter' }], - }, - alias: ['Tweet', 'Twitter', 'X', 'X API'], - }, -} satisfies INodeTypeDescription; - -export const nodeTypeTwitterV2 = { - displayName: 'X (Formerly Twitter)', - name: 'n8n-nodes-base.twitter', - group: ['output'], - subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', - description: 'Post, like, and search tweets, send messages, search users, and add users to lists', - defaultVersion: 2, - version: 2, - defaults: { name: 'X' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'twitterOAuth2Api', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Direct Message', - value: 'directMessage', - description: 'Send a direct message to a user', - }, - { name: 'List', value: 'list', description: 'Add a user to a list' }, - { name: 'Tweet', value: 'tweet', description: 'Create, like, search, or delete a tweet' }, - { name: 'User', value: 'user', description: 'Search users by username' }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'tweet', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['directMessage'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Send a direct message to a user', - action: 'Create Direct Message', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to send the message to', - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - typeOptions: { rows: 2 }, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - description: - 'The text of the direct message. URL encoding is required. Max length of 10,000 characters.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['directMessage'] } }, - options: [ - { - displayName: 'Attachment ID', - name: 'attachments', - type: 'string', - default: '', - placeholder: '1664279886239010824', - description: 'The attachment ID to associate with the message', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['list'] } }, - options: [ - { - name: 'Add Member', - value: 'add', - description: 'Add a member to a list', - action: 'Add Member to List', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'add', - }, - { - displayName: 'List', - name: 'list', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The list you want to add the user to', - displayOptions: { show: { operation: ['add'], resource: ['list'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 99923132', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/i/lists/99923132', - url: '', - }, - ], - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to add to the list', - displayOptions: { show: { operation: ['add'], resource: ['list'] } }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['tweet'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create, quote, or reply to a tweet', - action: 'Create Tweet', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a tweet', - action: 'Delete Tweet', - }, - { name: 'Like', value: 'like', description: 'Like a tweet', action: 'Like Tweet' }, - { - name: 'Retweet', - value: 'retweet', - description: 'Retweet a tweet', - action: 'Retweet Tweet', - }, - { - name: 'Search', - value: 'search', - description: 'Search for tweets from the last seven days', - action: 'Search Tweets', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - typeOptions: { rows: 2 }, - default: '', - required: true, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - description: - 'The text of the status update. URLs must be encoded. Links wrapped with the t.co shortener will affect character count', - }, - { - displayName: 'Options', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['create'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Location ID', - name: 'location', - type: 'string', - placeholder: '4e696bef7e24d378', - default: '', - description: 'Location information for the tweet', - }, - { - displayName: 'Media ID', - name: 'attachments', - type: 'string', - default: '', - placeholder: '1664279886239010824', - description: 'The attachment ID to associate with the message', - }, - { - displayName: 'Quote a Tweet', - name: 'inQuoteToStatusId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - description: 'The tweet being quoted', - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Reply to Tweet', - name: 'inReplyToStatusId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - description: 'The tweet being replied to', - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - ], - }, - { - displayName: 'Locations are not supported due to Twitter V2 API limitations', - name: 'noticeLocation', - type: 'notice', - displayOptions: { show: { '/additionalFields.location': [''] } }, - default: '', - }, - { - displayName: 'Attachements are not supported due to Twitter V2 API limitations', - name: 'noticeAttachments', - type: 'notice', - displayOptions: { show: { '/additionalFields.attachments': [''] } }, - default: '', - }, - { - displayName: 'Tweet', - name: 'tweetDeleteId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to delete', - displayOptions: { show: { resource: ['tweet'], operation: ['delete'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Tweet', - name: 'tweetId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to like', - displayOptions: { show: { operation: ['like'], resource: ['tweet'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Search Term', - name: 'searchText', - type: 'string', - required: true, - default: '', - placeholder: 'e.g. automation', - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - description: - 'A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity. Check the searching examples here.', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - default: false, - description: 'Whether to return all results or only up to a given limit', - displayOptions: { show: { resource: ['tweet'], operation: ['search'] } }, - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - default: 50, - description: 'Max number of results to return', - typeOptions: { minValue: 1 }, - displayOptions: { - show: { resource: ['tweet'], operation: ['search'], returnAll: [false] }, - }, - }, - { - displayName: 'Options', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { operation: ['search'], resource: ['tweet'] } }, - options: [ - { - displayName: 'Sort Order', - name: 'sortOrder', - type: 'options', - options: [ - { name: 'Recent', value: 'recency' }, - { name: 'Relevant', value: 'relevancy' }, - ], - description: 'The order in which to return results', - default: 'recency', - }, - { - displayName: 'After', - name: 'startTime', - type: 'dateTime', - default: '', - description: - "Tweets before this date will not be returned. This date must be within the last 7 days if you don't have Academic Research access.", - }, - { - displayName: 'Before', - name: 'endTime', - type: 'dateTime', - default: '', - description: - "Tweets after this date will not be returned. This date must be within the last 7 days if you don't have Academic Research access.", - }, - { - displayName: 'Tweet Fields', - name: 'tweetFieldsObject', - type: 'multiOptions', - options: [ - { name: 'Attachments', value: 'attachments' }, - { name: 'Author ID', value: 'author_id' }, - { name: 'Context Annotations', value: 'context_annotations' }, - { name: 'Conversation ID', value: 'conversation_id' }, - { name: 'Created At', value: 'created_at' }, - { name: 'Edit Controls', value: 'edit_controls' }, - { name: 'Entities', value: 'entities' }, - { name: 'Geo', value: 'geo' }, - { name: 'ID', value: 'id' }, - { name: 'In Reply To User ID', value: 'in_reply_to_user_id' }, - { name: 'Lang', value: 'lang' }, - { name: 'Non Public Metrics', value: 'non_public_metrics' }, - { name: 'Public Metrics', value: 'public_metrics' }, - { name: 'Organic Metrics', value: 'organic_metrics' }, - { name: 'Promoted Metrics', value: 'promoted_metrics' }, - { name: 'Possibly Sensitive', value: 'possibly_sensitive' }, - { name: 'Referenced Tweets', value: 'referenced_tweets' }, - { name: 'Reply Settings', value: 'reply_settings' }, - { name: 'Source', value: 'source' }, - { name: 'Text', value: 'text' }, - { name: 'Withheld', value: 'withheld' }, - ], - default: [], - description: - 'The fields to add to each returned tweet object. Default fields are: ID, text, edit_history_tweet_ids.', - }, - ], - }, - { - displayName: 'Tweet', - name: 'tweetId', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - description: 'The tweet to retweet', - displayOptions: { show: { operation: ['retweet'], resource: ['tweet'] } }, - modes: [ - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1187836157394112513', - url: '', - }, - { - displayName: 'By URL', - name: 'url', - type: 'string', - validation: [], - placeholder: 'e.g. https://twitter.com/n8n_io/status/1187836157394112513', - url: '', - }, - ], - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['user'] } }, - options: [ - { - name: 'Get', - value: 'searchUser', - description: 'Retrieve a user by username', - action: 'Get User', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'searchUser', - }, - { - displayName: 'User', - name: 'user', - type: 'resourceLocator', - default: { mode: 'username', value: '' }, - required: true, - description: 'The user you want to search', - displayOptions: { - show: { operation: ['searchUser'], resource: ['user'] }, - hide: { me: [true] }, - }, - modes: [ - { - displayName: 'By Username', - name: 'username', - type: 'string', - validation: [], - placeholder: 'e.g. n8n', - url: '', - }, - { - displayName: 'By ID', - name: 'id', - type: 'string', - validation: [], - placeholder: 'e.g. 1068479892537384960', - url: '', - }, - ], - }, - { - displayName: 'Me', - name: 'me', - type: 'boolean', - displayOptions: { show: { operation: ['searchUser'], resource: ['user'] } }, - default: false, - description: 'Whether you want to search the authenticated user', - }, - ], - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Twitter/x.svg', - codex: { - categories: ['Marketing'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twitter/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/twitter' }], - }, - alias: ['Tweet', 'Twitter', 'X', 'X API'], - }, -} satisfies INodeTypeDescription; - -export const nodeTypeReadImapV1 = { - displayName: 'Email Trigger (IMAP)', - name: 'n8n-nodes-base.emailReadImap', - icon: 'fa:inbox', - group: ['trigger'], - description: 'Triggers the workflow when a new email is received', - defaultVersion: 2, - version: 1, - eventTriggerDescription: 'Waiting for you to receive an email', - defaults: { name: 'Email Trigger (IMAP)', color: '#44AA22' }, - triggerPanel: { - header: '', - executionsHelp: { - inactive: - "While building your workflow, click the 'listen' button, then send an email to make an event happen. This will trigger an execution, which will show up in this editor.

Once you're happy with your workflow, activate it. Then every time an email is received, the workflow will execute. These executions will show up in the executions list, but not in the editor.", - active: - "While building your workflow, click the 'listen' button, then send an email to make an event happen. This will trigger an execution, which will show up in this editor.

Your workflow will also execute automatically, since it's activated. Every time an email is received, this node will trigger an execution. These executions will show up in the executions list, but not in the editor.", - }, - activationHint: - "Once you’ve finished building your workflow, activate it to have it also listen continuously (you just won’t see those executions here).", - }, - inputs: [], - outputs: ['main'], - credentials: [{ name: 'imap', required: true, testedBy: 'imapConnectionTest' }], - properties: [ - { displayName: 'Mailbox Name', name: 'mailbox', type: 'string', default: 'INBOX' }, - { - displayName: 'Action', - name: 'postProcessAction', - type: 'options', - options: [ - { name: 'Mark as Read', value: 'read' }, - { name: 'Nothing', value: 'nothing' }, - ], - default: 'read', - description: - 'What to do after the email has been received. If "nothing" gets selected it will be processed multiple times.', - }, - { - displayName: 'Download Attachments', - name: 'downloadAttachments', - type: 'boolean', - default: false, - displayOptions: { show: { format: ['simple'] } }, - description: - 'Whether attachments of emails should be downloaded. Only set if needed as it increases processing.', - }, - { - displayName: 'Format', - name: 'format', - type: 'options', - options: [ - { - name: 'RAW', - value: 'raw', - description: - 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used', - }, - { - name: 'Resolved', - value: 'resolved', - description: - 'Returns the full email with all data resolved and attachments saved as binary data', - }, - { - name: 'Simple', - value: 'simple', - description: - 'Returns the full email; do not use if you wish to gather inline attachments', - }, - ], - default: 'simple', - description: 'The format to return the message in', - }, - { - displayName: 'Property Prefix Name', - name: 'dataPropertyAttachmentsPrefixName', - type: 'string', - default: 'attachment_', - displayOptions: { show: { format: ['resolved'] } }, - description: - 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"', - }, - { - displayName: 'Property Prefix Name', - name: 'dataPropertyAttachmentsPrefixName', - type: 'string', - default: 'attachment_', - displayOptions: { show: { format: ['simple'], downloadAttachments: [true] } }, - description: - 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Custom Email Rules', - name: 'customEmailConfig', - type: 'string', - default: '["UNSEEN"]', - description: - 'Custom email fetching rules. See node-imap\'s search function for more details.', - }, - { - displayName: 'Ignore SSL Issues', - name: 'allowUnauthorizedCerts', - type: 'boolean', - default: false, - description: 'Whether to connect even if SSL certificate validation is not possible', - }, - { - displayName: 'Force Reconnect', - name: 'forceReconnect', - type: 'number', - default: 60, - description: 'Sets an interval (in minutes) to force a reconnection', - }, - ], - }, - ], - codex: { - categories: ['Communication', 'Core Nodes'], - subcategories: { 'Core Nodes': ['Other Trigger Nodes'] }, - resources: { - primaryDocumentation: [ - { - url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.emailimap/', - }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/imap' }], - }, - }, -} satisfies INodeTypeDescription; - -export const nodeTypeReadImapV2 = { - displayName: 'Email Trigger (IMAP)', - name: 'n8n-nodes-base.emailReadImap', - icon: 'fa:inbox', - group: ['trigger'], - description: 'Triggers the workflow when a new email is received', - defaultVersion: 2, - version: 2, - eventTriggerDescription: 'Waiting for you to receive an email', - defaults: { name: 'Email Trigger (IMAP)', color: '#44AA22' }, - triggerPanel: { - header: '', - executionsHelp: { - inactive: - "While building your workflow, click the 'listen' button, then send an email to make an event happen. This will trigger an execution, which will show up in this editor.

Once you're happy with your workflow, activate it. Then every time an email is received, the workflow will execute. These executions will show up in the executions list, but not in the editor.", - active: - "While building your workflow, click the 'listen' button, then send an email to make an event happen. This will trigger an execution, which will show up in this editor.

Your workflow will also execute automatically, since it's activated. Every time an email is received, this node will trigger an execution. These executions will show up in the executions list, but not in the editor.", - }, - activationHint: - "Once you’ve finished building your workflow, activate it to have it also listen continuously (you just won’t see those executions here).", - }, - inputs: [], - outputs: ['main'], - credentials: [{ name: 'imap', required: true, testedBy: 'imapConnectionTest' }], - properties: [ - { displayName: 'Mailbox Name', name: 'mailbox', type: 'string', default: 'INBOX' }, - { - displayName: 'Action', - name: 'postProcessAction', - type: 'options', - options: [ - { name: 'Mark as Read', value: 'read' }, - { name: 'Nothing', value: 'nothing' }, - ], - default: 'read', - description: - 'What to do after the email has been received. If "nothing" gets selected it will be processed multiple times.', - }, - { - displayName: 'Download Attachments', - name: 'downloadAttachments', - type: 'boolean', - default: false, - displayOptions: { show: { format: ['simple'] } }, - description: - 'Whether attachments of emails should be downloaded. Only set if needed as it increases processing.', - }, - { - displayName: 'Format', - name: 'format', - type: 'options', - options: [ - { - name: 'RAW', - value: 'raw', - description: - 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used', - }, - { - name: 'Resolved', - value: 'resolved', - description: - 'Returns the full email with all data resolved and attachments saved as binary data', - }, - { - name: 'Simple', - value: 'simple', - description: - 'Returns the full email; do not use if you wish to gather inline attachments', - }, - ], - default: 'simple', - description: 'The format to return the message in', - }, - { - displayName: 'Property Prefix Name', - name: 'dataPropertyAttachmentsPrefixName', - type: 'string', - default: 'attachment_', - displayOptions: { show: { format: ['resolved'] } }, - description: - 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"', - }, - { - displayName: 'Property Prefix Name', - name: 'dataPropertyAttachmentsPrefixName', - type: 'string', - default: 'attachment_', - displayOptions: { show: { format: ['simple'], downloadAttachments: [true] } }, - description: - 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Custom Email Rules', - name: 'customEmailConfig', - type: 'string', - default: '["UNSEEN"]', - description: - 'Custom email fetching rules. See node-imap\'s search function for more details.', - }, - { - displayName: 'Force Reconnect Every Minutes', - name: 'forceReconnect', - type: 'number', - default: 60, - description: 'Sets an interval (in minutes) to force a reconnection', - }, - ], - }, - ], - codex: { - categories: ['Communication', 'Core Nodes'], - subcategories: { 'Core Nodes': ['Other Trigger Nodes'] }, - resources: { - primaryDocumentation: [ - { - url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.emailimap/', - }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/imap' }], - }, - }, -} satisfies INodeTypeDescription; - -export const nodeTypeNextCloudV1 = { - displayName: 'Nextcloud', - name: 'n8n-nodes-base.nextCloud', - group: ['input'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Access data on Nextcloud', - defaults: { name: 'Nextcloud' }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'nextCloudApi', - required: true, - displayOptions: { show: { authentication: ['accessToken'] } }, - }, - { - name: 'nextCloudOAuth2Api', - required: true, - displayOptions: { show: { authentication: ['oAuth2'] } }, - }, - ], - properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { name: 'Access Token', value: 'accessToken' }, - { name: 'OAuth2', value: 'oAuth2' }, - ], - default: 'accessToken', - }, - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { name: 'File', value: 'file' }, - { name: 'Folder', value: 'folder' }, - { name: 'User', value: 'user' }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'file', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['file'] } }, - options: [ - { name: 'Copy', value: 'copy', description: 'Copy a file', action: 'Copy a file' }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a file', - action: 'Delete a file', - }, - { - name: 'Download', - value: 'download', - description: 'Download a file', - action: 'Download a file', - }, - { name: 'Move', value: 'move', description: 'Move a file', action: 'Move a file' }, - { name: 'Share', value: 'share', description: 'Share a file', action: 'Share a file' }, - { - name: 'Upload', - value: 'upload', - description: 'Upload a file', - action: 'Upload a file', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'upload', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['folder'] } }, - options: [ - { name: 'Copy', value: 'copy', description: 'Copy a folder', action: 'Copy a folder' }, - { - name: 'Create', - value: 'create', - description: 'Create a folder', - action: 'Create a folder', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a folder', - action: 'Delete a folder', - }, - { - name: 'List', - value: 'list', - description: 'Return the contents of a given folder', - action: 'List a folder', - }, - { name: 'Move', value: 'move', description: 'Move a folder', action: 'Move a folder' }, - { - name: 'Share', - value: 'share', - description: 'Share a folder', - action: 'Share a folder', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['user'] } }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Invite a user to a NextCloud organization', - action: 'Create a user', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a user', - action: 'Delete a user', - }, - { - name: 'Get', - value: 'get', - description: 'Retrieve information about a single user', - action: 'Get a user', - }, - { - name: 'Get Many', - value: 'getAll', - description: 'Retrieve a list of users', - action: 'Get many users', - }, - { - name: 'Update', - value: 'update', - description: 'Edit attributes related to a user', - action: 'Update a user', - }, - { name: 'Custom API Call', value: '__CUSTOM_API_CALL__' }, - ], - default: 'create', - }, - { - displayName: 'From Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['copy'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/original.txt', - description: 'The path of file or folder to copy. The path should start with "/".', - }, - { - displayName: 'To Path', - name: 'toPath', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['copy'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/copy.txt', - description: 'The destination path of file or folder. The path should start with "/".', - }, - { - displayName: 'Delete Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['delete'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/2019/invoice_1.pdf', - description: - 'The path to delete. Can be a single file or a whole folder. The path should start with "/".', - }, - { - displayName: 'From Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['move'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/old_name.txt', - description: 'The path of file or folder to move. The path should start with "/".', - }, - { - displayName: 'To Path', - name: 'toPath', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['move'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/new_name.txt', - description: 'The new path of file or folder. The path should start with "/".', - }, - { - displayName: 'File Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['download'], resource: ['file'] } }, - placeholder: '/invoices/2019/invoice_1.pdf', - description: - 'The file path of the file to download. Has to contain the full path. The path should start with "/".', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { show: { operation: ['download'], resource: ['file'] } }, - description: 'Name of the binary property to which to write the data of the read file', - }, - { - displayName: 'File Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['upload'], resource: ['file'] } }, - placeholder: '/invoices/2019/invoice_1.pdf', - description: - 'The absolute file path of the file to upload. Has to contain the full path. The parent folder has to exist. Existing files get overwritten.', - }, - { - displayName: 'Binary Data', - name: 'binaryDataUpload', - type: 'boolean', - default: false, - required: true, - displayOptions: { show: { operation: ['upload'], resource: ['file'] } }, - }, - { - displayName: 'File Content', - name: 'fileContent', - type: 'string', - default: '', - displayOptions: { - show: { binaryDataUpload: [false], operation: ['upload'], resource: ['file'] }, - }, - placeholder: '', - description: 'The text content of the file to upload', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { - show: { binaryDataUpload: [true], operation: ['upload'], resource: ['file'] }, - }, - placeholder: '', - description: - 'Name of the binary property which contains the data for the file to be uploaded', - }, - { - displayName: 'File Path', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['share'], resource: ['file', 'folder'] } }, - placeholder: '/invoices/2019/invoice_1.pdf', - description: - 'The file path of the file to share. Has to contain the full path. The path should start with "/".', - }, - { - displayName: 'Share Type', - name: 'shareType', - type: 'options', - displayOptions: { show: { operation: ['share'], resource: ['file', 'folder'] } }, - options: [ - { name: 'Circle', value: 7 }, - { name: 'Email', value: 4 }, - { name: 'Group', value: 1 }, - { name: 'Public Link', value: 3 }, - { name: 'User', value: 0 }, - ], - default: 0, - description: 'The share permissions to set', - }, - { - displayName: 'Circle ID', - name: 'circleId', - type: 'string', - displayOptions: { - show: { resource: ['file', 'folder'], operation: ['share'], shareType: [7] }, - }, - default: '', - description: 'The ID of the circle to share with', - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - placeholder: 'name@email.com', - displayOptions: { - show: { resource: ['file', 'folder'], operation: ['share'], shareType: [4] }, - }, - default: '', - description: 'The Email address to share with', - }, - { - displayName: 'Group ID', - name: 'groupId', - type: 'string', - displayOptions: { - show: { resource: ['file', 'folder'], operation: ['share'], shareType: [1] }, - }, - default: '', - description: 'The ID of the group to share with', - }, - { - displayName: 'User', - name: 'user', - type: 'string', - displayOptions: { - show: { resource: ['file', 'folder'], operation: ['share'], shareType: [0] }, - }, - default: '', - description: 'The user to share with', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { show: { resource: ['file', 'folder'], operation: ['share'] } }, - options: [ - { - displayName: 'Password', - name: 'password', - type: 'string', - typeOptions: { password: true }, - displayOptions: { - show: { '/resource': ['file', 'folder'], '/operation': ['share'], '/shareType': [3] }, - }, - default: '', - description: 'Optional search string', - }, - { - displayName: 'Permissions', - name: 'permissions', - type: 'options', - options: [ - { name: 'All', value: 31 }, - { name: 'Create', value: 4 }, - { name: 'Delete', value: 8 }, - { name: 'Read', value: 1 }, - { name: 'Update', value: 2 }, - ], - default: 1, - description: 'The share permissions to set', - }, - ], - }, - { - displayName: 'Folder', - name: 'path', - type: 'string', - default: '', - required: true, - displayOptions: { show: { operation: ['create'], resource: ['folder'] } }, - placeholder: '/invoices/2019', - description: - 'The folder to create. The parent folder has to exist. The path should start with "/".', - }, - { - displayName: 'Folder Path', - name: 'path', - type: 'string', - default: '', - displayOptions: { show: { operation: ['list'], resource: ['folder'] } }, - placeholder: '/invoices/2019/', - description: 'The path of which to list the content. The path should start with "/".', - }, - { - displayName: 'Username', - name: 'userId', - type: 'string', - default: '', - required: true, - displayOptions: { show: { resource: ['user'], operation: ['create'] } }, - placeholder: 'john', - description: 'Username the user will have', - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - default: '', - required: true, - displayOptions: { show: { resource: ['user'], operation: ['create'] } }, - placeholder: 'john@email.com', - description: 'The email of the user to invite', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { resource: ['user'], operation: ['create'] } }, - options: [ - { - displayName: 'Display Name', - name: 'displayName', - type: 'string', - default: '', - description: 'The display name of the user to invite', - }, - ], - }, - { - displayName: 'Username', - name: 'userId', - type: 'string', - default: '', - required: true, - displayOptions: { show: { resource: ['user'], operation: ['delete', 'get', 'update'] } }, - placeholder: 'john', - description: 'Username the user will have', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { show: { resource: ['user'], operation: ['getAll'] } }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - displayOptions: { show: { resource: ['user'], operation: ['getAll'], returnAll: [false] } }, - typeOptions: { minValue: 1, maxValue: 100 }, - default: 50, - description: 'Max number of results to return', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { show: { resource: ['user'], operation: ['getAll'] } }, - options: [ - { - displayName: 'Search', - name: 'search', - type: 'string', - default: '', - description: 'Optional search string', - }, - { - displayName: 'Offset', - name: 'offset', - type: 'number', - default: '', - description: 'Optional offset value', - }, - ], - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'fixedCollection', - typeOptions: { multipleValues: false }, - placeholder: 'Add Option', - default: {}, - displayOptions: { show: { resource: ['user'], operation: ['update'] } }, - options: [ - { - displayName: 'Fields', - name: 'field', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'options', - default: 'email', - options: [ - { - name: 'Address', - value: 'address', - description: 'The new address for the user', - }, - { - name: 'Display Name', - value: 'displayname', - description: 'The new display name for the user', - }, - { name: 'Email', value: 'email', description: 'The new email for the user' }, - { - name: 'Password', - value: 'password', - description: 'The new password for the user', - }, - { - name: 'Twitter', - value: 'twitter', - description: 'The new twitter handle for the user', - }, - { - name: 'Website', - value: 'website', - description: 'The new website for the user', - }, - ], - description: 'Key of the updated attribute', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the updated attribute', - }, - ], - }, - ], - }, - ], - codex: { - categories: ['Data & Storage'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.nextcloud/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/nextCloud' }], - }, - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/NextCloud/nextcloud.svg', -} satisfies INodeTypeDescription; - -export const nodeTypeTelegramV1 = { - displayName: 'Telegram', - name: 'n8n-nodes-base.telegram', - group: ['output'], - version: [1, 1.1], - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Sends data to Telegram', - defaults: { name: 'Telegram' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'telegramApi', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { name: 'Chat', value: 'chat' }, - { name: 'Callback', value: 'callback' }, - { name: 'File', value: 'file' }, - { name: 'Message', value: 'message' }, - ], - default: 'message', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['chat'] } }, - options: [ - { - name: 'Get', - value: 'get', - description: 'Get up to date information about a chat', - action: 'Get a chat', - }, - { - name: 'Get Administrators', - value: 'administrators', - description: 'Get the Administrators of a chat', - action: 'Get all administrators in a chat', - }, - { - name: 'Get Member', - value: 'member', - description: 'Get the member of a chat', - action: 'Get a member in a chat', - }, - { - name: 'Leave', - value: 'leave', - description: 'Leave a group, supergroup or channel', - action: 'Leave a chat', - }, - { - name: 'Set Description', - value: 'setDescription', - description: 'Set the description of a chat', - action: 'Set description on a chat', - }, - { - name: 'Set Title', - value: 'setTitle', - description: 'Set the title of a chat', - action: 'Set a title on a chat', - }, - ], - default: 'get', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['callback'] } }, - options: [ - { - name: 'Answer Query', - value: 'answerQuery', - description: 'Send answer to callback query sent from inline keyboard', - action: 'Answer Query a callback', - }, - { - name: 'Answer Inline Query', - value: 'answerInlineQuery', - description: 'Send answer to callback query sent from inline bot', - action: 'Answer an inline query callback', - }, - ], - default: 'answerQuery', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['file'] } }, - options: [{ name: 'Get', value: 'get', description: 'Get a file', action: 'Get a file' }], - default: 'get', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['message'] } }, - options: [ - { - name: 'Delete Chat Message', - value: 'deleteMessage', - description: 'Delete a chat message', - action: 'Delete a chat message', - }, - { - name: 'Edit Message Text', - value: 'editMessageText', - description: 'Edit a text message', - action: 'Edit a test message', - }, - { - name: 'Pin Chat Message', - value: 'pinChatMessage', - description: 'Pin a chat message', - action: 'Pin a chat message', - }, - { - name: 'Send Animation', - value: 'sendAnimation', - description: 'Send an animated file', - action: 'Send an animated file', - }, - { - name: 'Send Audio', - value: 'sendAudio', - description: 'Send a audio file', - action: 'Send an audio file', - }, - { - name: 'Send Chat Action', - value: 'sendChatAction', - description: 'Send a chat action', - action: 'Send a chat action', - }, - { - name: 'Send Document', - value: 'sendDocument', - description: 'Send a document', - action: 'Send a document', - }, - { - name: 'Send Location', - value: 'sendLocation', - description: 'Send a location', - action: 'Send a location', - }, - { - name: 'Send Media Group', - value: 'sendMediaGroup', - description: 'Send group of photos or videos to album', - action: 'Send a media group message', - }, - { - name: 'Send Message', - value: 'sendMessage', - description: 'Send a text message', - action: 'Send a text message', - }, - { - name: 'Send Photo', - value: 'sendPhoto', - description: 'Send a photo', - action: 'Send a photo message', - }, - { - name: 'Send Sticker', - value: 'sendSticker', - description: 'Send a sticker', - action: 'Send a sticker', - }, - { - name: 'Send Video', - value: 'sendVideo', - description: 'Send a video', - action: 'Send a video', - }, - { - name: 'Unpin Chat Message', - value: 'unpinChatMessage', - description: 'Unpin a chat message', - action: 'Unpin a chat message', - }, - ], - default: 'sendMessage', - }, - { - displayName: 'Chat ID', - name: 'chatId', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'administrators', - 'deleteMessage', - 'get', - 'leave', - 'member', - 'pinChatMessage', - 'setDescription', - 'setTitle', - 'sendAnimation', - 'sendAudio', - 'sendChatAction', - 'sendDocument', - 'sendLocation', - 'sendMessage', - 'sendMediaGroup', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - 'unpinChatMessage', - ], - resource: ['chat', 'message'], - }, - }, - required: true, - description: - 'Unique identifier for the target chat or username of the target channel (in the format @channelusername)', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['deleteMessage'], resource: ['message'] } }, - required: true, - description: 'Unique identifier of the message to delete', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['pinChatMessage', 'unpinChatMessage'], resource: ['message'] }, - }, - required: true, - description: 'Unique identifier of the message to pin or unpin', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['pinChatMessage'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Disable Notification', - name: 'disable_notification', - type: 'boolean', - default: false, - description: - 'Whether to send a notification to all chat members about the new pinned message', - }, - ], - }, - { - displayName: 'User ID', - name: 'userId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['member'], resource: ['chat'] } }, - required: true, - description: 'Unique identifier of the target user', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { show: { operation: ['setDescription'], resource: ['chat'] } }, - required: true, - description: 'New chat description, 0-255 characters', - }, - { - displayName: 'Title', - name: 'title', - type: 'string', - default: '', - displayOptions: { show: { operation: ['setTitle'], resource: ['chat'] } }, - required: true, - description: 'New chat title, 1-255 characters', - }, - { - displayName: 'Query ID', - name: 'queryId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerQuery'], resource: ['callback'] } }, - required: true, - description: 'Unique identifier for the query to be answered', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['answerQuery'], resource: ['callback'] } }, - default: {}, - options: [ - { - displayName: 'Cache Time', - name: 'cache_time', - type: 'number', - typeOptions: { minValue: 0 }, - default: 0, - description: - 'The maximum amount of time in seconds that the result of the callback query may be cached client-side', - }, - { - displayName: 'Show Alert', - name: 'show_alert', - type: 'boolean', - default: false, - description: - 'Whether an alert will be shown by the client instead of a notification at the top of the chat screen', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: "URL that will be opened by the user's client", - }, - ], - }, - { - displayName: 'Query ID', - name: 'queryId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - required: true, - description: 'Unique identifier for the answered query', - }, - { - displayName: 'Results', - name: 'results', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - required: true, - description: 'A JSON-serialized array of results for the inline query', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - default: {}, - options: [ - { - displayName: 'Cache Time', - name: 'cache_time', - type: 'number', - typeOptions: { minValue: 0 }, - default: 0, - description: - 'The maximum amount of time in seconds that the result of the callback query may be cached client-side', - }, - { - displayName: 'Show Alert', - name: 'show_alert', - type: 'boolean', - default: false, - description: - 'Whether an alert will be shown by the client instead of a notification at the top of the chat screen', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: "URL that will be opened by the user's client", - }, - ], - }, - { - displayName: 'File ID', - name: 'fileId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['get'], resource: ['file'] } }, - required: true, - description: 'The ID of the file', - }, - { - displayName: 'Download', - name: 'download', - type: 'boolean', - displayOptions: { show: { operation: ['get'], resource: ['file'] } }, - default: true, - description: 'Whether to download the file', - }, - { - displayName: 'Message Type', - name: 'messageType', - type: 'options', - displayOptions: { show: { operation: ['editMessageText'], resource: ['message'] } }, - options: [ - { name: 'Inline Message', value: 'inlineMessage' }, - { name: 'Message', value: 'message' }, - ], - default: 'message', - description: 'The type of the message to edit', - }, - { - displayName: 'Chat ID', - name: 'chatId', - type: 'string', - default: '', - displayOptions: { - show: { messageType: ['message'], operation: ['editMessageText'], resource: ['message'] }, - }, - required: true, - description: - 'Unique identifier for the target chat or username of the target channel (in the format @channelusername). To find your chat ID ask @get_id_bot.', - }, - { - displayName: 'Binary Data', - name: 'binaryData', - type: 'boolean', - default: false, - required: true, - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - resource: ['message'], - }, - }, - description: 'Whether the data to upload should be taken from binary field', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - resource: ['message'], - binaryData: [true], - }, - }, - placeholder: '', - description: 'Name of the binary property that contains the data to upload', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { - show: { messageType: ['message'], operation: ['editMessageText'], resource: ['message'] }, - }, - required: true, - description: 'Unique identifier of the message to edit', - }, - { - displayName: 'Inline Message ID', - name: 'inlineMessageId', - type: 'string', - default: '', - displayOptions: { - show: { - messageType: ['inlineMessage'], - operation: ['editMessageText'], - resource: ['message'], - }, - }, - required: true, - description: 'Unique identifier of the inline message to edit', - }, - { - displayName: 'Reply Markup', - name: 'replyMarkup', - displayOptions: { show: { operation: ['editMessageText'], resource: ['message'] } }, - type: 'options', - options: [ - { name: 'None', value: 'none' }, - { name: 'Inline Keyboard', value: 'inlineKeyboard' }, - ], - default: 'none', - description: 'Additional interface options', - }, - { - displayName: 'Animation', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendAnimation'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Animation to send. Pass a file_id to send an animation that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get an animation from the Internet.', - }, - { - displayName: 'Audio', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendAudio'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Audio file to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Action', - name: 'action', - type: 'options', - displayOptions: { show: { operation: ['sendChatAction'], resource: ['message'] } }, - options: [ - { name: 'Find Location', value: 'find_location', action: 'Find location' }, - { name: 'Record Audio', value: 'record_audio', action: 'Record audio' }, - { name: 'Record Video', value: 'record_video', action: 'Record video' }, - { name: 'Record Video Note', value: 'record_video_note', action: 'Record video note' }, - { name: 'Typing', value: 'typing', action: 'Typing a message' }, - { name: 'Upload Audio', value: 'upload_audio', action: 'Upload audio' }, - { name: 'Upload Document', value: 'upload_document', action: 'Upload document' }, - { name: 'Upload Photo', value: 'upload_photo', action: 'Upload photo' }, - { name: 'Upload Video', value: 'upload_video', action: 'Upload video' }, - { name: 'Upload Video Note', value: 'upload_video_note', action: 'Upload video note' }, - ], - default: 'typing', - description: - 'Type of action to broadcast. Choose one, depending on what the user is about to receive. The status is set for 5 seconds or less (when a message arrives from your bot).', - }, - { - displayName: 'Document', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendDocument'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Document to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Latitude', - name: 'latitude', - type: 'number', - default: 0, - typeOptions: { numberPrecision: 10, minValue: -90, maxValue: 90 }, - displayOptions: { show: { operation: ['sendLocation'], resource: ['message'] } }, - description: 'Location latitude', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'number', - typeOptions: { numberPrecision: 10, minValue: -180, maxValue: 180 }, - default: 0, - displayOptions: { show: { operation: ['sendLocation'], resource: ['message'] } }, - description: 'Location longitude', - }, - { - displayName: 'Media', - name: 'media', - type: 'fixedCollection', - displayOptions: { show: { operation: ['sendMediaGroup'], resource: ['message'] } }, - description: 'The media to add', - placeholder: 'Add Media', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Media', - name: 'media', - values: [ - { - displayName: 'Type', - name: 'type', - type: 'options', - options: [ - { name: 'Photo', value: 'photo' }, - { name: 'Video', value: 'video' }, - ], - default: 'photo', - description: 'The type of the media to add', - }, - { - displayName: 'Media File', - name: 'media', - type: 'string', - default: '', - description: - 'Media to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or pass an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Caption', - name: 'caption', - type: 'string', - default: '', - description: 'Caption text to set, 0-1024 characters', - }, - { - displayName: 'Parse Mode', - name: 'parse_mode', - type: 'options', - options: [ - { name: 'Markdown (Legacy)', value: 'Markdown' }, - { name: 'MarkdownV2', value: 'MarkdownV2' }, - { name: 'HTML', value: 'HTML' }, - ], - default: 'HTML', - description: 'How to parse the text', - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { - show: { operation: ['editMessageText', 'sendMessage'], resource: ['message'] }, - }, - description: 'Text of the message to be sent', - }, - { - displayName: 'Photo', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendPhoto'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Photo to send. Pass a file_id to send a photo that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a photo from the Internet.', - }, - { - displayName: 'Sticker', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendSticker'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Sticker to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a .webp file from the Internet.', - }, - { - displayName: 'Video', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendVideo'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Video file to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Reply Markup', - name: 'replyMarkup', - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendDocument', - 'sendMessage', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - 'sendAudio', - 'sendLocation', - ], - resource: ['message'], - }, - }, - type: 'options', - options: [ - { name: 'Force Reply', value: 'forceReply' }, - { name: 'Inline Keyboard', value: 'inlineKeyboard' }, - { name: 'None', value: 'none' }, - { name: 'Reply Keyboard', value: 'replyKeyboard' }, - { name: 'Reply Keyboard Remove', value: 'replyKeyboardRemove' }, - ], - default: 'none', - description: 'Additional interface options', - }, - { - displayName: 'Force Reply', - name: 'forceReply', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { replyMarkup: ['forceReply'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Force Reply', - name: 'force_reply', - type: 'boolean', - default: false, - description: - 'Whether to show reply interface to the user, as if they manually selected the bot‘s message and tapped ’Reply', - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to force reply from specific users only', - }, - ], - }, - { - displayName: 'Inline Keyboard', - name: 'inlineKeyboard', - placeholder: 'Add Keyboard Row', - description: 'Adds an inline keyboard that appears right next to the message it belongs to', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { replyMarkup: ['inlineKeyboard'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Rows', - name: 'rows', - values: [ - { - displayName: 'Row', - name: 'row', - type: 'fixedCollection', - description: 'The value to set', - placeholder: 'Add Button', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Buttons', - name: 'buttons', - values: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: 'Label text on the button', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Callback Data', - name: 'callback_data', - type: 'string', - default: '', - description: - 'Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes', - }, - { - displayName: 'Pay', - name: 'pay', - type: 'boolean', - default: false, - description: 'Whether to send a Pay button', - }, - { - displayName: 'Switch Inline Query Current Chat', - name: 'switch_inline_query_current_chat', - type: 'string', - default: '', - description: - "If set, pressing the button will insert the bot‘s username and the specified inline query in the current chat's input field.Can be empty, in which case only the bot’s username will be inserted", - }, - { - displayName: 'Switch Inline Query', - name: 'switch_inline_query', - type: 'string', - default: '', - description: - 'If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot‘s username and the specified inline query in the input field. Can be empty, in which case just the bot’s username will be inserted.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: 'HTTP or tg:// URL to be opened when button is pressed', - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Reply Keyboard', - name: 'replyKeyboard', - placeholder: 'Add Reply Keyboard Row', - description: 'Adds a custom keyboard with reply options', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { replyMarkup: ['replyKeyboard'] } }, - default: {}, - options: [ - { - displayName: 'Rows', - name: 'rows', - values: [ - { - displayName: 'Row', - name: 'row', - type: 'fixedCollection', - description: 'The value to set', - placeholder: 'Add Button', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Buttons', - name: 'buttons', - values: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Request Contact', - name: 'request_contact', - type: 'boolean', - default: false, - description: - "Whether the user's phone number will be sent as a contact when the button is pressed.Available in private chats only", - }, - { - displayName: 'Request Location', - name: 'request_location', - type: 'boolean', - default: false, - description: "Whether the user's request_location", - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Reply Keyboard Options', - name: 'replyKeyboardOptions', - type: 'collection', - placeholder: 'Add Option', - displayOptions: { show: { replyMarkup: ['replyKeyboard'] } }, - default: {}, - options: [ - { - displayName: 'Resize Keyboard', - name: 'resize_keyboard', - type: 'boolean', - default: false, - description: - 'Whether to request clients to resize the keyboard vertically for optimal fit', - }, - { - displayName: 'One Time Keyboard', - name: 'one_time_keyboard', - type: 'boolean', - default: false, - description: "Whether to request clients to hide the keyboard as soon as it's been used", - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to show the keyboard to specific users only', - }, - ], - }, - { - displayName: 'Reply Keyboard Remove', - name: 'replyKeyboardRemove', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { replyMarkup: ['replyKeyboardRemove'] } }, - default: {}, - options: [ - { - displayName: 'Remove Keyboard', - name: 'remove_keyboard', - type: 'boolean', - default: false, - description: 'Whether to request clients to remove the custom keyboard', - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to force reply from specific users only', - }, - ], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'editMessageText', - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendLocation', - 'sendMessage', - 'sendMediaGroup', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - ], - resource: ['message'], - }, - }, - default: {}, - options: [ - { - displayName: 'Append n8n Attribution', - name: 'appendAttribution', - type: 'boolean', - default: true, - description: - 'Whether to include the phrase “This message was sent automatically with n8n” to the end of the message', - displayOptions: { show: { '/operation': ['sendMessage'] } }, - }, - { - displayName: 'Caption', - name: 'caption', - type: 'string', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - ], - }, - }, - default: '', - description: 'Caption text to set, 0-1024 characters', - }, - { - displayName: 'Disable Notification', - name: 'disable_notification', - type: 'boolean', - default: false, - displayOptions: { hide: { '/operation': ['editMessageText'] } }, - description: - 'Whether to send the message silently. Users will receive a notification with no sound.', - }, - { - displayName: 'Disable WebPage Preview', - name: 'disable_web_page_preview', - type: 'boolean', - displayOptions: { show: { '/operation': ['editMessageText', 'sendMessage'] } }, - default: false, - description: 'Whether to disable link previews for links in this message', - }, - { - displayName: 'Duration', - name: 'duration', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendAudio', 'sendVideo'] } }, - default: 0, - description: 'Duration of clip in seconds', - }, - { - displayName: 'File Name', - name: 'fileName', - type: 'string', - default: '', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - '/resource': ['message'], - '/binaryData': [true], - }, - }, - placeholder: 'image.jpeg', - }, - { - displayName: 'Height', - name: 'height', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendVideo'] } }, - default: 0, - description: 'Height of the video', - }, - { - displayName: 'Parse Mode', - name: 'parse_mode', - type: 'options', - options: [ - { name: 'Markdown (Legacy)', value: 'Markdown' }, - { name: 'MarkdownV2', value: 'MarkdownV2' }, - { name: 'HTML', value: 'HTML' }, - ], - displayOptions: { - show: { - '/operation': [ - 'editMessageText', - 'sendAnimation', - 'sendAudio', - 'sendMessage', - 'sendPhoto', - 'sendVideo', - 'sendDocument', - ], - }, - }, - default: 'HTML', - description: 'How to parse the text', - }, - { - displayName: 'Performer', - name: 'performer', - type: 'string', - displayOptions: { show: { '/operation': ['sendAudio'] } }, - default: '', - description: 'Name of the performer', - }, - { - displayName: 'Reply To Message ID', - name: 'reply_to_message_id', - type: 'number', - displayOptions: { hide: { '/operation': ['editMessageText'] } }, - default: 0, - description: 'If the message is a reply, ID of the original message', - }, - { - displayName: 'Message Thread ID', - name: 'message_thread_id', - type: 'number', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendChatAction', - 'sendDocument', - 'sendLocation', - 'sendMediaGroup', - 'sendMessage', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - ], - }, - }, - default: 0, - description: 'The unique identifier of the forum topic', - }, - { - displayName: 'Title', - name: 'title', - type: 'string', - displayOptions: { show: { '/operation': ['sendAudio'] } }, - default: '', - description: 'Title of the track', - }, - { - displayName: 'Thumbnail', - name: 'thumb', - type: 'string', - displayOptions: { - show: { '/operation': ['sendAnimation', 'sendAudio', 'sendDocument', 'sendVideo'] }, - }, - default: '', - description: - 'Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail‘s width and height should not exceed 320.', - }, - { - displayName: 'Width', - name: 'width', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendVideo'] } }, - default: 0, - description: 'Width of the video', - }, - ], - }, - ], - codex: { - categories: ['Communication'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.telegram/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/telegram' }], - }, - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Telegram/telegram.svg', -} satisfies INodeTypeDescription; - -export const nodeTypeTelegramV1_1 = { - displayName: 'Telegram', - name: 'n8n-nodes-base.telegram', - group: ['output'], - version: [1, 1.1], - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Sends data to Telegram', - defaults: { name: 'Telegram' }, - inputs: ['main'], - outputs: ['main'], - credentials: [{ name: 'telegramApi', required: true }], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { name: 'Chat', value: 'chat' }, - { name: 'Callback', value: 'callback' }, - { name: 'File', value: 'file' }, - { name: 'Message', value: 'message' }, - ], - default: 'message', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['chat'] } }, - options: [ - { - name: 'Get', - value: 'get', - description: 'Get up to date information about a chat', - action: 'Get a chat', - }, - { - name: 'Get Administrators', - value: 'administrators', - description: 'Get the Administrators of a chat', - action: 'Get all administrators in a chat', - }, - { - name: 'Get Member', - value: 'member', - description: 'Get the member of a chat', - action: 'Get a member in a chat', - }, - { - name: 'Leave', - value: 'leave', - description: 'Leave a group, supergroup or channel', - action: 'Leave a chat', - }, - { - name: 'Set Description', - value: 'setDescription', - description: 'Set the description of a chat', - action: 'Set description on a chat', - }, - { - name: 'Set Title', - value: 'setTitle', - description: 'Set the title of a chat', - action: 'Set a title on a chat', - }, - ], - default: 'get', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['callback'] } }, - options: [ - { - name: 'Answer Query', - value: 'answerQuery', - description: 'Send answer to callback query sent from inline keyboard', - action: 'Answer Query a callback', - }, - { - name: 'Answer Inline Query', - value: 'answerInlineQuery', - description: 'Send answer to callback query sent from inline bot', - action: 'Answer an inline query callback', - }, - ], - default: 'answerQuery', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['file'] } }, - options: [{ name: 'Get', value: 'get', description: 'Get a file', action: 'Get a file' }], - default: 'get', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show: { resource: ['message'] } }, - options: [ - { - name: 'Delete Chat Message', - value: 'deleteMessage', - description: 'Delete a chat message', - action: 'Delete a chat message', - }, - { - name: 'Edit Message Text', - value: 'editMessageText', - description: 'Edit a text message', - action: 'Edit a test message', - }, - { - name: 'Pin Chat Message', - value: 'pinChatMessage', - description: 'Pin a chat message', - action: 'Pin a chat message', - }, - { - name: 'Send Animation', - value: 'sendAnimation', - description: 'Send an animated file', - action: 'Send an animated file', - }, - { - name: 'Send Audio', - value: 'sendAudio', - description: 'Send a audio file', - action: 'Send an audio file', - }, - { - name: 'Send Chat Action', - value: 'sendChatAction', - description: 'Send a chat action', - action: 'Send a chat action', - }, - { - name: 'Send Document', - value: 'sendDocument', - description: 'Send a document', - action: 'Send a document', - }, - { - name: 'Send Location', - value: 'sendLocation', - description: 'Send a location', - action: 'Send a location', - }, - { - name: 'Send Media Group', - value: 'sendMediaGroup', - description: 'Send group of photos or videos to album', - action: 'Send a media group message', - }, - { - name: 'Send Message', - value: 'sendMessage', - description: 'Send a text message', - action: 'Send a text message', - }, - { - name: 'Send Photo', - value: 'sendPhoto', - description: 'Send a photo', - action: 'Send a photo message', - }, - { - name: 'Send Sticker', - value: 'sendSticker', - description: 'Send a sticker', - action: 'Send a sticker', - }, - { - name: 'Send Video', - value: 'sendVideo', - description: 'Send a video', - action: 'Send a video', - }, - { - name: 'Unpin Chat Message', - value: 'unpinChatMessage', - description: 'Unpin a chat message', - action: 'Unpin a chat message', - }, - ], - default: 'sendMessage', - }, - { - displayName: 'Chat ID', - name: 'chatId', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'administrators', - 'deleteMessage', - 'get', - 'leave', - 'member', - 'pinChatMessage', - 'setDescription', - 'setTitle', - 'sendAnimation', - 'sendAudio', - 'sendChatAction', - 'sendDocument', - 'sendLocation', - 'sendMessage', - 'sendMediaGroup', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - 'unpinChatMessage', - ], - resource: ['chat', 'message'], - }, - }, - required: true, - description: - 'Unique identifier for the target chat or username of the target channel (in the format @channelusername)', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['deleteMessage'], resource: ['message'] } }, - required: true, - description: 'Unique identifier of the message to delete', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['pinChatMessage', 'unpinChatMessage'], resource: ['message'] }, - }, - required: true, - description: 'Unique identifier of the message to pin or unpin', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['pinChatMessage'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Disable Notification', - name: 'disable_notification', - type: 'boolean', - default: false, - description: - 'Whether to send a notification to all chat members about the new pinned message', - }, - ], - }, - { - displayName: 'User ID', - name: 'userId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['member'], resource: ['chat'] } }, - required: true, - description: 'Unique identifier of the target user', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { show: { operation: ['setDescription'], resource: ['chat'] } }, - required: true, - description: 'New chat description, 0-255 characters', - }, - { - displayName: 'Title', - name: 'title', - type: 'string', - default: '', - displayOptions: { show: { operation: ['setTitle'], resource: ['chat'] } }, - required: true, - description: 'New chat title, 1-255 characters', - }, - { - displayName: 'Query ID', - name: 'queryId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerQuery'], resource: ['callback'] } }, - required: true, - description: 'Unique identifier for the query to be answered', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['answerQuery'], resource: ['callback'] } }, - default: {}, - options: [ - { - displayName: 'Cache Time', - name: 'cache_time', - type: 'number', - typeOptions: { minValue: 0 }, - default: 0, - description: - 'The maximum amount of time in seconds that the result of the callback query may be cached client-side', - }, - { - displayName: 'Show Alert', - name: 'show_alert', - type: 'boolean', - default: false, - description: - 'Whether an alert will be shown by the client instead of a notification at the top of the chat screen', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: "URL that will be opened by the user's client", - }, - ], - }, - { - displayName: 'Query ID', - name: 'queryId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - required: true, - description: 'Unique identifier for the answered query', - }, - { - displayName: 'Results', - name: 'results', - type: 'string', - default: '', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - required: true, - description: 'A JSON-serialized array of results for the inline query', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { operation: ['answerInlineQuery'], resource: ['callback'] } }, - default: {}, - options: [ - { - displayName: 'Cache Time', - name: 'cache_time', - type: 'number', - typeOptions: { minValue: 0 }, - default: 0, - description: - 'The maximum amount of time in seconds that the result of the callback query may be cached client-side', - }, - { - displayName: 'Show Alert', - name: 'show_alert', - type: 'boolean', - default: false, - description: - 'Whether an alert will be shown by the client instead of a notification at the top of the chat screen', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: "URL that will be opened by the user's client", - }, - ], - }, - { - displayName: 'File ID', - name: 'fileId', - type: 'string', - default: '', - displayOptions: { show: { operation: ['get'], resource: ['file'] } }, - required: true, - description: 'The ID of the file', - }, - { - displayName: 'Download', - name: 'download', - type: 'boolean', - displayOptions: { show: { operation: ['get'], resource: ['file'] } }, - default: true, - description: 'Whether to download the file', - }, - { - displayName: 'Message Type', - name: 'messageType', - type: 'options', - displayOptions: { show: { operation: ['editMessageText'], resource: ['message'] } }, - options: [ - { name: 'Inline Message', value: 'inlineMessage' }, - { name: 'Message', value: 'message' }, - ], - default: 'message', - description: 'The type of the message to edit', - }, - { - displayName: 'Chat ID', - name: 'chatId', - type: 'string', - default: '', - displayOptions: { - show: { messageType: ['message'], operation: ['editMessageText'], resource: ['message'] }, - }, - required: true, - description: - 'Unique identifier for the target chat or username of the target channel (in the format @channelusername). To find your chat ID ask @get_id_bot.', - }, - { - displayName: 'Binary Data', - name: 'binaryData', - type: 'boolean', - default: false, - required: true, - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - resource: ['message'], - }, - }, - description: 'Whether the data to upload should be taken from binary field', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - resource: ['message'], - binaryData: [true], - }, - }, - placeholder: '', - description: 'Name of the binary property that contains the data to upload', - }, - { - displayName: 'Message ID', - name: 'messageId', - type: 'string', - default: '', - displayOptions: { - show: { messageType: ['message'], operation: ['editMessageText'], resource: ['message'] }, - }, - required: true, - description: 'Unique identifier of the message to edit', - }, - { - displayName: 'Inline Message ID', - name: 'inlineMessageId', - type: 'string', - default: '', - displayOptions: { - show: { - messageType: ['inlineMessage'], - operation: ['editMessageText'], - resource: ['message'], - }, - }, - required: true, - description: 'Unique identifier of the inline message to edit', - }, - { - displayName: 'Reply Markup', - name: 'replyMarkup', - displayOptions: { show: { operation: ['editMessageText'], resource: ['message'] } }, - type: 'options', - options: [ - { name: 'None', value: 'none' }, - { name: 'Inline Keyboard', value: 'inlineKeyboard' }, - ], - default: 'none', - description: 'Additional interface options', - }, - { - displayName: 'Animation', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendAnimation'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Animation to send. Pass a file_id to send an animation that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get an animation from the Internet.', - }, - { - displayName: 'Audio', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendAudio'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Audio file to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Action', - name: 'action', - type: 'options', - displayOptions: { show: { operation: ['sendChatAction'], resource: ['message'] } }, - options: [ - { name: 'Find Location', value: 'find_location', action: 'Find location' }, - { name: 'Record Audio', value: 'record_audio', action: 'Record audio' }, - { name: 'Record Video', value: 'record_video', action: 'Record video' }, - { name: 'Record Video Note', value: 'record_video_note', action: 'Record video note' }, - { name: 'Typing', value: 'typing', action: 'Typing a message' }, - { name: 'Upload Audio', value: 'upload_audio', action: 'Upload audio' }, - { name: 'Upload Document', value: 'upload_document', action: 'Upload document' }, - { name: 'Upload Photo', value: 'upload_photo', action: 'Upload photo' }, - { name: 'Upload Video', value: 'upload_video', action: 'Upload video' }, - { name: 'Upload Video Note', value: 'upload_video_note', action: 'Upload video note' }, - ], - default: 'typing', - description: - 'Type of action to broadcast. Choose one, depending on what the user is about to receive. The status is set for 5 seconds or less (when a message arrives from your bot).', - }, - { - displayName: 'Document', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendDocument'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Document to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Latitude', - name: 'latitude', - type: 'number', - default: 0, - typeOptions: { numberPrecision: 10, minValue: -90, maxValue: 90 }, - displayOptions: { show: { operation: ['sendLocation'], resource: ['message'] } }, - description: 'Location latitude', - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'number', - typeOptions: { numberPrecision: 10, minValue: -180, maxValue: 180 }, - default: 0, - displayOptions: { show: { operation: ['sendLocation'], resource: ['message'] } }, - description: 'Location longitude', - }, - { - displayName: 'Media', - name: 'media', - type: 'fixedCollection', - displayOptions: { show: { operation: ['sendMediaGroup'], resource: ['message'] } }, - description: 'The media to add', - placeholder: 'Add Media', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Media', - name: 'media', - values: [ - { - displayName: 'Type', - name: 'type', - type: 'options', - options: [ - { name: 'Photo', value: 'photo' }, - { name: 'Video', value: 'video' }, - ], - default: 'photo', - description: 'The type of the media to add', - }, - { - displayName: 'Media File', - name: 'media', - type: 'string', - default: '', - description: - 'Media to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or pass an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Caption', - name: 'caption', - type: 'string', - default: '', - description: 'Caption text to set, 0-1024 characters', - }, - { - displayName: 'Parse Mode', - name: 'parse_mode', - type: 'options', - options: [ - { name: 'Markdown (Legacy)', value: 'Markdown' }, - { name: 'MarkdownV2', value: 'MarkdownV2' }, - { name: 'HTML', value: 'HTML' }, - ], - default: 'HTML', - description: 'How to parse the text', - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - required: true, - default: '', - displayOptions: { - show: { operation: ['editMessageText', 'sendMessage'], resource: ['message'] }, - }, - description: 'Text of the message to be sent', - }, - { - displayName: 'Photo', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendPhoto'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Photo to send. Pass a file_id to send a photo that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a photo from the Internet.', - }, - { - displayName: 'Sticker', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendSticker'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Sticker to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a .webp file from the Internet.', - }, - { - displayName: 'Video', - name: 'file', - type: 'string', - default: '', - displayOptions: { - show: { operation: ['sendVideo'], resource: ['message'], binaryData: [false] }, - }, - description: - 'Video file to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), an HTTP URL for Telegram to get a file from the Internet.', - }, - { - displayName: 'Reply Markup', - name: 'replyMarkup', - displayOptions: { - show: { - operation: [ - 'sendAnimation', - 'sendDocument', - 'sendMessage', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - 'sendAudio', - 'sendLocation', - ], - resource: ['message'], - }, - }, - type: 'options', - options: [ - { name: 'Force Reply', value: 'forceReply' }, - { name: 'Inline Keyboard', value: 'inlineKeyboard' }, - { name: 'None', value: 'none' }, - { name: 'Reply Keyboard', value: 'replyKeyboard' }, - { name: 'Reply Keyboard Remove', value: 'replyKeyboardRemove' }, - ], - default: 'none', - description: 'Additional interface options', - }, - { - displayName: 'Force Reply', - name: 'forceReply', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { replyMarkup: ['forceReply'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Force Reply', - name: 'force_reply', - type: 'boolean', - default: false, - description: - 'Whether to show reply interface to the user, as if they manually selected the bot‘s message and tapped ’Reply', - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to force reply from specific users only', - }, - ], - }, - { - displayName: 'Inline Keyboard', - name: 'inlineKeyboard', - placeholder: 'Add Keyboard Row', - description: 'Adds an inline keyboard that appears right next to the message it belongs to', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { replyMarkup: ['inlineKeyboard'], resource: ['message'] } }, - default: {}, - options: [ - { - displayName: 'Rows', - name: 'rows', - values: [ - { - displayName: 'Row', - name: 'row', - type: 'fixedCollection', - description: 'The value to set', - placeholder: 'Add Button', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Buttons', - name: 'buttons', - values: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: 'Label text on the button', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Callback Data', - name: 'callback_data', - type: 'string', - default: '', - description: - 'Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes', - }, - { - displayName: 'Pay', - name: 'pay', - type: 'boolean', - default: false, - description: 'Whether to send a Pay button', - }, - { - displayName: 'Switch Inline Query Current Chat', - name: 'switch_inline_query_current_chat', - type: 'string', - default: '', - description: - "If set, pressing the button will insert the bot‘s username and the specified inline query in the current chat's input field.Can be empty, in which case only the bot’s username will be inserted", - }, - { - displayName: 'Switch Inline Query', - name: 'switch_inline_query', - type: 'string', - default: '', - description: - 'If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot‘s username and the specified inline query in the input field. Can be empty, in which case just the bot’s username will be inserted.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: 'HTTP or tg:// URL to be opened when button is pressed', - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Reply Keyboard', - name: 'replyKeyboard', - placeholder: 'Add Reply Keyboard Row', - description: 'Adds a custom keyboard with reply options', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { replyMarkup: ['replyKeyboard'] } }, - default: {}, - options: [ - { - displayName: 'Rows', - name: 'rows', - values: [ - { - displayName: 'Row', - name: 'row', - type: 'fixedCollection', - description: 'The value to set', - placeholder: 'Add Button', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - displayName: 'Buttons', - name: 'buttons', - values: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - description: - 'Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Request Contact', - name: 'request_contact', - type: 'boolean', - default: false, - description: - "Whether the user's phone number will be sent as a contact when the button is pressed.Available in private chats only", - }, - { - displayName: 'Request Location', - name: 'request_location', - type: 'boolean', - default: false, - description: "Whether the user's request_location", - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - displayName: 'Reply Keyboard Options', - name: 'replyKeyboardOptions', - type: 'collection', - placeholder: 'Add Option', - displayOptions: { show: { replyMarkup: ['replyKeyboard'] } }, - default: {}, - options: [ - { - displayName: 'Resize Keyboard', - name: 'resize_keyboard', - type: 'boolean', - default: false, - description: - 'Whether to request clients to resize the keyboard vertically for optimal fit', - }, - { - displayName: 'One Time Keyboard', - name: 'one_time_keyboard', - type: 'boolean', - default: false, - description: "Whether to request clients to hide the keyboard as soon as it's been used", - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to show the keyboard to specific users only', - }, - ], - }, - { - displayName: 'Reply Keyboard Remove', - name: 'replyKeyboardRemove', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { show: { replyMarkup: ['replyKeyboardRemove'] } }, - default: {}, - options: [ - { - displayName: 'Remove Keyboard', - name: 'remove_keyboard', - type: 'boolean', - default: false, - description: 'Whether to request clients to remove the custom keyboard', - }, - { - displayName: 'Selective', - name: 'selective', - type: 'boolean', - default: false, - description: 'Whether to force reply from specific users only', - }, - ], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'editMessageText', - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendLocation', - 'sendMessage', - 'sendMediaGroup', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - ], - resource: ['message'], - }, - }, - default: {}, - options: [ - { - displayName: 'Append n8n Attribution', - name: 'appendAttribution', - type: 'boolean', - default: true, - description: - 'Whether to include the phrase “This message was sent automatically with n8n” to the end of the message', - displayOptions: { show: { '/operation': ['sendMessage'] } }, - }, - { - displayName: 'Caption', - name: 'caption', - type: 'string', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - ], - }, - }, - default: '', - description: 'Caption text to set, 0-1024 characters', - }, - { - displayName: 'Disable Notification', - name: 'disable_notification', - type: 'boolean', - default: false, - displayOptions: { hide: { '/operation': ['editMessageText'] } }, - description: - 'Whether to send the message silently. Users will receive a notification with no sound.', - }, - { - displayName: 'Disable WebPage Preview', - name: 'disable_web_page_preview', - type: 'boolean', - displayOptions: { show: { '/operation': ['editMessageText', 'sendMessage'] } }, - default: false, - description: 'Whether to disable link previews for links in this message', - }, - { - displayName: 'Duration', - name: 'duration', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendAudio', 'sendVideo'] } }, - default: 0, - description: 'Duration of clip in seconds', - }, - { - displayName: 'File Name', - name: 'fileName', - type: 'string', - default: '', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendDocument', - 'sendPhoto', - 'sendVideo', - 'sendSticker', - ], - '/resource': ['message'], - '/binaryData': [true], - }, - }, - placeholder: 'image.jpeg', - }, - { - displayName: 'Height', - name: 'height', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendVideo'] } }, - default: 0, - description: 'Height of the video', - }, - { - displayName: 'Parse Mode', - name: 'parse_mode', - type: 'options', - options: [ - { name: 'Markdown (Legacy)', value: 'Markdown' }, - { name: 'MarkdownV2', value: 'MarkdownV2' }, - { name: 'HTML', value: 'HTML' }, - ], - displayOptions: { - show: { - '/operation': [ - 'editMessageText', - 'sendAnimation', - 'sendAudio', - 'sendMessage', - 'sendPhoto', - 'sendVideo', - 'sendDocument', - ], - }, - }, - default: 'HTML', - description: 'How to parse the text', - }, - { - displayName: 'Performer', - name: 'performer', - type: 'string', - displayOptions: { show: { '/operation': ['sendAudio'] } }, - default: '', - description: 'Name of the performer', - }, - { - displayName: 'Reply To Message ID', - name: 'reply_to_message_id', - type: 'number', - displayOptions: { hide: { '/operation': ['editMessageText'] } }, - default: 0, - description: 'If the message is a reply, ID of the original message', - }, - { - displayName: 'Message Thread ID', - name: 'message_thread_id', - type: 'number', - displayOptions: { - show: { - '/operation': [ - 'sendAnimation', - 'sendAudio', - 'sendChatAction', - 'sendDocument', - 'sendLocation', - 'sendMediaGroup', - 'sendMessage', - 'sendPhoto', - 'sendSticker', - 'sendVideo', - ], - }, - }, - default: 0, - description: 'The unique identifier of the forum topic', - }, - { - displayName: 'Title', - name: 'title', - type: 'string', - displayOptions: { show: { '/operation': ['sendAudio'] } }, - default: '', - description: 'Title of the track', - }, - { - displayName: 'Thumbnail', - name: 'thumb', - type: 'string', - displayOptions: { - show: { '/operation': ['sendAnimation', 'sendAudio', 'sendDocument', 'sendVideo'] }, - }, - default: '', - description: - 'Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail‘s width and height should not exceed 320.', - }, - { - displayName: 'Width', - name: 'width', - type: 'number', - typeOptions: { minValue: 0 }, - displayOptions: { show: { '/operation': ['sendAnimation', 'sendVideo'] } }, - default: 0, - description: 'Width of the video', - }, - ], - }, - ], - codex: { - categories: ['Communication'], - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.telegram/' }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/telegram' }], - }, - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Telegram/telegram.svg', -} satisfies INodeTypeDescription; + credentials: [{ name: 'twitterOAuth1Api' }], + properties: [], +}); -export const nodeTypeShopifyTriggerV1 = { - displayName: 'Shopify Trigger', - name: 'n8n-nodes-base.shopifyTrigger', - group: ['trigger'], +export const nodeTypeReadImap = mock({ + displayName: 'Email Trigger (IMAP)', + name: 'n8n-nodes-base.emailReadImap', version: 1, - subtitle: '={{$parameter["event"]}}', - description: 'Handle Shopify events via webhooks', - defaults: { name: 'Shopify Trigger' }, - inputs: [], - outputs: ['main'], - credentials: [ - { - name: 'shopifyApi', - required: true, - displayOptions: { show: { authentication: ['apiKey'] } }, - }, - { - name: 'shopifyAccessTokenApi', - required: true, - displayOptions: { show: { authentication: ['accessToken'] } }, - }, - { - name: 'shopifyOAuth2Api', - required: true, - displayOptions: { show: { authentication: ['oAuth2'] } }, - }, - ], - webhooks: [{ name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook' }], - properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { name: 'Access Token', value: 'accessToken' }, - { name: 'OAuth2', value: 'oAuth2' }, - { name: 'API Key', value: 'apiKey' }, - ], - default: 'apiKey', - }, - { - displayName: 'Trigger On', - name: 'topic', - type: 'options', - default: '', - options: [ - { name: 'App Uninstalled', value: 'app/uninstalled' }, - { name: 'Cart Created', value: 'carts/create' }, - { name: 'Cart Updated', value: 'carts/update' }, - { name: 'Checkout Created', value: 'checkouts/create' }, - { name: 'Checkout Delete', value: 'checkouts/delete' }, - { name: 'Checkout Update', value: 'checkouts/update' }, - { name: 'Collection Created', value: 'collections/create' }, - { name: 'Collection Deleted', value: 'collections/delete' }, - { name: 'Collection Listings Added', value: 'collection_listings/add' }, - { name: 'Collection Listings Removed', value: 'collection_listings/remove' }, - { name: 'Collection Listings Updated', value: 'collection_listings/update' }, - { name: 'Collection Updated', value: 'collections/update' }, - { name: 'Customer Created', value: 'customers/create' }, - { name: 'Customer Deleted', value: 'customers/delete' }, - { name: 'Customer Disabled', value: 'customers/disable' }, - { name: 'Customer Enabled', value: 'customers/enable' }, - { name: 'Customer Groups Created', value: 'customer_groups/create' }, - { name: 'Customer Groups Deleted', value: 'customer_groups/delete' }, - { name: 'Customer Groups Updated', value: 'customer_groups/update' }, - { name: 'Customer Updated', value: 'customers/update' }, - { name: 'Draft Orders Created', value: 'draft_orders/create' }, - { name: 'Draft Orders Deleted', value: 'draft_orders/delete' }, - { name: 'Draft Orders Updated', value: 'draft_orders/update' }, - { name: 'Fulfillment Created', value: 'fulfillments/create' }, - { name: 'Fulfillment Events Created', value: 'fulfillment_events/create' }, - { name: 'Fulfillment Events Deleted', value: 'fulfillment_events/delete' }, - { name: 'Fulfillment Updated', value: 'fulfillments/update' }, - { name: 'Inventory Items Created', value: 'inventory_items/create' }, - { name: 'Inventory Items Deleted', value: 'inventory_items/delete' }, - { name: 'Inventory Items Updated', value: 'inventory_items/update' }, - { name: 'Inventory Levels Connected', value: 'inventory_levels/connect' }, - { name: 'Inventory Levels Disconnected', value: 'inventory_levels/disconnect' }, - { name: 'Inventory Levels Updated', value: 'inventory_levels/update' }, - { name: 'Locale Created', value: 'locales/create' }, - { name: 'Locale Updated', value: 'locales/update' }, - { name: 'Location Created', value: 'locations/create' }, - { name: 'Location Deleted', value: 'locations/delete' }, - { name: 'Location Updated', value: 'locations/update' }, - { name: 'Order Cancelled', value: 'orders/cancelled' }, - { name: 'Order Created', value: 'orders/create' }, - { name: 'Order Fulfilled', value: 'orders/fulfilled' }, - { name: 'Order Paid', value: 'orders/paid' }, - { name: 'Order Partially Fulfilled', value: 'orders/partially_fulfilled' }, - { name: 'Order Transactions Created', value: 'order_transactions/create' }, - { name: 'Order Updated', value: 'orders/updated' }, - { name: 'Orders Deleted', value: 'orders/delete' }, - { name: 'Product Created', value: 'products/create' }, - { name: 'Product Deleted', value: 'products/delete' }, - { name: 'Product Listings Added', value: 'product_listings/add' }, - { name: 'Product Listings Removed', value: 'product_listings/remove' }, - { name: 'Product Listings Updated', value: 'product_listings/update' }, - { name: 'Product Updated', value: 'products/update' }, - { name: 'Refund Created', value: 'refunds/create' }, - { name: 'Shop Updated', value: 'shop/update' }, - { name: 'Tender Transactions Created', value: 'tender_transactions/create' }, - { name: 'Theme Created', value: 'themes/create' }, - { name: 'Theme Deleted', value: 'themes/delete' }, - { name: 'Theme Published', value: 'themes/publish' }, - { name: 'Theme Updated', value: 'themes/update' }, - ], - }, - ], - codex: { - categories: ['Sales'], - resources: { - primaryDocumentation: [ - { - url: 'https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.shopifytrigger/', - }, - ], - credentialDocumentation: [{ url: 'https://docs.n8n.io/credentials/shopify' }], - }, - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Shopify/shopify.svg', -} satisfies INodeTypeDescription; + credentials: [{ name: 'imap' }], + properties: [], +}); + +export const nodeTypeNextCloud = mock({ + displayName: 'Nextcloud', + name: 'n8n-nodes-base.nextCloud', + version: 1, + credentials: [{ name: 'nextCloudApi' }], + properties: [], +}); + +export const nodeTypeTelegram = mock({ + name: 'n8n-nodes-base.telegram', + version: 1, + credentials: [{ name: 'telegramApi', required: true }], + properties: [], +}); -export const nodeTypeHttpRequestV1 = { +export const nodeTypeHttpRequest = mock({ displayName: 'HTTP Request', name: 'n8n-nodes-base.httpRequest', - group: ['output'], - subtitle: '={{$parameter["requestMethod"] + ": " + $parameter["url"]}}', - description: 'Makes an HTTP request and returns the response data', - defaultVersion: 4.1, version: 1, - defaults: { name: 'HTTP Request', color: '#2200DD' }, - inputs: ['main'], - outputs: ['main'], credentials: [ { name: 'httpBasicAuth', - required: true, - displayOptions: { show: { authentication: ['basicAuth'] } }, + displayOptions: { show: { authentication: ['basicAuth'] }, hide: undefined }, }, { name: 'httpDigestAuth', - required: true, - displayOptions: { show: { authentication: ['digestAuth'] } }, + displayOptions: { show: { authentication: ['digestAuth'] }, hide: undefined }, }, { name: 'httpHeaderAuth', - required: true, - displayOptions: { show: { authentication: ['headerAuth'] } }, + displayOptions: { show: { authentication: ['headerAuth'] }, hide: undefined }, }, { name: 'httpQueryAuth', - required: true, - displayOptions: { show: { authentication: ['queryAuth'] } }, + displayOptions: { show: { authentication: ['queryAuth'] }, hide: undefined }, }, { name: 'oAuth1Api', - required: true, - displayOptions: { show: { authentication: ['oAuth1'] } }, + displayOptions: { show: { authentication: ['oAuth1'] }, hide: undefined }, }, { name: 'oAuth2Api', - required: true, - displayOptions: { show: { authentication: ['oAuth2'] } }, + displayOptions: { show: { authentication: ['oAuth2'] }, hide: undefined }, }, ], properties: [ @@ -4365,2228 +77,6 @@ export const nodeTypeHttpRequestV1 = { { name: 'Query Auth', value: 'queryAuth' }, ], default: 'none', - description: 'The way to authenticate', - }, - { - displayName: 'Request Method', - name: 'requestMethod', - type: 'options', - options: [ - { name: 'DELETE', value: 'DELETE' }, - { name: 'GET', value: 'GET' }, - { name: 'HEAD', value: 'HEAD' }, - { name: 'OPTIONS', value: 'OPTIONS' }, - { name: 'PATCH', value: 'PATCH' }, - { name: 'POST', value: 'POST' }, - { name: 'PUT', value: 'PUT' }, - ], - default: 'GET', - description: 'The request method to use', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - placeholder: 'http://example.com/index.html', - description: 'The URL to make the request to', - required: true, - }, - { - displayName: 'Ignore SSL Issues', - name: 'allowUnauthorizedCerts', - type: 'boolean', - default: false, - description: - 'Whether to download the response even if SSL certificate validation is not possible', - }, - { - displayName: 'Response Format', - name: 'responseFormat', - type: 'options', - options: [ - { name: 'File', value: 'file' }, - { name: 'JSON', value: 'json' }, - { name: 'String', value: 'string' }, - ], - default: 'json', - description: 'The format in which the data gets returned from the URL', - }, - { - displayName: 'Property Name', - name: 'dataPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { show: { responseFormat: ['string'] } }, - description: 'Name of the property to which to write the response data', - }, - { - displayName: 'Binary Property', - name: 'dataPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { show: { responseFormat: ['file'] } }, - description: 'Name of the binary property to which to write the data of the read file', - }, - { - displayName: 'JSON/RAW Parameters', - name: 'jsonParameters', - type: 'boolean', - default: false, - description: - 'Whether the query and/or body parameter should be set via the value-key pair UI or JSON/RAW', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Batch Interval', - name: 'batchInterval', - type: 'number', - typeOptions: { minValue: 0 }, - default: 1000, - description: 'Time (in milliseconds) between each batch of requests. 0 for disabled.', - }, - { - displayName: 'Batch Size', - name: 'batchSize', - type: 'number', - typeOptions: { minValue: -1 }, - default: 50, - description: - 'Input will be split in batches to throttle requests. -1 for disabled. 0 will be treated as 1.', - }, - { - displayName: 'Body Content Type', - name: 'bodyContentType', - type: 'options', - displayOptions: { show: { '/requestMethod': ['PATCH', 'POST', 'PUT'] } }, - options: [ - { name: 'JSON', value: 'json' }, - { name: 'RAW/Custom', value: 'raw' }, - { name: 'Form-Data Multipart', value: 'multipart-form-data' }, - { name: 'Form Urlencoded', value: 'form-urlencoded' }, - ], - default: 'json', - description: 'Content-Type to use to send body parameters', - }, - { - displayName: 'Full Response', - name: 'fullResponse', - type: 'boolean', - default: false, - description: 'Whether to return the full response data instead of only the body', - }, - { - displayName: 'Follow All Redirects', - name: 'followAllRedirects', - type: 'boolean', - default: false, - description: 'Whether to follow non-GET HTTP 3xx redirects', - }, - { - displayName: 'Follow GET Redirect', - name: 'followRedirect', - type: 'boolean', - default: true, - description: 'Whether to follow GET HTTP 3xx redirects', - }, - { - displayName: 'Ignore Response Code', - name: 'ignoreResponseCode', - type: 'boolean', - default: false, - description: 'Whether to succeeds also when status code is not 2xx', - }, - { - displayName: 'MIME Type', - name: 'bodyContentCustomMimeType', - type: 'string', - default: '', - placeholder: 'text/xml', - description: 'Specify the mime type for raw/custom body type', - displayOptions: { show: { '/requestMethod': ['PATCH', 'POST', 'PUT'] } }, - }, - { - displayName: 'Proxy', - name: 'proxy', - type: 'string', - default: '', - placeholder: 'http://myproxy:3128', - description: 'HTTP proxy to use', - }, - { - displayName: 'Split Into Items', - name: 'splitIntoItems', - type: 'boolean', - default: false, - description: 'Whether to output each element of an array as own item', - displayOptions: { show: { '/responseFormat': ['json'] } }, - }, - { - displayName: 'Timeout', - name: 'timeout', - type: 'number', - typeOptions: { minValue: 1 }, - default: 10000, - description: - 'Time in ms to wait for the server to send response headers (and start the response body) before aborting the request', - }, - { - displayName: 'Use Querystring', - name: 'useQueryString', - type: 'boolean', - default: false, - description: - 'Whether you need arrays to be serialized as foo=bar&foo=baz instead of the default foo[0]=bar&foo[1]=baz', - }, - ], - }, - { - displayName: 'Send Binary Data', - name: 'sendBinaryData', - type: 'boolean', - displayOptions: { - show: { jsonParameters: [true], requestMethod: ['PATCH', 'POST', 'PUT'] }, - }, - default: false, - description: 'Whether binary data should be send as body', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - required: true, - default: 'data', - displayOptions: { - hide: { sendBinaryData: [false] }, - show: { jsonParameters: [true], requestMethod: ['PATCH', 'POST', 'PUT'] }, - }, - description: - 'Name of the binary property which contains the data for the file to be uploaded. For Form-Data Multipart, they can be provided in the format: "sendKey1:binaryProperty1,sendKey2:binaryProperty2', - }, - { - displayName: 'Body Parameters', - name: 'bodyParametersJson', - type: 'json', - displayOptions: { - hide: { sendBinaryData: [true] }, - show: { jsonParameters: [true], requestMethod: ['PATCH', 'POST', 'PUT', 'DELETE'] }, - }, - default: '', - description: 'Body parameters as JSON or RAW', - }, - { - displayName: 'Body Parameters', - name: 'bodyParametersUi', - placeholder: 'Add Parameter', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { - show: { jsonParameters: [false], requestMethod: ['PATCH', 'POST', 'PUT', 'DELETE'] }, - }, - description: 'The body parameter to send', - default: {}, - options: [ - { - name: 'parameter', - displayName: 'Parameter', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the parameter', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the parameter', - }, - ], - }, - ], - }, - { - displayName: 'Headers', - name: 'headerParametersJson', - type: 'json', - displayOptions: { show: { jsonParameters: [true] } }, - default: '', - description: 'Header parameters as JSON or RAW', - }, - { - displayName: 'Headers', - name: 'headerParametersUi', - placeholder: 'Add Header', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { jsonParameters: [false] } }, - description: 'The headers to send', - default: {}, - options: [ - { - name: 'parameter', - displayName: 'Header', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the header', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value to set for the header', - }, - ], - }, - ], - }, - { - displayName: 'Query Parameters', - name: 'queryParametersJson', - type: 'json', - displayOptions: { show: { jsonParameters: [true] } }, - default: '', - description: 'Query parameters as JSON (flat object)', - }, - { - displayName: 'Query Parameters', - name: 'queryParametersUi', - placeholder: 'Add Parameter', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - displayOptions: { show: { jsonParameters: [false] } }, - description: 'The query parameter to send', - default: {}, - options: [ - { - name: 'parameter', - displayName: 'Parameter', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the parameter', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the parameter', - }, - ], - }, - ], - }, - { - displayName: - "You can view the raw requests this node makes in your browser's developer console", - name: 'infoMessage', - type: 'notice', - default: '', - }, - ], - iconUrl: 'icons/n8n-nodes-base/dist/nodes/HttpRequest/httprequest.svg', - codex: { - categories: ['Development', 'Core Nodes'], - subcategories: { 'Core Nodes': ['Helpers'] }, - resources: { - primaryDocumentation: [ - { - url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/', - }, - ], - }, - alias: ['API', 'Request', 'URL', 'Build', 'cURL'], - }, -} satisfies INodeTypeDescription; - -export const nodeTypeRespondToWebhookV1 = { - displayName: 'Respond to Webhook', - name: 'n8n-nodes-base.respondToWebhook', - group: ['transform'], - version: 1, - description: 'Returns data for Webhook', - defaults: { name: 'Respond to Webhook' }, - inputs: ['main'], - outputs: ['main'], - credentials: [], - properties: [ - { - displayName: - 'Verify that the "Webhook" node\'s "Respond" parameter is set to "Using Respond to Webhook Node". More details', - name: 'generalNotice', - type: 'notice', - default: '', - }, - { - displayName: 'Respond With', - name: 'respondWith', - type: 'options', - options: [ - { - name: 'All Incoming Items', - value: 'allIncomingItems', - description: 'Respond with all input JSON items', - }, - { - name: 'Binary File', - value: 'binary', - description: 'Respond with incoming file binary data', - }, - { - name: 'First Incoming Item', - value: 'firstIncomingItem', - description: 'Respond with the first input JSON item', - }, - { name: 'JSON', value: 'json', description: 'Respond with a custom JSON body' }, - { name: 'No Data', value: 'noData', description: 'Respond with an empty body' }, - { - name: 'Redirect', - value: 'redirect', - description: 'Respond with a redirect to a given URL', - }, - { name: 'Text', value: 'text', description: 'Respond with a simple text message body' }, - ], - default: 'firstIncomingItem', - description: 'The data that should be returned', - }, - { - displayName: - 'When using expressions, note that this node will only run for the first item in the input data', - name: 'webhookNotice', - type: 'notice', - displayOptions: { show: { respondWith: ['json', 'text'] } }, - default: '', - }, - { - displayName: 'Redirect URL', - name: 'redirectURL', - type: 'string', - required: true, - displayOptions: { show: { respondWith: ['redirect'] } }, - default: '', - placeholder: 'e.g. http://www.n8n.io', - description: 'The URL to redirect to', - validateType: 'url', - }, - { - displayName: 'Response Body', - name: 'responseBody', - type: 'json', - displayOptions: { show: { respondWith: ['json'] } }, - default: '{\n "myField": "value"\n}', - typeOptions: { rows: 4 }, - description: 'The HTTP response JSON data', - }, - { - displayName: 'Response Body', - name: 'responseBody', - type: 'string', - displayOptions: { show: { respondWith: ['text'] } }, - typeOptions: { rows: 2 }, - default: '', - placeholder: 'e.g. Workflow completed', - description: 'The HTTP response text data', - }, - { - displayName: 'Response Data Source', - name: 'responseDataSource', - type: 'options', - displayOptions: { show: { respondWith: ['binary'] } }, - options: [ - { - name: 'Choose Automatically From Input', - value: 'automatically', - description: 'Use if input data will contain a single piece of binary data', - }, - { - name: 'Specify Myself', - value: 'set', - description: 'Enter the name of the input field the binary data will be in', - }, - ], - default: 'automatically', - }, - { - displayName: 'Input Field Name', - name: 'inputFieldName', - type: 'string', - required: true, - default: 'data', - displayOptions: { show: { respondWith: ['binary'], responseDataSource: ['set'] } }, - description: 'The name of the node input field with the binary data', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Response Code', - name: 'responseCode', - type: 'number', - typeOptions: { minValue: 100, maxValue: 599 }, - default: 200, - description: 'The HTTP response code to return. Defaults to 200.', - }, - { - displayName: 'Response Headers', - name: 'responseHeaders', - placeholder: 'Add Response Header', - description: 'Add headers to the webhook response', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - name: 'entries', - displayName: 'Entries', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the header', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the header', - }, - ], - }, - ], - }, - { - displayName: 'Put Response in Field', - name: 'responseKey', - type: 'string', - displayOptions: { show: { '/respondWith': ['allIncomingItems', 'firstIncomingItem'] } }, - default: '', - description: 'The name of the response field to put all items in', - placeholder: 'e.g. data', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes', 'Utility'], - subcategories: { 'Core Nodes': ['Helpers'] }, - resources: { - primaryDocumentation: [ - { - url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.respondtowebhook/', - }, - ], - }, - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/RespondToWebhook/webhook.svg', -} satisfies INodeTypeDescription; - -export const nodeTypeWebhookV1 = { - displayName: 'Webhook', - name: 'n8n-nodes-base.webhook', - group: ['trigger'], - version: [1, 1.1], - description: 'Starts the workflow when a webhook is called', - eventTriggerDescription: 'Waiting for you to call the Test URL', - activationMessage: 'You can now make calls to your production webhook URL.', - defaults: { name: 'Webhook' }, - supportsCORS: true, - triggerPanel: { - header: '', - executionsHelp: { - inactive: - 'Webhooks have two modes: test and production.

Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.

Use production mode to run your workflow automatically.
Activate the workflow, then make requests to the production URL. These executions will show up in the executions list, but not in the editor.', - active: - 'Webhooks have two modes: test and production.

Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.

Use production mode to run your workflow automatically. Since the workflow is activated, you can make requests to the production URL. These executions will show up in the executions list, but not in the editor.', - }, - activationHint: - 'Once you’ve finished building your workflow, run it without having to click this button by using the production webhook URL.', - }, - inputs: [], - outputs: ['main'], - credentials: [ - { - name: 'httpBasicAuth', - required: true, - displayOptions: { show: { authentication: ['basicAuth'] } }, - }, - { - name: 'httpHeaderAuth', - required: true, - displayOptions: { show: { authentication: ['headerAuth'] } }, - }, - ], - webhooks: [ - { - name: 'default', - httpMethod: '={{$parameter["httpMethod"] || "GET"}}', - isFullPath: true, - responseCode: '={{$parameter["responseCode"]}}', - responseMode: '={{$parameter["responseMode"]}}', - responseData: - '={{$parameter["responseData"] || ($parameter.options.noResponseBody ? "noData" : undefined) }}', - responseBinaryPropertyName: '={{$parameter["responseBinaryPropertyName"]}}', - responseContentType: '={{$parameter["options"]["responseContentType"]}}', - responsePropertyName: '={{$parameter["options"]["responsePropertyName"]}}', - responseHeaders: '={{$parameter["options"]["responseHeaders"]}}', - path: '={{$parameter["path"]}}', - }, - ], - properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { name: 'Basic Auth', value: 'basicAuth' }, - { name: 'Header Auth', value: 'headerAuth' }, - { name: 'None', value: 'none' }, - ], - default: 'none', - description: 'The way to authenticate', - }, - { - displayName: 'HTTP Method', - name: 'httpMethod', - type: 'options', - options: [ - { name: 'DELETE', value: 'DELETE' }, - { name: 'GET', value: 'GET' }, - { name: 'HEAD', value: 'HEAD' }, - { name: 'PATCH', value: 'PATCH' }, - { name: 'POST', value: 'POST' }, - { name: 'PUT', value: 'PUT' }, - ], - default: 'GET', - description: 'The HTTP method to listen to', - }, - { - displayName: 'Path', - name: 'path', - type: 'string', - default: '', - placeholder: 'webhook', - required: true, - description: 'The path to listen to', - }, - { - displayName: 'Respond', - name: 'responseMode', - type: 'options', - options: [ - { - name: 'Immediately', - value: 'onReceived', - description: 'As soon as this node executes', - }, - { - name: 'When Last Node Finishes', - value: 'lastNode', - description: 'Returns data of the last-executed node', - }, - { - name: "Using 'Respond to Webhook' Node", - value: 'responseNode', - description: 'Response defined in that node', - }, - ], - default: 'onReceived', - description: 'When and how to respond to the webhook', - }, - { - displayName: - 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details', - name: 'webhookNotice', - type: 'notice', - displayOptions: { show: { responseMode: ['responseNode'] } }, - default: '', - }, - { - displayName: 'Response Code', - name: 'responseCode', - type: 'number', - displayOptions: { hide: { responseMode: ['responseNode'] } }, - typeOptions: { minValue: 100, maxValue: 599 }, - default: 200, - description: 'The HTTP Response code to return', - }, - { - displayName: 'Response Data', - name: 'responseData', - type: 'options', - displayOptions: { show: { responseMode: ['lastNode'] } }, - options: [ - { - name: 'All Entries', - value: 'allEntries', - description: 'Returns all the entries of the last node. Always returns an array.', - }, - { - name: 'First Entry JSON', - value: 'firstEntryJson', - description: - 'Returns the JSON data of the first entry of the last node. Always returns a JSON object.', - }, - { - name: 'First Entry Binary', - value: 'firstEntryBinary', - description: - 'Returns the binary data of the first entry of the last node. Always returns a binary file.', - }, - { name: 'No Response Body', value: 'noData', description: 'Returns without a body' }, - ], - default: 'firstEntryJson', - description: - 'What data should be returned. If it should return all items as an array or only the first item as object.', - }, - { - displayName: 'Property Name', - name: 'responseBinaryPropertyName', - type: 'string', - required: true, - default: 'data', - displayOptions: { show: { responseData: ['firstEntryBinary'] } }, - description: 'Name of the binary property to return', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Binary Data', - name: 'binaryData', - type: 'boolean', - displayOptions: { show: { '/httpMethod': ['PATCH', 'PUT', 'POST'], '@version': [1] } }, - default: false, - description: 'Whether the webhook will receive binary data', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - displayOptions: { show: { binaryData: [true], '@version': [1] } }, - description: - 'Name of the binary property to write the data of the received file to. If the data gets received via "Form-Data Multipart" it will be the prefix and a number starting with 0 will be attached to it.', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - displayOptions: { hide: { '@version': [1] } }, - description: - 'Name of the binary property to write the data of the received file to, only relevant if binary data is received', - }, - { - displayName: 'Ignore Bots', - name: 'ignoreBots', - type: 'boolean', - default: false, - description: 'Whether to ignore requests from bots like link previewers and web crawlers', - }, - { - displayName: 'No Response Body', - name: 'noResponseBody', - type: 'boolean', - default: false, - description: 'Whether to send any body in the response', - displayOptions: { - hide: { rawBody: [true] }, - show: { '/responseMode': ['onReceived'] }, - }, - }, - { - displayName: 'Raw Body', - name: 'rawBody', - type: 'boolean', - displayOptions: { - show: { '@version': [1] }, - hide: { binaryData: [true], noResponseBody: [true] }, - }, - default: false, - description: 'Raw body (binary)', - }, - { - displayName: 'Raw Body', - name: 'rawBody', - type: 'boolean', - displayOptions: { hide: { noResponseBody: [true], '@version': [1] } }, - default: false, - description: 'Whether to return the raw body', - }, - { - displayName: 'Response Data', - name: 'responseData', - type: 'string', - displayOptions: { - show: { '/responseMode': ['onReceived'] }, - hide: { noResponseBody: [true] }, - }, - default: '', - placeholder: 'success', - description: 'Custom response data to send', - }, - { - displayName: 'Response Content-Type', - name: 'responseContentType', - type: 'string', - displayOptions: { - show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] }, - }, - default: '', - placeholder: 'application/xml', - description: - 'Set a custom content-type to return if another one as the "application/json" should be returned', - }, - { - displayName: 'Response Headers', - name: 'responseHeaders', - placeholder: 'Add Response Header', - description: 'Add headers to the webhook response', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - name: 'entries', - displayName: 'Entries', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the header', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the header', - }, - ], - }, - ], - }, - { - displayName: 'Property Name', - name: 'responsePropertyName', - type: 'string', - displayOptions: { - show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] }, - }, - default: 'data', - description: 'Name of the property to return the data of instead of the whole JSON', - }, - { - displayName: 'Allowed Origins (CORS)', - name: 'allowedOrigins', - type: 'string', - default: '*', - description: - 'The origin(s) to allow cross-origin non-preflight requests from in a browser', - }, - ], - }, - ], - codex: { - categories: ['Development', 'Core Nodes'], - subcategories: { 'Core Nodes': ['Helpers'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/' }, - ], - }, - alias: ['HTTP', 'API', 'Build', 'WH'], - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Webhook/webhook.svg', -} satisfies INodeTypeDescription; - -export const nodeTypeWebhookV1_1 = { - displayName: 'Webhook', - name: 'n8n-nodes-base.webhook', - group: ['trigger'], - version: [1, 1.1], - description: 'Starts the workflow when a webhook is called', - eventTriggerDescription: 'Waiting for you to call the Test URL', - activationMessage: 'You can now make calls to your production webhook URL.', - defaults: { name: 'Webhook' }, - supportsCORS: true, - triggerPanel: { - header: '', - executionsHelp: { - inactive: - 'Webhooks have two modes: test and production.

Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.

Use production mode to run your workflow automatically. Activate the workflow, then make requests to the production URL. These executions will show up in the executions list, but not in the editor.', - active: - 'Webhooks have two modes: test and production.

Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.

Use production mode to run your workflow automatically. Since the workflow is activated, you can make requests to the production URL. These executions will show up in the executions list, but not in the editor.', - }, - activationHint: - 'Once you’ve finished building your workflow, run it without having to click this button by using the production webhook URL.', - }, - inputs: [], - outputs: ['main'], - credentials: [ - { - name: 'httpBasicAuth', - required: true, - displayOptions: { show: { authentication: ['basicAuth'] } }, - }, - { - name: 'httpHeaderAuth', - required: true, - displayOptions: { show: { authentication: ['headerAuth'] } }, - }, - ], - webhooks: [ - { - name: 'default', - httpMethod: '={{$parameter["httpMethod"] || "GET"}}', - isFullPath: true, - responseCode: '={{$parameter["responseCode"]}}', - responseMode: '={{$parameter["responseMode"]}}', - responseData: - '={{$parameter["responseData"] || ($parameter.options.noResponseBody ? "noData" : undefined) }}', - responseBinaryPropertyName: '={{$parameter["responseBinaryPropertyName"]}}', - responseContentType: '={{$parameter["options"]["responseContentType"]}}', - responsePropertyName: '={{$parameter["options"]["responsePropertyName"]}}', - responseHeaders: '={{$parameter["options"]["responseHeaders"]}}', - path: '={{$parameter["path"]}}', - }, - ], - properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { name: 'Basic Auth', value: 'basicAuth' }, - { name: 'Header Auth', value: 'headerAuth' }, - { name: 'None', value: 'none' }, - ], - default: 'none', - description: 'The way to authenticate', - }, - { - displayName: 'HTTP Method', - name: 'httpMethod', - type: 'options', - options: [ - { name: 'DELETE', value: 'DELETE' }, - { name: 'GET', value: 'GET' }, - { name: 'HEAD', value: 'HEAD' }, - { name: 'PATCH', value: 'PATCH' }, - { name: 'POST', value: 'POST' }, - { name: 'PUT', value: 'PUT' }, - ], - default: 'GET', - description: 'The HTTP method to listen to', - }, - { - displayName: 'Path', - name: 'path', - type: 'string', - default: '', - placeholder: 'webhook', - required: true, - description: 'The path to listen to', - }, - { - displayName: 'Respond', - name: 'responseMode', - type: 'options', - options: [ - { - name: 'Immediately', - value: 'onReceived', - description: 'As soon as this node executes', - }, - { - name: 'When Last Node Finishes', - value: 'lastNode', - description: 'Returns data of the last-executed node', - }, - { - name: "Using 'Respond to Webhook' Node", - value: 'responseNode', - description: 'Response defined in that node', - }, - ], - default: 'onReceived', - description: 'When and how to respond to the webhook', - }, - { - displayName: - 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details', - name: 'webhookNotice', - type: 'notice', - displayOptions: { show: { responseMode: ['responseNode'] } }, - default: '', - }, - { - displayName: 'Response Code', - name: 'responseCode', - type: 'number', - displayOptions: { hide: { responseMode: ['responseNode'] } }, - typeOptions: { minValue: 100, maxValue: 599 }, - default: 200, - description: 'The HTTP Response code to return', - }, - { - displayName: 'Response Data', - name: 'responseData', - type: 'options', - displayOptions: { show: { responseMode: ['lastNode'] } }, - options: [ - { - name: 'All Entries', - value: 'allEntries', - description: 'Returns all the entries of the last node. Always returns an array.', - }, - { - name: 'First Entry JSON', - value: 'firstEntryJson', - description: - 'Returns the JSON data of the first entry of the last node. Always returns a JSON object.', - }, - { - name: 'First Entry Binary', - value: 'firstEntryBinary', - description: - 'Returns the binary data of the first entry of the last node. Always returns a binary file.', - }, - { name: 'No Response Body', value: 'noData', description: 'Returns without a body' }, - ], - default: 'firstEntryJson', - description: - 'What data should be returned. If it should return all items as an array or only the first item as object.', - }, - { - displayName: 'Property Name', - name: 'responseBinaryPropertyName', - type: 'string', - required: true, - default: 'data', - displayOptions: { show: { responseData: ['firstEntryBinary'] } }, - description: 'Name of the binary property to return', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Binary Data', - name: 'binaryData', - type: 'boolean', - displayOptions: { show: { '/httpMethod': ['PATCH', 'PUT', 'POST'], '@version': [1] } }, - default: false, - description: 'Whether the webhook will receive binary data', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - displayOptions: { show: { binaryData: [true], '@version': [1] } }, - description: - 'Name of the binary property to write the data of the received file to. If the data gets received via "Form-Data Multipart" it will be the prefix and a number starting with 0 will be attached to it.', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - displayOptions: { hide: { '@version': [1] } }, - description: - 'Name of the binary property to write the data of the received file to, only relevant if binary data is received', - }, - { - displayName: 'Ignore Bots', - name: 'ignoreBots', - type: 'boolean', - default: false, - description: 'Whether to ignore requests from bots like link previewers and web crawlers', - }, - { - displayName: 'No Response Body', - name: 'noResponseBody', - type: 'boolean', - default: false, - description: 'Whether to send any body in the response', - displayOptions: { - hide: { rawBody: [true] }, - show: { '/responseMode': ['onReceived'] }, - }, - }, - { - displayName: 'Raw Body', - name: 'rawBody', - type: 'boolean', - displayOptions: { - show: { '@version': [1] }, - hide: { binaryData: [true], noResponseBody: [true] }, - }, - default: false, - description: 'Raw body (binary)', - }, - { - displayName: 'Raw Body', - name: 'rawBody', - type: 'boolean', - displayOptions: { hide: { noResponseBody: [true], '@version': [1] } }, - default: false, - description: 'Whether to return the raw body', - }, - { - displayName: 'Response Data', - name: 'responseData', - type: 'string', - displayOptions: { - show: { '/responseMode': ['onReceived'] }, - hide: { noResponseBody: [true] }, - }, - default: '', - placeholder: 'success', - description: 'Custom response data to send', - }, - { - displayName: 'Response Content-Type', - name: 'responseContentType', - type: 'string', - displayOptions: { - show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] }, - }, - default: '', - placeholder: 'application/xml', - description: - 'Set a custom content-type to return if another one as the "application/json" should be returned', - }, - { - displayName: 'Response Headers', - name: 'responseHeaders', - placeholder: 'Add Response Header', - description: 'Add headers to the webhook response', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - options: [ - { - name: 'entries', - displayName: 'Entries', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the header', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value of the header', - }, - ], - }, - ], - }, - { - displayName: 'Property Name', - name: 'responsePropertyName', - type: 'string', - displayOptions: { - show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] }, - }, - default: 'data', - description: 'Name of the property to return the data of instead of the whole JSON', - }, - { - displayName: 'Allowed Origins (CORS)', - name: 'allowedOrigins', - type: 'string', - default: '*', - description: - 'The origin(s) to allow cross-origin non-preflight requests from in a browser', - }, - ], }, ], - codex: { - categories: ['Development', 'Core Nodes'], - subcategories: { 'Core Nodes': ['Helpers'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/' }, - ], - }, - alias: ['HTTP', 'API', 'Build', 'WH'], - }, - iconUrl: 'icons/n8n-nodes-base/dist/nodes/Webhook/webhook.svg', -} satisfies INodeTypeDescription; - -export const nodeTypesSet = { - '1': { - displayName: 'Set', - name: 'n8n-nodes-base.set', - icon: 'fa:pen', - group: ['input'], - description: 'Sets values on items and optionally remove other values', - defaultVersion: 3.2, - version: [1, 2], - defaults: { name: 'Set', color: '#0000FF' }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Keep Only Set', - name: 'keepOnlySet', - type: 'boolean', - default: false, - description: - 'Whether only the values set on this node should be kept and all others removed', - }, - { - displayName: 'Values to Set', - name: 'values', - placeholder: 'Add Value', - type: 'fixedCollection', - typeOptions: { multipleValues: true, sortable: true }, - description: 'The value to set', - default: {}, - options: [ - { - name: 'boolean', - displayName: 'Boolean', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - requiresDataPath: 'single', - default: 'propertyName', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'boolean', - default: false, - description: 'The boolean value to write in the property', - }, - ], - }, - { - name: 'number', - displayName: 'Number', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - requiresDataPath: 'single', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - default: 0, - description: 'The number value to write in the property', - }, - ], - }, - { - name: 'string', - displayName: 'String', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - requiresDataPath: 'single', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The string value to write in the property', - }, - ], - }, - ], - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - '

By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.

If that is not intended this can be deactivated, it will then set { "a.b": value } instead.

.', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes'], - subcategories: { 'Core Nodes': ['Data Transformation'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, - ], - }, - alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], - }, - }, - '2': { - displayName: 'Set', - name: 'n8n-nodes-base.set', - icon: 'fa:pen', - group: ['input'], - description: 'Sets values on items and optionally remove other values', - defaultVersion: 3.2, - version: [1, 2], - defaults: { name: 'Set', color: '#0000FF' }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Keep Only Set', - name: 'keepOnlySet', - type: 'boolean', - default: false, - description: - 'Whether only the values set on this node should be kept and all others removed', - }, - { - displayName: 'Values to Set', - name: 'values', - placeholder: 'Add Value', - type: 'fixedCollection', - typeOptions: { multipleValues: true, sortable: true }, - description: 'The value to set', - default: {}, - options: [ - { - name: 'boolean', - displayName: 'Boolean', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - requiresDataPath: 'single', - default: 'propertyName', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'boolean', - default: false, - description: 'The boolean value to write in the property', - }, - ], - }, - { - name: 'number', - displayName: 'Number', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - requiresDataPath: 'single', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - default: 0, - description: 'The number value to write in the property', - }, - ], - }, - { - name: 'string', - displayName: 'String', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - requiresDataPath: 'single', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The string value to write in the property', - }, - ], - }, - ], - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - '

By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.

If that is not intended this can be deactivated, it will then set { "a.b": value } instead.

.', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes'], - subcategories: { 'Core Nodes': ['Data Transformation'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, - ], - }, - alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], - }, - }, - '3': { - displayName: 'Edit Fields (Set)', - name: 'n8n-nodes-base.set', - icon: 'fa:pen', - group: ['input'], - description: 'Modify, add, or remove item fields', - defaultVersion: 3.2, - version: [3, 3.1, 3.2], - subtitle: '={{$parameter["mode"]}}', - defaults: { name: 'Edit Fields', color: '#0000FF' }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Manual Mapping', - value: 'manual', - description: 'Edit item fields one by one', - action: 'Edit item fields one by one', - }, - { - name: 'JSON Output', - value: 'raw', - description: 'Customize item output with JSON', - action: 'Customize item output with JSON', - }, - ], - default: 'manual', - }, - { - displayName: 'Duplicate Item', - name: 'duplicateItem', - type: 'boolean', - default: false, - isNodeSetting: true, - }, - { - displayName: 'Duplicate Item Count', - name: 'duplicateCount', - type: 'number', - default: 0, - typeOptions: { minValue: 0 }, - description: - 'How many times the item should be duplicated, mainly used for testing and debugging', - isNodeSetting: true, - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: - 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', - name: 'duplicateWarning', - type: 'notice', - default: '', - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: 'JSON Output', - name: 'jsonOutput', - type: 'json', - typeOptions: { rows: 5 }, - default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', - validateType: 'object', - ignoreValidationDuringExecution: true, - displayOptions: { show: { mode: ['raw'] } }, - }, - { - displayName: 'Fields to Set', - name: 'fields', - placeholder: 'Add Field', - type: 'fixedCollection', - description: 'Edit existing fields or add new ones to modify the output data', - typeOptions: { multipleValues: true, sortable: true }, - default: {}, - options: [ - { - name: 'values', - displayName: 'Values', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'e.g. fieldName', - description: - 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', - requiresDataPath: 'single', - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - description: 'The field value type', - options: [ - { name: 'String', value: 'stringValue' }, - { name: 'Number', value: 'numberValue' }, - { name: 'Boolean', value: 'booleanValue' }, - { name: 'Array', value: 'arrayValue' }, - { name: 'Object', value: 'objectValue' }, - ], - default: 'stringValue', - }, - { - displayName: 'Value', - name: 'stringValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['stringValue'] } }, - validateType: 'string', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'numberValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['numberValue'] } }, - validateType: 'number', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'booleanValue', - type: 'options', - default: 'true', - options: [ - { name: 'True', value: 'true' }, - { name: 'False', value: 'false' }, - ], - displayOptions: { show: { type: ['booleanValue'] } }, - validateType: 'boolean', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'arrayValue', - type: 'string', - default: '', - placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', - displayOptions: { show: { type: ['arrayValue'] } }, - validateType: 'array', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'objectValue', - type: 'json', - default: '={}', - typeOptions: { rows: 2 }, - displayOptions: { show: { type: ['objectValue'] } }, - validateType: 'object', - ignoreValidationDuringExecution: true, - }, - ], - }, - ], - displayOptions: { show: { mode: ['manual'] } }, - }, - { - displayName: 'Include in Output', - name: 'include', - type: 'options', - description: 'How to select the fields you want to include in your output items', - default: 'all', - options: [ - { - name: 'All Input Fields', - value: 'all', - description: 'Also include all unchanged fields from the input', - }, - { - name: 'No Input Fields', - value: 'none', - description: 'Include only the fields specified above', - }, - { - name: 'Selected Input Fields', - value: 'selected', - description: 'Also include the fields listed in the parameter “Fields to Include”', - }, - { - name: 'All Input Fields Except', - value: 'except', - description: 'Exclude the fields listed in the parameter “Fields to Exclude”', - }, - ], - }, - { - displayName: 'Fields to Include', - name: 'includeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToInclude1,fieldToInclude2', - description: - 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['selected'] } }, - }, - { - displayName: 'Fields to Exclude', - name: 'excludeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToExclude1,fieldToExclude2', - description: - 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['except'] } }, - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Include Binary Data', - name: 'includeBinary', - type: 'boolean', - default: true, - description: 'Whether binary data should be included if present in the input item', - }, - { - displayName: 'Ignore Type Conversion Errors', - name: 'ignoreConversionErrors', - type: 'boolean', - default: false, - description: - 'Whether to ignore field type errors and apply a less strict type conversion', - displayOptions: { show: { '/mode': ['manual'] } }, - }, - { - displayName: 'Support Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes'], - subcategories: { 'Core Nodes': ['Data Transformation'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, - ], - }, - alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], - }, - }, - '3.1': { - displayName: 'Edit Fields (Set)', - name: 'n8n-nodes-base.set', - icon: 'fa:pen', - group: ['input'], - description: 'Modify, add, or remove item fields', - defaultVersion: 3.2, - version: [3, 3.1, 3.2], - subtitle: '={{$parameter["mode"]}}', - defaults: { name: 'Edit Fields', color: '#0000FF' }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Manual Mapping', - value: 'manual', - description: 'Edit item fields one by one', - action: 'Edit item fields one by one', - }, - { - name: 'JSON Output', - value: 'raw', - description: 'Customize item output with JSON', - action: 'Customize item output with JSON', - }, - ], - default: 'manual', - }, - { - displayName: 'Duplicate Item', - name: 'duplicateItem', - type: 'boolean', - default: false, - isNodeSetting: true, - }, - { - displayName: 'Duplicate Item Count', - name: 'duplicateCount', - type: 'number', - default: 0, - typeOptions: { minValue: 0 }, - description: - 'How many times the item should be duplicated, mainly used for testing and debugging', - isNodeSetting: true, - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: - 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', - name: 'duplicateWarning', - type: 'notice', - default: '', - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: 'JSON Output', - name: 'jsonOutput', - type: 'json', - typeOptions: { rows: 5 }, - default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', - validateType: 'object', - ignoreValidationDuringExecution: true, - displayOptions: { show: { mode: ['raw'] } }, - }, - { - displayName: 'Fields to Set', - name: 'fields', - placeholder: 'Add Field', - type: 'fixedCollection', - description: 'Edit existing fields or add new ones to modify the output data', - typeOptions: { multipleValues: true, sortable: true }, - default: {}, - options: [ - { - name: 'values', - displayName: 'Values', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'e.g. fieldName', - description: - 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', - requiresDataPath: 'single', - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - description: 'The field value type', - options: [ - { name: 'String', value: 'stringValue' }, - { name: 'Number', value: 'numberValue' }, - { name: 'Boolean', value: 'booleanValue' }, - { name: 'Array', value: 'arrayValue' }, - { name: 'Object', value: 'objectValue' }, - ], - default: 'stringValue', - }, - { - displayName: 'Value', - name: 'stringValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['stringValue'] } }, - validateType: 'string', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'numberValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['numberValue'] } }, - validateType: 'number', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'booleanValue', - type: 'options', - default: 'true', - options: [ - { name: 'True', value: 'true' }, - { name: 'False', value: 'false' }, - ], - displayOptions: { show: { type: ['booleanValue'] } }, - validateType: 'boolean', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'arrayValue', - type: 'string', - default: '', - placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', - displayOptions: { show: { type: ['arrayValue'] } }, - validateType: 'array', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'objectValue', - type: 'json', - default: '={}', - typeOptions: { rows: 2 }, - displayOptions: { show: { type: ['objectValue'] } }, - validateType: 'object', - ignoreValidationDuringExecution: true, - }, - ], - }, - ], - displayOptions: { show: { mode: ['manual'] } }, - }, - { - displayName: 'Include in Output', - name: 'include', - type: 'options', - description: 'How to select the fields you want to include in your output items', - default: 'all', - options: [ - { - name: 'All Input Fields', - value: 'all', - description: 'Also include all unchanged fields from the input', - }, - { - name: 'No Input Fields', - value: 'none', - description: 'Include only the fields specified above', - }, - { - name: 'Selected Input Fields', - value: 'selected', - description: 'Also include the fields listed in the parameter “Fields to Include”', - }, - { - name: 'All Input Fields Except', - value: 'except', - description: 'Exclude the fields listed in the parameter “Fields to Exclude”', - }, - ], - }, - { - displayName: 'Fields to Include', - name: 'includeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToInclude1,fieldToInclude2', - description: - 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['selected'] } }, - }, - { - displayName: 'Fields to Exclude', - name: 'excludeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToExclude1,fieldToExclude2', - description: - 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['except'] } }, - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Include Binary Data', - name: 'includeBinary', - type: 'boolean', - default: true, - description: 'Whether binary data should be included if present in the input item', - }, - { - displayName: 'Ignore Type Conversion Errors', - name: 'ignoreConversionErrors', - type: 'boolean', - default: false, - description: - 'Whether to ignore field type errors and apply a less strict type conversion', - displayOptions: { show: { '/mode': ['manual'] } }, - }, - { - displayName: 'Support Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes'], - subcategories: { 'Core Nodes': ['Data Transformation'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, - ], - }, - alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], - }, - }, - '3.2': { - displayName: 'Edit Fields (Set)', - name: 'n8n-nodes-base.set', - icon: 'fa:pen', - group: ['input'], - description: 'Modify, add, or remove item fields', - defaultVersion: 3.2, - version: [3, 3.1, 3.2], - subtitle: '={{$parameter["mode"]}}', - defaults: { name: 'Edit Fields', color: '#0000FF' }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Manual Mapping', - value: 'manual', - description: 'Edit item fields one by one', - action: 'Edit item fields one by one', - }, - { - name: 'JSON Output', - value: 'raw', - description: 'Customize item output with JSON', - action: 'Customize item output with JSON', - }, - ], - default: 'manual', - }, - { - displayName: 'Duplicate Item', - name: 'duplicateItem', - type: 'boolean', - default: false, - isNodeSetting: true, - }, - { - displayName: 'Duplicate Item Count', - name: 'duplicateCount', - type: 'number', - default: 0, - typeOptions: { minValue: 0 }, - description: - 'How many times the item should be duplicated, mainly used for testing and debugging', - isNodeSetting: true, - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: - 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', - name: 'duplicateWarning', - type: 'notice', - default: '', - displayOptions: { show: { duplicateItem: [true] } }, - }, - { - displayName: 'JSON Output', - name: 'jsonOutput', - type: 'json', - typeOptions: { rows: 5 }, - default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', - validateType: 'object', - ignoreValidationDuringExecution: true, - displayOptions: { show: { mode: ['raw'] } }, - }, - { - displayName: 'Fields to Set', - name: 'fields', - placeholder: 'Add Field', - type: 'fixedCollection', - description: 'Edit existing fields or add new ones to modify the output data', - typeOptions: { multipleValues: true, sortable: true }, - default: {}, - options: [ - { - name: 'values', - displayName: 'Values', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'e.g. fieldName', - description: - 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', - requiresDataPath: 'single', - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - description: 'The field value type', - options: [ - { name: 'String', value: 'stringValue' }, - { name: 'Number', value: 'numberValue' }, - { name: 'Boolean', value: 'booleanValue' }, - { name: 'Array', value: 'arrayValue' }, - { name: 'Object', value: 'objectValue' }, - ], - default: 'stringValue', - }, - { - displayName: 'Value', - name: 'stringValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['stringValue'] } }, - validateType: 'string', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'numberValue', - type: 'string', - default: '', - displayOptions: { show: { type: ['numberValue'] } }, - validateType: 'number', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'booleanValue', - type: 'options', - default: 'true', - options: [ - { name: 'True', value: 'true' }, - { name: 'False', value: 'false' }, - ], - displayOptions: { show: { type: ['booleanValue'] } }, - validateType: 'boolean', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'arrayValue', - type: 'string', - default: '', - placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', - displayOptions: { show: { type: ['arrayValue'] } }, - validateType: 'array', - ignoreValidationDuringExecution: true, - }, - { - displayName: 'Value', - name: 'objectValue', - type: 'json', - default: '={}', - typeOptions: { rows: 2 }, - displayOptions: { show: { type: ['objectValue'] } }, - validateType: 'object', - ignoreValidationDuringExecution: true, - }, - ], - }, - ], - displayOptions: { show: { mode: ['manual'] } }, - }, - { - displayName: 'Include in Output', - name: 'include', - type: 'options', - description: 'How to select the fields you want to include in your output items', - default: 'all', - options: [ - { - name: 'All Input Fields', - value: 'all', - description: 'Also include all unchanged fields from the input', - }, - { - name: 'No Input Fields', - value: 'none', - description: 'Include only the fields specified above', - }, - { - name: 'Selected Input Fields', - value: 'selected', - description: 'Also include the fields listed in the parameter “Fields to Include”', - }, - { - name: 'All Input Fields Except', - value: 'except', - description: 'Exclude the fields listed in the parameter “Fields to Exclude”', - }, - ], - }, - { - displayName: 'Fields to Include', - name: 'includeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToInclude1,fieldToInclude2', - description: - 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['selected'] } }, - }, - { - displayName: 'Fields to Exclude', - name: 'excludeFields', - type: 'string', - default: '', - placeholder: 'e.g. fieldToExclude1,fieldToExclude2', - description: - 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', - requiresDataPath: 'multiple', - displayOptions: { show: { include: ['except'] } }, - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Include Binary Data', - name: 'includeBinary', - type: 'boolean', - default: true, - description: 'Whether binary data should be included if present in the input item', - }, - { - displayName: 'Ignore Type Conversion Errors', - name: 'ignoreConversionErrors', - type: 'boolean', - default: false, - description: - 'Whether to ignore field type errors and apply a less strict type conversion', - displayOptions: { show: { '/mode': ['manual'] } }, - }, - { - displayName: 'Support Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', - }, - ], - }, - ], - codex: { - categories: ['Core Nodes'], - subcategories: { 'Core Nodes': ['Data Transformation'] }, - resources: { - primaryDocumentation: [ - { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, - ], - }, - alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], - }, - }, -} satisfies { [version: string]: INodeTypeDescription }; +}); diff --git a/packages/editor-ui/src/utils/testData/templateTestData.ts b/packages/editor-ui/src/utils/testData/templateTestData.ts deleted file mode 100644 index 255104459e5c7d..00000000000000 --- a/packages/editor-ui/src/utils/testData/templateTestData.ts +++ /dev/null @@ -1,450 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import { faker } from '@faker-js/faker/locale/en'; -import type { ITemplatesWorkflowFull, IWorkflowTemplateNode } from '@/Interface'; -import { NodeConnectionType } from 'n8n-workflow'; - -export const newWorkflowTemplateNode = ({ - type, - ...optionalOpts -}: Pick & - Pick & - Partial): IWorkflowTemplateNode => ({ - type, - name: faker.commerce.productName(), - position: [0, 0], - parameters: {}, - typeVersion: 1, - ...optionalOpts, -}); - -export const fullShopifyTelegramTwitterTemplate = { - full: true, - id: 1205, - name: 'Promote new Shopify products on Twitter and Telegram', - totalViews: 485, - createdAt: '2021-08-24T10:40:50.007Z', - description: - 'This workflow automatically promotes your new Shopify products on Twitter and Telegram. This workflow is also featured in the blog post [*6 e-commerce workflows to power up your Shopify store*](https://n8n.io/blog/no-code-ecommerce-workflow-automations/#promote-your-new-products-on-social-media).\n\n## Prerequisites\n\n- A Shopify account and [credentials](https://docs.n8n.io/integrations/credentials/shopify/)\n- A Twitter account and [credentials](https://docs.n8n.io/integrations/credentials/twitter/)\n- A Telegram account and [credentials](https://docs.n8n.io/integrations/credentials/telegram/) for the channel you want to send messages to.\n\n## Nodes\n\n- [Shopify Trigger node](https://docs.n8n.io/integrations/trigger-nodes/n8n-nodes-base.shopifytrigger/) triggers the workflow when you create a new product in Shopify.\n- [Twitter node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.twitter/) posts a tweet with the text "Hey there, my design is now on a new product! Visit my {shop name} to get this cool {product title} (and check out more {product type})".\n- [Telegram node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.telegram/) posts a message with the same text as above in a Telegram channel.', - workflow: { - nodes: [ - { - id: 'd65f8060-0196-430a-923c-57f838991cc1', - name: 'Twitter', - type: 'n8n-nodes-base.twitter', - position: [720, -220], - parameters: { - text: '=Hey there, my design is now on a new product ✨\nVisit my {{$json["vendor"]}} shop to get this cool{{$json["title"]}} (and check out more {{$json["product_type"]}}) 🛍️', - additionalFields: {}, - }, - credentials: { - twitterOAuth1Api: 'twitter', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991dd3', - name: 'Telegram', - type: 'n8n-nodes-base.telegram', - position: [720, -20], - parameters: { - text: '=Hey there, my design is now on a new product!\nVisit my {{$json["vendor"]}} shop to get this cool{{$json["title"]}} (and check out more {{$json["product_type"]}})', - chatId: '123456', - additionalFields: {}, - }, - credentials: { - telegramApi: 'telegram_habot', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991dd2', - name: 'product created', - type: 'n8n-nodes-base.shopifyTrigger', - position: [540, -110], - webhookId: '2a7e0e50-8f09-4a2b-bf54-a849a6ac4fe0', - parameters: { - topic: 'products/create', - }, - credentials: { - shopifyApi: 'shopify_nodeqa', - }, - typeVersion: 1, - }, - ], - connections: { - 'product created': { - main: [ - [ - { - node: 'Twitter', - type: NodeConnectionType.Main, - index: 0, - }, - { - node: 'Telegram', - type: NodeConnectionType.Main, - index: 0, - }, - ], - ], - }, - }, - }, - workflowInfo: { - nodeCount: 3, - nodeTypes: { - 'n8n-nodes-base.twitter': { - count: 1, - }, - 'n8n-nodes-base.telegram': { - count: 1, - }, - 'n8n-nodes-base.shopifyTrigger': { - count: 1, - }, - }, - }, - user: { - username: 'lorenanda', - }, - nodes: [ - { - id: 49, - icon: 'file:telegram.svg', - name: 'n8n-nodes-base.telegram', - defaults: { - name: 'Telegram', - }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg==', - }, - categories: [ - { - id: 6, - name: 'Communication', - }, - ], - displayName: 'Telegram', - typeVersion: 1, - }, - { - id: 107, - icon: 'file:shopify.svg', - name: 'n8n-nodes-base.shopifyTrigger', - defaults: { - name: 'Shopify Trigger', - }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4=', - }, - categories: [ - { - id: 2, - name: 'Sales', - }, - ], - displayName: 'Shopify Trigger', - typeVersion: 1, - }, - { - id: 325, - icon: 'file:x.svg', - name: 'n8n-nodes-base.twitter', - defaults: { - name: 'X', - }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K', - }, - categories: [ - { - id: 1, - name: 'Marketing & Content', - }, - ], - displayName: 'X (Formerly Twitter)', - typeVersion: 2, - }, - ], - categories: [ - { - id: 2, - name: 'Sales', - }, - { - id: 19, - name: 'Marketing & Growth', - }, - ], - image: [ - { - id: 527, - url: 'https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/89a078b208fe4c6181902608b1cd1332.png', - }, - ], -} satisfies ITemplatesWorkflowFull; - -export const fullSaveEmailAttachmentsToNextCloudTemplate = { - id: 1344, - name: 'Save email attachments to Nextcloud', - totalViews: 649, - createdAt: '2021-11-29T13:59:16.771Z', - description: - 'This workflow will take all emails you put into a certain folder, upload any attachements to Nextcloud, and mark the emails as read (configurable).\n\nAttachements will be saved with automatically generated filenames:\n`2021-01-01_From-Sender-Name_Filename-of-attachement.pdf`\n\nInstructions:\n1. **Allow lodash to be used in n8n** (or rewrite the code...)\n `NODE_FUNCTION_ALLOW_EXTERNAL=lodash` (environment variable)\n2. Import workflow\n3. Set credentials for Email & Nextcloud nodes\n4. Configure to use correct folder / custom filters\n5. Activate\n\nCustom filter examples:\n- Only unread emails:\n `Custom Email Config` = `["UNSEEN"]`\n- Filter emails by \'to\' address:\n `Custom Email Config` = `[["TO", "example+invoices@posteo.de"]]`', - workflow: { - nodes: [ - { - id: 'd65f8060-0196-430a-923c-57f8389911f3', - name: 'IMAP Email', - type: 'n8n-nodes-base.emailReadImap', - position: [240, 420], - parameters: { - format: 'resolved', - mailbox: 'Invoices', - options: { customEmailConfig: '["ALL"]' }, - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991gg2', - name: 'Nextcloud', - type: 'n8n-nodes-base.nextCloud', - position: [940, 420], - parameters: { - path: '=Documents/Invoices/{{$json["date"]}}_{{$json["from"]}}_{{$binary.file.fileName}}', - binaryDataUpload: true, - binaryPropertyName: 'file', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991ddh', - name: 'Map each attachment', - type: 'n8n-nodes-base.function', - position: [620, 420], - parameters: { - functionCode: - "const _ = require('lodash')\n\nconst sanitize = str => _.chain(str)\n .replace(/[^A-Za-z0-9&.-]/g, '-') // sanitise via whitelist of characters\n .replace(/-(?=-)/g, '') // remove repeated dashes - https://regexr.com/6ag8h\n .trim('-') // trim any leading/trailing dashes\n .truncate({\n length: 60,\n omission: '-' // when the string ends with '-', you'll know it was truncated\n })\n .value()\n\nconst result = _.flatMap(items.map(item => {\n //console.log({item})\n\n // Maps each attachment to a separate item\n return _.values(item.binary).map(file => {\n console.log(\"Saving attachement:\", file.fileName, 'from:', ...item.json.from.value)\n \n // sanitize filename but exclude extension\n const filename_parts = file.fileName.split('.')\n const ext = _.slice(filename_parts, filename_parts.length-1)\n const filename_main = _.join(_.dropRight(filename_parts), '.')\n file.fileName = sanitize(filename_main) + '.' + ext\n \n return {\n json: {\n from: sanitize(item.json.from.value[0].name),\n date: sanitize(new Date(item.json.date).toISOString().split(\"T\")[0]) // get date part \"2020-01-01\"\n }, \n binary: { file }\n }\n })\n}))\n\n//console.log(result)\nreturn result", - }, - typeVersion: 1, - }, - ], - connections: { - 'IMAP Email': { - main: [[{ node: 'Map each attachment', type: NodeConnectionType.Main, index: 0 }]], - }, - 'Map each attachment': { - main: [[{ node: 'Nextcloud', type: NodeConnectionType.Main, index: 0 }]], - }, - }, - }, - workflowInfo: { - nodeCount: 3, - nodeTypes: { - 'n8n-nodes-base.function': { count: 1 }, - 'n8n-nodes-base.nextCloud': { count: 1 }, - 'n8n-nodes-base.emailReadImap': { count: 1 }, - }, - }, - user: { username: 'tennox' }, - nodes: [ - { - id: 10, - icon: 'fa:inbox', - name: 'n8n-nodes-base.emailReadImap', - defaults: { name: 'Email Trigger (IMAP)', color: '#44AA22' }, - iconData: { icon: 'inbox', type: 'icon' }, - categories: [ - { id: 6, name: 'Communication' }, - { id: 9, name: 'Core Nodes' }, - ], - displayName: 'Email Trigger (IMAP)', - typeVersion: 2, - }, - { - id: 14, - icon: 'fa:code', - name: 'n8n-nodes-base.function', - defaults: { name: 'Function', color: '#FF9922' }, - iconData: { icon: 'code', type: 'icon' }, - categories: [ - { id: 5, name: 'Development' }, - { id: 9, name: 'Core Nodes' }, - ], - displayName: 'Function', - typeVersion: 1, - }, - { - id: 25, - icon: 'file:nextcloud.svg', - name: 'n8n-nodes-base.nextCloud', - defaults: { name: 'Nextcloud' }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzYgNTEiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI0EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9IkEiIG92ZXJmbG93PSJ2aXNpYmxlIj48cGF0aCBkPSJNMzcuNTMzIDBjLTcuNzcgMC0xNC4zNTUgNS4yNjgtMTYuMzk2IDEyLjM3OS0xLjc3OC0zLjgxOS01LjU5Ny02LjQ1My0xMC4wNzUtNi40NTNDNS4wMDQgNS45MjYgMCAxMC45MzEgMCAxNy4wNTRhMTEuMTYgMTEuMTYgMCAwIDAgMTEuMTI4IDExLjEyOGM0LjQxMiAwIDguMjk3LTIuNjM0IDEwLjA3NS02LjQ1M2ExNi45OSAxNi45OSAwIDAgMCAxNi4zMyAxMi4zNzljNy43MDQgMCAxNC4yODktNS4yMDIgMTYuMzk2LTEyLjI0OCAxLjc3OCAzLjY4NyA1LjU5NyA2LjI1NiA5Ljk0MyA2LjI1NkExMS4xNiAxMS4xNiAwIDAgMCA3NSAxNi45ODljMC02LjEyNC01LjAwNC0xMS4wNjItMTEuMTI4LTExLjA2Mi00LjM0NiAwLTguMTY1IDIuNTY4LTkuOTQzIDYuMjU2QzUxLjgyMiA1LjIwMiA0NS4zMDMgMCAzNy41MzMgMHptMCA2LjUxOWExMC40OCAxMC40OCAwIDAgMSAxMC41MzUgMTAuNTM2QTEwLjQ4IDEwLjQ4IDAgMCAxIDM3LjUzMyAyNy41OWExMC40OCAxMC40OCAwIDAgMS0xMC41MzYtMTAuNTM1QTEwLjQ4IDEwLjQ4IDAgMCAxIDM3LjUzMyA2LjUxOXptLTI2LjQwNSA1LjkyNmE0LjU4IDQuNTggMCAwIDEgNC42MDkgNC42MDkgNC41OCA0LjU4IDAgMCAxLTQuNjA5IDQuNjA5IDQuNTggNC41OCAwIDAgMS00LjYwOS00LjYwOSA0LjU4IDQuNTggMCAwIDEgNC42MDktNC42MDl6bTUyLjc0NCAwYTQuNTggNC41OCAwIDAgMSA0LjYwOSA0LjYwOSA0LjYwOSA0LjYwOSAwIDEgMS05LjIxOCAwYy4wNjYtMi41NjggMi4wNDEtNC42MDkgNC42MDktNC42MDl6TTE5LjE3NiA0MS45NTdjMS44MjcgMCAyLjg1IDEuMzAxIDIuODUgMy4yNTIgMCAuMTg2LS4xNTUuMzQxLS4zNDEuMzQxSDE2Ljc2Yy4wMzEgMS43MzQgMS4yMzkgMi43MjYgMi42MzMgMi43MjZhMi44OSAyLjg5IDAgMCAwIDEuNzk2LS42MTljLjE4Ni0uMTI0LjM0MS0uMDkzLjQzNC4wOTNsLjA5My4xNTVjLjA5My4xNTUuMDYyLjMxLS4wOTMuNDM0YTMuODQgMy44NCAwIDAgMS0yLjI2MS43NDNjLTIuMDEzIDAtMy41NjItMS40NTYtMy41NjItMy41NjIuMDMxLTIuMjMgMS41MTgtMy41NjIgMy4zNzYtMy41NjJ6bTEuODg5IDIuOTExYy0uMDYyLTEuNDI1LS45MjktMi4xMzctMS45Mi0yLjEzNy0xLjE0NiAwLTIuMTM3Ljc0My0yLjM1NCAyLjEzN2g0LjI3NHptMTAuMjUzLTEuOTJ2LS43NzQtMS42MTFjMC0uMjE3LjEyNC0uMzQxLjM0MS0uMzQxaC4yNDhjLjIxNyAwIC4zMS4xMjQuMzEuMzQxdjEuNjExaDEuMzk0Yy4yMTcgMCAuMzQxLjEyNC4zNDEuMzQxdi4wOTNjMCAuMjE3LS4xMjQuMzEtLjM0MS4zMWgtMS4zOTR2My40MDdjMCAxLjU4Ljk2IDEuNzY2IDEuNDg3IDEuNzk2LjI3OS4wMzEuMzcyLjA5My4zNzIuMzQxdi4xODZjMCAuMjE3LS4wOTMuMzEtLjM3Mi4zMS0xLjQ4NyAwLTIuMzg1LS44OTgtMi4zODUtMi41MDl2LTMuNXptNy4wOTMtLjk5MWMxLjE3NyAwIDEuOTIuNDk2IDIuMjYxLjc3NC4xNTUuMTI0LjE4Ni4yNzkuMDMxLjQ2NWwtLjA5My4xNTVjLS4xMjQuMTg2LS4yNzkuMTg2LS40NjUuMDYyLS4zMS0uMjE3LS44OTgtLjYxOS0xLjcwMy0uNjE5LTEuNDg3IDAtMi42NjQgMS4xMTUtMi42NjQgMi43NTcgMCAxLjYxMSAxLjE3NyAyLjcyNiAyLjY2NCAyLjcyNi45NiAwIDEuNjExLS40MzQgMS45Mi0uNzEyLjE4Ni0uMTI0LjMxLS4wOTMuNDM0LjA5M2wuMDkzLjEyNGMuMDkzLjE4Ni4wNjIuMzEtLjA5My40NjVhMy44MSAzLjgxIDAgMCAxLTIuNDE2Ljg2N2MtMi4wMTMgMC0zLjU2Mi0xLjQ1Ni0zLjU2Mi0zLjU2Mi4wMzEtMi4xMDYgMS41OC0zLjU5MyAzLjU5My0zLjU5M3ptNC4xMTktMi4xOTljMC0uMjE3LS4xMjQtLjM0MS4wOTMtLjM0MWguMjQ4Yy4yMTcgMCAuNTU4LjEyNC41NTguMzQxdjcuNDAzYzAgLjg2Ny40MDMuOTYuNzEyLjk5MS4xNTUgMCAuMjc5LjA5My4yNzkuMzF2LjIxN2MwIC4yMTctLjA5My4zNDEtLjM0MS4zNDEtLjU1NyAwLTEuNTQ5LS4xODYtMS41NDktMS42NzN2LTcuNTg5em02LjM1IDIuMTk5YzEuOTgyIDAgMy41OTMgMS41MTggMy41OTMgMy41MzEgMCAyLjA0NC0xLjYxMSAzLjU5My0zLjU5MyAzLjU5M3MtMy41OTMtMS41NDktMy41OTMtMy41OTNjMC0yLjAxMyAxLjYxMS0zLjUzMSAzLjU5My0zLjUzMXptMCA2LjMxOWMxLjQ1NiAwIDIuNjMzLTEuMTc3IDIuNjMzLTIuNzg4IDAtMS41NDktMS4xNzctMi42OTUtMi42MzMtMi42OTVhMi42NyAyLjY3IDAgMCAwLTIuNjY0IDIuNjk1Yy4wMzEgMS41OCAxLjIwOCAyLjc4OCAyLjY2NCAyLjc4OHptMTUuNDU2LTYuMzE5YTIuNDUgMi40NSAwIDAgMSAyLjIzIDEuMzYzaC4wMzFzLS4wMzEtLjIxNy0uMDMxLS41MjZ2LTMuMDY2YzAtLjIxNy0uMDkzLS4zNDEuMTI0LS4zNDFoLjI0OGMuMjE3IDAgLjU1OC4xMjQuNTU4LjM0MXY4LjgyN2MwIC4yMTctLjA5My4zNDEtLjMxLjM0MWgtLjIxN2MtLjIxNyAwLS4zNDEtLjA5My0uMzQxLS4zMXYtLjUyN2MwLS4yNDguMDYyLS40MzQuMDYyLS40MzRoLS4wMzFzLS41ODkgMS40MjUtMi4zNTQgMS40MjVjLTEuODI3IDAtMi45NzMtMS40NTYtMi45NzMtMy41NjItLjA2Mi0yLjEwNiAxLjIwOC0zLjUzMSAzLjAwNC0zLjUzMWgwem0uMDMxIDYuMzE5YzEuMTQ2IDAgMi4xOTktLjgwNSAyLjE5OS0yLjc1NyAwLTEuMzk0LS43MTItMi43MjYtMi4xNjgtMi43MjYtMS4yMDggMC0yLjE5OS45OTEtMi4xOTkgMi43MjYuMDMxIDEuNjczLjg5OCAyLjc1NyAyLjE2OCAyLjc1N3ptLTU2LjU1OC42NWguMjQ4Yy4yMTcgMCAuMzQxLS4xMjQuMzQxLS4zNDF2LTYuNjI4YzAtMS4wNTMgMS4xNDYtMS43OTYgMi40NDctMS43OTZzMi40NDcuNzQzIDIuNDQ3IDEuNzk2djYuNjU5YzAgLjIxNy4xMjQuMzQxLjM0MS4zNDFoLjI0OGMuMjE3IDAgLjMxLS4xMjQuMzEtLjM0MXYtNi43MjFjMC0xLjc2Ni0xLjc2NS0yLjYzMy0zLjM3Ni0yLjYzM2gwIDAgMCAwYy0xLjU0OSAwLTMuMzE0Ljg2Ny0zLjMxNCAyLjYzM3Y2LjY5YzAgLjIxNy4wOTMuMzQxLjMxLjM0MXptNTEuNjk1LTYuODE0aC0uMjQ4Yy0uMjE3IDAtLjM0MS4xMjQtLjM0MS4zNDF2My43NDhjMCAxLjA1My0uNjgxIDIuMDEzLTIuMDEzIDIuMDEzLTEuMzAxIDAtMi4wMTMtLjk2LTIuMDEzLTIuMDEzdi0zLjc0OGMwLS4yMTctLjEyNC0uMzQxLS4zNDEtLjM0MUg1NC4zYy0uMjE3IDAtLjMxLjEyNC0uMzEuMzQxdjMuOTk2YzAgMS43NjUgMS4zMDEgMi42MzMgMi45MTIgMi42MzNoMCAwIDAgMGMxLjYxMSAwIDIuOTExLS44NjcgMi45MTEtMi42MzN2LTMuOTk2Yy4wMzEtLjIxNy0uMDkzLS4zNDEtLjMxLS4zNDFoMHptLTMwLjY2NC0uMDMxYy0uMDYyIDAtLjE1NS4wNjItLjIxNy4xNTVsLTEuMjM5IDEuNDg3LS45MjkgMS4xMTUtMS40MjUtMS43MDQtLjc3NC0uOTI5Yy0uMDYyLS4wOTMtLjE1NS0uMTI0LS4yMTctLjEyNHMtLjE1NS4wMzEtLjI0OC4wOTNsLS4xODYuMTU1Yy0uMTU1LjEyNC0uMTU1LjI3OS0uMDMxLjQ2NWwxLjIzOSAxLjQ4NyAxLjA1MyAxLjIzOS0xLjUxOCAxLjgyN2gwbC0uNzc0LjkyOWMtLjEyNC4xNTUtLjEyNC4zNDEuMDMxLjQ5NmwuMTg2LjE1NWMuMTU1LjEyNC4zMS4wOTMuNDY1LS4wNjJsMS4yMzktMS40ODcuOTI5LTEuMTE1IDEuNDI1IDEuNzA0aDBsLjc3NC45MjljLjEyNC4xNTUuMzEuMTg2LjQ2NS4wMzFsLjE4Ni0uMTU1Yy4xNTUtLjEyNC4xNTUtLjI3OS4wMzEtLjQ2NWwtMS4yMzktMS40ODctMS4wNTMtMS4yMzkgMS41MTgtMS44MjdoMGwuNzc0LS45MjljLjEyNC0uMTU1LjEyNC0uMzQxLS4wMzEtLjQ5NWwtLjE4Ni0uMTg2Yy0uMDkzLS4wNjItLjE1NS0uMDkzLS4yNDgtLjA2MmgweiIgZmlsbD0iIzAwODJjOSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiLz48L3N5bWJvbD48L3N2Zz4=', - }, - categories: [{ id: 3, name: 'Data & Storage' }], - displayName: 'Nextcloud', - typeVersion: 1, - }, - ], - categories: [ - { id: 2, name: 'Sales' }, - { id: 8, name: 'Finance & Accounting' }, - ], - image: [], - full: true, -} satisfies ITemplatesWorkflowFull; - -/** Template that doesn't contain nodes requiring credentials */ -export const fullCreateApiEndpointTemplate = { - id: 1750, - name: 'Creating an API endpoint', - totalViews: 13265, - createdAt: '2022-07-06T14:45:19.659Z', - description: - '**Task:**\nCreate a simple API endpoint using the Webhook and Respond to Webhook nodes\n\n**Why:**\nYou can prototype or replace a backend process with a single workflow\n\n**Main use cases:**\nReplace backend logic with a workflow', - workflow: { - nodes: [ - { - id: 'd65f8060-0196-430a-923c-57f838991dd1', - name: 'Webhook', - type: 'n8n-nodes-base.webhook', - position: [375, 115], - webhookId: '6f7b288e-1efe-4504-a6fd-660931327269', - parameters: { - path: '6f7b288e-1efe-4504-a6fd-660931327269', - options: {}, - responseMode: 'responseNode', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991dd9', - name: 'Note1', - type: 'n8n-nodes-base.stickyNote', - position: [355, -25], - parameters: { - width: 600, - height: 280, - content: - '## Create a simple API endpoint\n\nIn this workflow we show how to create a simple API endpoint with `Webhook` and `Respond to Webhook` nodes\n\n', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991dd5', - name: 'Respond to Webhook', - type: 'n8n-nodes-base.respondToWebhook', - position: [815, 115], - parameters: { - options: {}, - respondWith: 'text', - responseBody: - '=The URL of the Google search query for the term "{{$node["Webhook"].json["query"]["first_name"]}} {{$node["Webhook"].json["query"]["last_name"]}}" is: {{$json["product"]}}', - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991df1', - name: 'Create URL string', - type: 'n8n-nodes-base.set', - position: [595, 115], - parameters: { - values: { - string: [ - { - name: 'product', - value: - '=https://www.google.com/search?q={{$json["query"]["first_name"]}}+{{$json["query"]["last_name"]}}', - }, - ], - }, - options: {}, - keepOnlySet: true, - }, - typeVersion: 1, - }, - { - id: 'd65f8060-0196-430a-923c-57f838991dbb', - name: 'Note3', - type: 'n8n-nodes-base.stickyNote', - position: [355, 275], - parameters: { - width: 600, - height: 220, - content: - '### How to use it\n1. Execute the workflow so that the webhook starts listening\n2. Make a test request by pasting, **in a new browser tab**, the test URL from the `Webhook` node and appending the following test at the end `?first_name=bob&last_name=dylan`\n\nYou will receive the following output in the new tab `The URL of the Google search query for the term "bob dylan" is: https://www.google.com/search?q=bob+dylan`\n\n', - }, - typeVersion: 1, - }, - ], - connections: { - Webhook: { main: [[{ node: 'Create URL string', type: NodeConnectionType.Main, index: 0 }]] }, - 'Create URL string': { - main: [[{ node: 'Respond to Webhook', type: NodeConnectionType.Main, index: 0 }]], - }, - }, - }, - workflowInfo: { - nodeCount: 2, - nodeTypes: {}, - }, - user: { username: 'jon-n8n' }, - nodes: [ - { - id: 38, - icon: 'fa:pen', - name: 'n8n-nodes-base.set', - defaults: { name: 'Edit Fields', color: '#0000FF' }, - iconData: { icon: 'pen', type: 'icon' }, - categories: [{ id: 9, name: 'Core Nodes' }], - displayName: 'Edit Fields (Set)', - typeVersion: 3, - }, - { - id: 47, - icon: 'file:webhook.svg', - name: 'n8n-nodes-base.webhook', - defaults: { name: 'Webhook' }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0zNSwzN2MtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMzcuMiwzNywzNSwzN3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUsNDNjLTMsMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEsMS40LDIuOSwyLjMsNC43LDIuM2MzLjMsMCw2LTIuNyw2LTZzLTIuNy02LTYtNiBjLTEsMC0yLDAuMy0yLjksMC43bC0xLjcsMUwyMy4zLDE2bDMuNS0xLjlsNS4zLDkuNGMxLTAuMywyLTAuNSwzLTAuNWM1LjUsMCwxMCw0LjUsMTAsMTBTNDAuNSw0MywzNSw0M3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMTQsNDNDOC41LDQzLDQsMzguNSw0LDMzYzAtNC42LDMuMS04LjUsNy41LTkuN2wxLDMuOUM5LjksMjcuOSw4LDMwLjMsOCwzM2MwLDMuMywyLjcsNiw2LDYgczYtMi43LDYtNnYtMmgxNXY0SDIzLjhDMjIuOSwzOS42LDE4LjgsNDMsMTQsNDN6Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0iTTE0LDM3Yy0yLjIsMC00LTEuOC00LTRzMS44LTQsNC00czQsMS44LDQsNFMxNi4yLDM3LDE0LDM3eiIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSwxOWMtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMjcuMiwxOSwyNSwxOXoiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTUuNywzNEwxMi4zLDMybDUuOS05LjdjLTItMS45LTMuMi00LjUtMy4yLTcuM2MwLTUuNSw0LjUtMTAsMTAtMTBjNS41LDAsMTAsNC41LDEwLDEwIGMwLDAuOS0wLjEsMS43LTAuMywyLjVsLTMuOS0xYzAuMS0wLjUsMC4yLTEsMC4yLTEuNWMwLTMuMy0yLjctNi02LTZzLTYsMi43LTYsNmMwLDIuMSwxLjEsNCwyLjksNS4xbDEuNywxTDE1LjcsMzR6Ii8+PC9zdmc+Cg==', - }, - categories: [ - { id: 5, name: 'Development' }, - { id: 9, name: 'Core Nodes' }, - ], - displayName: 'Webhook', - typeVersion: 1, - }, - { - id: 535, - icon: 'file:webhook.svg', - name: 'n8n-nodes-base.respondToWebhook', - defaults: { name: 'Respond to Webhook' }, - iconData: { - type: 'file', - fileBuffer: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0zNSwzN2MtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMzcuMiwzNywzNSwzN3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUsNDNjLTMsMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEsMS40LDIuOSwyLjMsNC43LDIuM2MzLjMsMCw2LTIuNyw2LTZzLTIuNy02LTYtNiBjLTEsMC0yLDAuMy0yLjksMC43bC0xLjcsMUwyMy4zLDE2bDMuNS0xLjlsNS4zLDkuNGMxLTAuMywyLTAuNSwzLTAuNWM1LjUsMCwxMCw0LjUsMTAsMTBTNDAuNSw0MywzNSw0M3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMTQsNDNDOC41LDQzLDQsMzguNSw0LDMzYzAtNC42LDMuMS04LjUsNy41LTkuN2wxLDMuOUM5LjksMjcuOSw4LDMwLjMsOCwzM2MwLDMuMywyLjcsNiw2LDYgczYtMi43LDYtNnYtMmgxNXY0SDIzLjhDMjIuOSwzOS42LDE4LjgsNDMsMTQsNDN6Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0iTTE0LDM3Yy0yLjIsMC00LTEuOC00LTRzMS44LTQsNC00czQsMS44LDQsNFMxNi4yLDM3LDE0LDM3eiIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSwxOWMtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMjcuMiwxOSwyNSwxOXoiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTUuNywzNEwxMi4zLDMybDUuOS05LjdjLTItMS45LTMuMi00LjUtMy4yLTcuM2MwLTUuNSw0LjUtMTAsMTAtMTBjNS41LDAsMTAsNC41LDEwLDEwIGMwLDAuOS0wLjEsMS43LTAuMywyLjVsLTMuOS0xYzAuMS0wLjUsMC4yLTEsMC4yLTEuNWMwLTMuMy0yLjctNi02LTZzLTYsMi43LTYsNmMwLDIuMSwxLjEsNCwyLjksNS4xbDEuNywxTDE1LjcsMzR6Ii8+PC9zdmc+Cg==', - }, - categories: [ - { id: 7, name: 'Utility' }, - { id: 9, name: 'Core Nodes' }, - ], - displayName: 'Respond to Webhook', - typeVersion: 1, - }, - ], - categories: [{ id: 20, name: 'Building Blocks' }], - image: [], - full: true, -} satisfies ITemplatesWorkflowFull; 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 1e907a1dd6761e..8a30bd88982a20 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 @@ -1,12 +1,71 @@ +import { setActivePinia } from 'pinia'; +import { createTestingPinia } from '@pinia/testing'; +import { mock } from 'vitest-mock-extended'; +import type { ICredentialType } from 'n8n-workflow'; + +import type { + ICredentialsResponse, + ITemplatesWorkflowFull, + IWorkflowTemplateNode, +} from '@/Interface'; import { useTemplatesStore } from '@/stores/templates.store'; import { keyFromCredentialTypeAndName } from '@/utils/templates/templateTransforms'; import { useSetupTemplateStore } from '@/views/SetupWorkflowFromTemplateView/setupTemplate.store'; -import { setActivePinia } from 'pinia'; -import * as testData from './setupTemplate.store.testData'; -import { createTestingPinia } from '@pinia/testing'; import { useCredentialsStore } from '@/stores/credentials.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; +import { + nodeTypeHttpRequest, + nodeTypeNextCloud, + nodeTypeReadImap, + nodeTypeTelegram, + nodeTypeTwitter, +} from '@/utils/testData/nodeTypeTestData'; +import * as testData from './setupTemplate.store.testData'; + +const mockCredentialsResponse = (id: string) => + mock({ + id, + name: 'Telegram account', + type: 'telegramApi', + }); + +const mockCredentialType = (name: string) => mock({ name }); + +const mockTemplateNode = (name: string, type: string) => + mock({ + name, + type, + typeVersion: 1, + position: [0, 0], + }); + +const testTemplate1 = mock({ + id: 1, + workflow: { + nodes: [ + mockTemplateNode('IMAP Email', 'n8n-nodes-base.emailReadImap'), + mockTemplateNode('Nextcloud', 'n8n-nodes-base.nextCloud'), + ], + }, + full: true, +}); + +const testTemplate2 = mock({ + id: 2, + workflow: { + nodes: [ + { + ...mockTemplateNode('Telegram', 'n8n-nodes-base.telegram'), + credentials: { + telegramApi: 'telegram_habot', + }, + }, + ], + }, + full: true, +}); + describe('SetupWorkflowFromTemplateView store', () => { describe('setInitialCredentialsSelection', () => { beforeEach(() => { @@ -17,18 +76,13 @@ describe('SetupWorkflowFromTemplateView store', () => { ); const credentialsStore = useCredentialsStore(); - credentialsStore.setCredentialTypes([testData.credentialTypeTelegram]); + credentialsStore.setCredentialTypes([mockCredentialType('telegramApi')]); const templatesStore = useTemplatesStore(); - templatesStore.addWorkflows([testData.fullShopifyTelegramTwitterTemplate]); + templatesStore.addWorkflows([testTemplate2]); const nodeTypesStore = useNodeTypesStore(); - nodeTypesStore.setNodeTypes([ - testData.nodeTypeTelegramV1, - testData.nodeTypeTwitterV1, - testData.nodeTypeShopifyTriggerV1, - testData.nodeTypeHttpRequestV1, - ]); + nodeTypesStore.setNodeTypes([nodeTypeTelegram, nodeTypeTwitter, nodeTypeHttpRequest]); const setupTemplateStore = useSetupTemplateStore(); - setupTemplateStore.setTemplateId(testData.fullShopifyTelegramTwitterTemplate.id.toString()); + setupTemplateStore.setTemplateId(testTemplate2.id.toString()); }); it("should select no credentials when there isn't any available", () => { @@ -43,8 +97,9 @@ describe('SetupWorkflowFromTemplateView store', () => { it("should select credential when there's only one", () => { // Setup + const credentialId = 'YaSKdvEcT1TSFrrr1'; const credentialsStore = useCredentialsStore(); - credentialsStore.setCredentials([testData.credentialsTelegram1]); + credentialsStore.setCredentials([mockCredentialsResponse(credentialId)]); const setupTemplateStore = useSetupTemplateStore(); @@ -52,17 +107,14 @@ describe('SetupWorkflowFromTemplateView store', () => { setupTemplateStore.setInitialCredentialSelection(); expect(setupTemplateStore.selectedCredentialIdByKey).toEqual({ - [keyFromCredentialTypeAndName('telegramApi', 'telegram_habot')]: 'YaSKdvEcT1TSFrrr1', + [keyFromCredentialTypeAndName('telegramApi', 'telegram_habot')]: credentialId, }); }); it('should select no credentials when there are more than 1 available', () => { // Setup const credentialsStore = useCredentialsStore(); - credentialsStore.setCredentials([ - testData.credentialsTelegram1, - testData.credentialsTelegram2, - ]); + credentialsStore.setCredentials([mockCredentialsResponse('1'), mockCredentialsResponse('2')]); const setupTemplateStore = useSetupTemplateStore(); @@ -83,7 +135,7 @@ describe('SetupWorkflowFromTemplateView store', () => { ])('should not auto-select credentials for %s', (credentialType, auth) => { // Setup const credentialsStore = useCredentialsStore(); - credentialsStore.setCredentialTypes([testData.newCredentialType(credentialType)]); + credentialsStore.setCredentialTypes([mockCredentialType(credentialType)]); credentialsStore.setCredentials([ testData.newCredential({ name: `${credentialType}Credential`, @@ -128,24 +180,17 @@ describe('SetupWorkflowFromTemplateView store', () => { // Setup const credentialsStore = useCredentialsStore(); - credentialsStore.setCredentialTypes([testData.credentialTypeTelegram]); + credentialsStore.setCredentialTypes([mockCredentialType('telegramApi')]); const templatesStore = useTemplatesStore(); - templatesStore.addWorkflows([testData.fullSaveEmailAttachmentsToNextCloudTemplate]); + templatesStore.addWorkflows([testTemplate1]); const nodeTypesStore = useNodeTypesStore(); - nodeTypesStore.setNodeTypes([ - testData.nodeTypeReadImapV1, - testData.nodeTypeReadImapV2, - testData.nodeTypeNextCloudV1, - ]); + nodeTypesStore.setNodeTypes([nodeTypeReadImap, nodeTypeNextCloud]); const setupTemplateStore = useSetupTemplateStore(); - setupTemplateStore.setTemplateId( - testData.fullSaveEmailAttachmentsToNextCloudTemplate.id.toString(), - ); + setupTemplateStore.setTemplateId(testTemplate1.id.toString()); }); - const templateImapNode = testData.fullSaveEmailAttachmentsToNextCloudTemplate.workflow.nodes[0]; - const templateNextcloudNode = - testData.fullSaveEmailAttachmentsToNextCloudTemplate.workflow.nodes[1]; + const templateImapNode = testTemplate1.workflow.nodes[0]; + const templateNextcloudNode = testTemplate1.workflow.nodes[1]; it('should return correct credential usages', () => { const setupTemplateStore = useSetupTemplateStore(); diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.testData.ts b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.testData.ts index a396a43bdfbeaf..be05e5b18e37b3 100644 --- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.testData.ts +++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/setupTemplate.store.testData.ts @@ -53,52 +53,3 @@ export const newCredential = ( name: faker.commerce.productName(), ...opts, }); - -export const credentialsTelegram1: ICredentialsResponse = { - createdAt: '2023-11-23T14:26:07.969Z', - updatedAt: '2023-11-23T14:26:07.964Z', - id: 'YaSKdvEcT1TSFrrr1', - name: 'Telegram account', - type: 'telegramApi', - ownedBy: { - id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f', - email: 'user@n8n.io', - firstName: 'Player', - lastName: 'One', - }, - sharedWithProjects: [], -}; - -export const credentialsTelegram2: ICredentialsResponse = { - createdAt: '2023-11-23T14:26:07.969Z', - updatedAt: '2023-11-23T14:26:07.964Z', - id: 'YaSKdvEcT1TSFrrr2', - name: 'Telegram account', - type: 'telegramApi', - ownedBy: { - id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f', - email: 'user@n8n.io', - firstName: 'Player', - lastName: 'One', - }, - sharedWithProjects: [], -}; - -export { - fullSaveEmailAttachmentsToNextCloudTemplate, - fullShopifyTelegramTwitterTemplate, -} from '@/utils/testData/templateTestData'; - -export { credentialTypeTelegram, newCredentialType } from '@/utils/testData/credentialTypeTestData'; - -export { - nodeTypeHttpRequestV1, - nodeTypeNextCloudV1, - nodeTypeReadImapV1, - nodeTypeReadImapV2, - nodeTypeShopifyTriggerV1, - nodeTypeTelegramV1, - nodeTypeTelegramV1_1, - nodeTypeTwitterV1, - nodeTypeTwitterV2, -} from '@/utils/testData/nodeTypeTestData'; 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 28299b475d257f..410c3161ecbaed 100644 --- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts +++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/__tests__/useCredentialSetupState.test.ts @@ -1,3 +1,5 @@ +import { mock } from 'vitest-mock-extended'; +import type { IWorkflowTemplateNode } from '@/Interface'; import { keyFromCredentialTypeAndName } from '@/utils/templates/templateTransforms'; import type { IWorkflowTemplateNodeWithCredentials } from '@/utils/templates/templateTransforms'; import type { CredentialUsages } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState'; @@ -5,8 +7,7 @@ import { getAppCredentials, groupNodeCredentialsByKey, } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState'; -import * as testData from './setupTemplate.store.testData'; -import { newWorkflowTemplateNode } from '@/utils/testData/templateTestData'; +import { nodeTypeTelegram, nodeTypeTwitter } from '@/utils/testData/nodeTypeTestData'; const objToMap = (obj: Record) => { return new Map(Object.entries(obj)) as Map; @@ -40,7 +41,7 @@ describe('useCredentialSetupState', () => { groupNodeCredentialsByKey([ { node: nodesByName.Twitter, - requiredCredentials: testData.nodeTypeTwitterV1.credentials, + requiredCredentials: nodeTypeTwitter.credentials!, }, ]), ).toEqual( @@ -58,14 +59,14 @@ describe('useCredentialSetupState', () => { it('returns credentials grouped when the credential names are the same', () => { const [node1, node2] = [ - newWorkflowTemplateNode({ + mock({ id: 'twitter', type: 'n8n-nodes-base.twitter', credentials: { twitterOAuth1Api: 'credential', }, }) as IWorkflowTemplateNodeWithCredentials, - newWorkflowTemplateNode({ + mock({ id: 'telegram', type: 'n8n-nodes-base.telegram', credentials: { @@ -78,11 +79,11 @@ describe('useCredentialSetupState', () => { groupNodeCredentialsByKey([ { node: node1, - requiredCredentials: testData.nodeTypeTwitterV1.credentials, + requiredCredentials: nodeTypeTwitter.credentials!, }, { node: node2, - requiredCredentials: testData.nodeTypeTelegramV1.credentials, + requiredCredentials: nodeTypeTelegram.credentials!, }, ]), ).toEqual(