diff --git a/package-lock.json b/package-lock.json index 8ca12dd97..35d4ed9d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n", - "version": "0.198.0", + "version": "0.198.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "n8n", - "version": "0.198.0", + "version": "0.198.2", "hasInstallScript": true, "workspaces": [ "packages/*", @@ -43375,7 +43375,7 @@ }, "packages/cli": { "name": "n8n", - "version": "0.198.0", + "version": "0.198.2", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@oclif/command": "^1.5.18", @@ -43422,7 +43422,7 @@ "lodash.unset": "^4.5.2", "mysql2": "~2.3.0", "n8n-core": "~0.138.0", - "n8n-editor-ui": "~0.164.0", + "n8n-editor-ui": "~0.164.2", "n8n-nodes-base": "~0.196.0", "n8n-workflow": "~0.120.0", "nodemailer": "^6.7.1", @@ -45822,7 +45822,7 @@ }, "packages/editor-ui": { "name": "n8n-editor-ui", - "version": "0.164.0", + "version": "0.164.2", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@codemirror/autocomplete": "^6.1.0", @@ -72169,7 +72169,7 @@ "lodash.unset": "^4.5.2", "mysql2": "~2.3.0", "n8n-core": "~0.138.0", - "n8n-editor-ui": "~0.164.0", + "n8n-editor-ui": "~0.164.2", "n8n-nodes-base": "~0.196.0", "n8n-workflow": "~0.120.0", "nodemailer": "^6.7.1", diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index 1651488da..772dd0e54 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -4,7 +4,7 @@ import { promises as fs } from 'fs'; import { Command, flags } from '@oclif/command'; import { BinaryDataManager, UserSettings, PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core'; -import { INode, LoggerProxy } from 'n8n-workflow'; +import { LoggerProxy } from 'n8n-workflow'; import { ActiveExecutions, @@ -25,6 +25,7 @@ import { import { getLogger } from '../src/Logger'; import config from '../config'; import { getInstanceOwner } from '../src/UserManagement/UserManagementHelper'; +import { findCliWorkflowStart } from '../src/utils'; export class Execute extends Command { static description = '\nExecutes a given workflow'; @@ -116,6 +117,10 @@ export class Execute extends Command { } } + if (!workflowData) { + throw new Error('Failed to retrieve workflow data for requested workflow'); + } + // Make sure the settings exist await UserSettings.prepareUserSettings(); @@ -144,33 +149,14 @@ export class Execute extends Command { workflowId = undefined; } - // Check if the workflow contains the required "Start" node - // "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue - const requiredNodeTypes = ['n8n-nodes-base.start']; - let startNode: INode | undefined; - // eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-non-null-assertion - for (const node of workflowData!.nodes) { - if (requiredNodeTypes.includes(node.type)) { - startNode = node; - break; - } - } - - if (startNode === undefined) { - // If the workflow does not contain a start-node we can not know what - // should be executed and with which data to start. - console.info(`The workflow does not contain a "Start" node. So it can not be executed.`); - // eslint-disable-next-line consistent-return - return Promise.resolve(); - } - try { + const startingNode = findCliWorkflowStart(workflowData.nodes); + const user = await getInstanceOwner(); const runData: IWorkflowExecutionDataProcess = { executionMode: 'cli', - startNodes: [startNode.name], - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - workflowData: workflowData!, + startNodes: [startingNode.name], + workflowData, userId: user.id, }; @@ -207,6 +193,7 @@ export class Execute extends Command { logger.error('\nExecution error:'); logger.info('===================================='); logger.error(e.message); + if (e.description) logger.error(e.description); logger.error(e.stack); this.exit(1); } diff --git a/packages/cli/commands/executeBatch.ts b/packages/cli/commands/executeBatch.ts index d2887eb47..3f4ffd923 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -39,6 +39,7 @@ import { import config from '../config'; import { User } from '../src/databases/entities/User'; import { getInstanceOwner } from '../src/UserManagement/UserManagementHelper'; +import { findCliWorkflowStart } from '../src/utils'; export class ExecuteBatch extends Command { static description = '\nExecutes multiple workflows once'; @@ -613,16 +614,6 @@ export class ExecuteBatch extends Command { coveredNodes: {}, }; - const requiredNodeTypes = ['n8n-nodes-base.start']; - let startNode: INode | undefined; - // eslint-disable-next-line no-restricted-syntax - for (const node of workflowData.nodes) { - if (requiredNodeTypes.includes(node.type)) { - startNode = node; - break; - } - } - // We have a cool feature here. // On each node, on the Settings tab in the node editor you can change // the `Notes` field to add special cases for comparison and snapshots. @@ -659,14 +650,6 @@ export class ExecuteBatch extends Command { }); return new Promise(async (resolve) => { - if (startNode === undefined) { - // If the workflow does not contain a start-node we can not know what - // should be executed and with which data to start. - executionResult.error = 'Workflow cannot be started as it does not contain a "Start" node.'; - executionResult.executionStatus = 'warning'; - resolve(executionResult); - } - let gotCancel = false; // Timeouts execution after 5 minutes. @@ -678,9 +661,11 @@ export class ExecuteBatch extends Command { }, ExecuteBatch.executionTimeout); try { + const startingNode = findCliWorkflowStart(workflowData.nodes); + const runData: IWorkflowExecutionDataProcess = { executionMode: 'cli', - startNodes: [startNode!.name], + startNodes: [startingNode.name], workflowData, userId: ExecuteBatch.instanceOwner.id, }; diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 7f2208615..fc5ad0aad 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -33,6 +33,7 @@ import { IWorkflowHooksOptionalParameters, IWorkflowSettings, LoggerProxy as Logger, + SubworkflowOperationError, Workflow, WorkflowExecuteMode, WorkflowHooks, @@ -67,6 +68,7 @@ import { } from './UserManagement/UserManagementHelper'; import { whereClause } from './WorkflowHelpers'; import { IWorkflowErrorData } from './Interfaces'; +import { findSubworkflowStart } from './utils'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); @@ -748,21 +750,7 @@ export async function getRunData( ): Promise { const mode = 'integrated'; - // Find Start-Node - const requiredNodeTypes = ['n8n-nodes-base.start']; - let startNode: INode | undefined; - // eslint-disable-next-line no-restricted-syntax - for (const node of workflowData.nodes) { - if (requiredNodeTypes.includes(node.type)) { - startNode = node; - break; - } - } - if (startNode === undefined) { - // If the workflow does not contain a start-node we can not know what - // should be executed and with what data to start. - throw new Error(`The workflow does not contain a "Start" node and can so not be executed.`); - } + const startingNode = findSubworkflowStart(workflowData.nodes); // Always start with empty data if no inputData got supplied inputData = inputData || [ @@ -774,7 +762,7 @@ export async function getRunData( // Initialize the incoming data const nodeExecutionStack: IExecuteData[] = []; nodeExecutionStack.push({ - node: startNode, + node: startingNode, data: { main: [inputData], }, diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index abf501b93..5557dec17 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -361,11 +361,12 @@ export class WorkflowRunnerProcess { ) { // Execute all nodes + const pinDataKeys = this.data?.pinData ? Object.keys(this.data.pinData) : []; + const noPinData = pinDataKeys.length === 0; + const isPinned = (nodeName: string) => pinDataKeys.includes(nodeName); + let startNode; - if ( - this.data.startNodes?.length === 1 && - Object.keys(this.data.pinData ?? {}).includes(this.data.startNodes[0]) - ) { + if (this.data.startNodes?.length === 1 && (noPinData || isPinned(this.data.startNodes[0]))) { startNode = this.workflow.getNode(this.data.startNodes[0]) ?? undefined; } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts new file mode 100644 index 000000000..d026bd319 --- /dev/null +++ b/packages/cli/src/utils.ts @@ -0,0 +1,30 @@ +import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow'; +import type { INode } from 'n8n-workflow'; + +function findWorkflowStart(executionMode: 'integrated' | 'cli') { + return function (nodes: INode[]) { + const executeWorkflowTriggerNode = nodes.find( + (node) => node.type === 'n8n-nodes-base.executeWorkflowTrigger', + ); + + if (executeWorkflowTriggerNode) return executeWorkflowTriggerNode; + + const startNode = nodes.find((node) => node.type === 'n8n-nodes-base.start'); + + if (startNode) return startNode; + + const title = 'Missing node to start execution'; + const description = + "Please make sure the workflow you're calling contains an Execute Workflow Trigger node"; + + if (executionMode === 'integrated') { + throw new SubworkflowOperationError(title, description); + } + + throw new CliWorkflowOperationError(title, description); + }; +} + +export const findSubworkflowStart = findWorkflowStart('integrated'); + +export const findCliWorkflowStart = findWorkflowStart('cli'); diff --git a/packages/editor-ui/public/static/webhook-icon.svg b/packages/editor-ui/public/static/webhook-icon.svg new file mode 100644 index 000000000..87c410718 --- /dev/null +++ b/packages/editor-ui/public/static/webhook-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index b2d8839d7..437b32f58 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -269,6 +269,11 @@ export interface IWorkflowTemplate { }; } +export interface INewWorkflowData { + name: string; + onboardingFlowEnabled: boolean; +} + // Almost identical to cli.Interfaces.ts export interface IWorkflowDb { id: string; @@ -756,6 +761,13 @@ export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR'; export interface ISubcategoryItemProps { subcategory: string; description: string; + icon?: string; + defaults?: INodeParameters; + iconData?: { + type: string; + icon?: string; + fileBuffer?: string; + }; } export interface INodeItemProps { @@ -876,6 +888,7 @@ export interface IRootState { instanceId: string; nodeMetadata: {[nodeName: string]: INodeMetadata}; isNpmAvailable: boolean; + subworkflowExecutionError: Error | null; } export interface ICommunityPackageMap { @@ -981,6 +994,15 @@ export type IFakeDoor = { export type IFakeDoorLocation = 'settings' | 'credentialsModal'; +export type INodeFilterType = "Regular" | "Trigger" | "All"; + +export interface INodeCreatorState { + itemsFilter: string; + showTabs: boolean; + showScrim: boolean; + selectedType: INodeFilterType; +} + export interface ISettingsState { settings: IN8nUISettings; promptsData: IN8nPrompts; diff --git a/packages/editor-ui/src/components/MainHeader/MainHeader.vue b/packages/editor-ui/src/components/MainHeader/MainHeader.vue index d82828f01..da70f58d4 100644 --- a/packages/editor-ui/src/components/MainHeader/MainHeader.vue +++ b/packages/editor-ui/src/components/MainHeader/MainHeader.vue @@ -56,7 +56,7 @@ export default mixins( diff --git a/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue b/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue new file mode 100644 index 000000000..8887d7959 --- /dev/null +++ b/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue @@ -0,0 +1,526 @@ + + + + + diff --git a/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.vue b/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.vue index 4acee002d..1e824808d 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.vue @@ -1,7 +1,7 @@