2023-10-06 08:04:33 -07:00
import { NodeOperationError } from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IExecuteFunctions ,
2019-12-19 14:07:55 -08:00
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
2023-10-06 08:04:33 -07:00
import { generatePairedItemData } from '../../utils/utilities' ;
import { getWorkflowInfo } from './GenericFunctions' ;
2019-12-19 14:07:55 -08:00
export class ExecuteWorkflow implements INodeType {
description : INodeTypeDescription = {
displayName : 'Execute Workflow' ,
name : 'executeWorkflow' ,
feat(editor, core, cli): implement new workflow experience (#4358)
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node (#4108)
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node
* feat(editor): Do not show duplicate button if canvas contains `maxNodes` amount of nodes
* feat(ManualTrigger node): Implement ManualTrigger node (#4110)
* feat(ManualTrigger node): Implement ManualTrigger node
* :memo: Remove generics doc items from ManualTrigger node
* feat(editor-ui): Trigger tab redesign (#4150)
* :construction: Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory
* :construction: Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations
* :sparkles: Implement MainPanel background scrim
* :recycle: Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`
* :bug: Fix SlideTransition for all the NodeCreato panels
* :lipstick: Fix cursos for CategoryItem and NodeItem
* :bug: Make sure ALL_NODE_FILTER is always set when MainPanel is mounted
* :art: Address PR comments
* label: Use Array type for CategorizedItems props
* :label: Add proper types for Vue props
* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel
* 🎨 Use kebab case for main-panel and icon component
* :label: Improve types
* feat(editor-ui): Redesign search input inside node creator panel (#4204)
* :construction: Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory
* :construction: Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations
* :sparkles: Implement MainPanel background scrim
* :recycle: Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`
* :bug: Fix SlideTransition for all the NodeCreato panels
* :lipstick: Fix cursos for CategoryItem and NodeItem
* :bug: Make sure ALL_NODE_FILTER is always set when MainPanel is mounted
* :art: Address PR comments
* label: Use Array type for CategorizedItems props
* :label: Add proper types for Vue props
* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel
* :sparkles: Redesign search input and unify usage of categorized items
* :label: Use lowercase "Boolean" as `isSearchVisible` computed return type
* :fire: Remove useless emit
* :sparkles: Implement no result view based on subcategory, minor fixes
* :art: Remove unused properties
* feat(node-email): Change EmailReadImap display name and name (#4239)
* feat(editor-ui): Implement "Choose a Triger" action and related behaviour (#4226)
* :sparkles: Implement "Choose a Triger" action and related behaviour
* :mute: Lint fix
* :recycle: Remove PlaceholderTrigger node, add a button instead
* :art: Merge onMouseEnter and onMouseLeave to a single function
* :bulb: Add comment
* :fire: Remove PlaceholderNode registration
* :art: Rename TriggerPlaceholderButton to CanvasAddButton
* :sparkles: Add method to unregister custom action and rework CanvasAddButton centering logic
* :art: Run `setRecenteredCanvasAddButtonPosition` on `CanvasAddButton` mount
* fix(editor): Fix selecting of node from node-creator panel by clicking
* :twisted_rightwards_arrows: Merge fixes
* fix(editor): Show execute workflow trigger instead of workflow trigger in the trigger helper panel
* feat(editor): Fix node creator panel slide transition (#4261)
* fix(editor): Fix node creator panel slide-in/slide-out transitions
* :art: Fix naming
* :art: Use kebab-case for transition component name
* feat(editor): Disable execution and show notice when user tries to run workflow without enabled triggers
* fix(editor): Address first batch of new WF experience review (#4279)
* fix(editor): Fix first batch of review items
* bug(editor): Fix nodeview canvas add button centering
* :mute: Fix linter errors
* bug(ManualTrigger Node): Fix manual trigger node execution
* fix(editor): Do not show canvas add button in execution or demo mode and prevent clicking if creator is open
* fix(editor): do not show pin data tooltip for manual trigger node
* fix(editor): do not use nodeViewOffset on zoomToFit
* :lipstick: Add margin for last node creator item and set font-weight to 700 for category title
* :sparkles: Position welcome note next to the added trigger node
* :bug: Remve always true welcome note
* feat(editor): Minor UI and UX tweaks (#4328)
* :lipstick: Make top viewport buttons less prominent
* :sparkles: Allow user to switch to all tabs if it contains filter results, move nodecreator state props to its own module
* :mute: Fix linting errors
* :mute: Fix linting errors
* :mute: Fix linting errors
* chore(build): Ping Turbo version to 1.5.5
* :lipstick: Minor traigger panel and node view style changes
* :speech_balloon: Update display name of execute workflow trigger
* feat(core, editor): Update subworkflow execution logic (#4269)
* :sparkles: Implement `findWorkflowStart`
* :zap: Extend `WorkflowOperationError`
* :zap: Add `WorkflowOperationError` to toast
* :blue_book: Extend interface
* :sparkles: Add `subworkflowExecutionError` to store
* :sparkles: Create `SubworkflowOperationError`
* :zap: Render subworkflow error as node error
* :truck: Move subworkflow start validation to `cli`
* :zap: Reset subworkflow execution error state
* :fire: Remove unused import
* :zap: Adjust CLI commands
* :fire: Remove unneeded check
* :fire: Remove stray log
* :zap: Simplify syntax
* :zap: Sort in case both Start and EWT present
* :recycle: Address Omar's feedback
* :fire: Remove unneeded lint exception
* :pencil2: Fix copy
* :shirt: Fix lint
* fix: moved find start node function to catchable place
Co-authored-by: Omar Ajoue <krynble@gmail.com>
* :lipstick: Change ExecuteWorkflow node to primary
* :sparkles: Allow user to navigate to all tab if it contains search results
* :bug: Fixed canvas control button while in demo, disable workflow activation for non-activavle nodes and revert zoomToFit bottom offset
* :fix: Do not chow request text if there's results
* :speech_balloon: Update noResults text
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
2022-10-18 05:23:22 -07:00
icon : 'fa:sign-in-alt' ,
2024-06-06 04:34:30 -07:00
iconColor : 'orange-red' ,
2019-12-19 14:07:55 -08:00
group : [ 'transform' ] ,
version : 1 ,
subtitle : '={{"Workflow: " + $parameter["workflowId"]}}' ,
description : 'Execute another workflow' ,
defaults : {
name : 'Execute Workflow' ,
color : '#ff6d5a' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
2023-02-28 06:51:33 -08:00
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'hidden' ,
noDataExpression : true ,
default : 'call_workflow' ,
options : [
{
name : 'Call Another Workflow' ,
value : 'call_workflow' ,
} ,
] ,
} ,
2020-01-02 15:13:53 -08:00
{
displayName : 'Source' ,
name : 'source' ,
type : 'options' ,
options : [
{
name : 'Database' ,
value : 'database' ,
2022-05-06 14:01:25 -07:00
description : 'Load the workflow from the database by ID' ,
2020-01-02 15:13:53 -08:00
} ,
{
name : 'Local File' ,
value : 'localFile' ,
2022-05-06 14:01:25 -07:00
description : 'Load the workflow from a locally saved file' ,
2020-01-02 15:13:53 -08:00
} ,
{
name : 'Parameter' ,
value : 'parameter' ,
2022-05-06 14:01:25 -07:00
description : 'Load the workflow from a parameter' ,
2020-01-02 15:13:53 -08:00
} ,
{
name : 'URL' ,
value : 'url' ,
2022-05-06 14:01:25 -07:00
description : 'Load the workflow from an URL' ,
2020-01-02 15:13:53 -08:00
} ,
] ,
default : 'database' ,
2022-05-06 14:01:25 -07:00
description : 'Where to get the workflow to execute from' ,
2020-01-02 15:13:53 -08:00
} ,
// ----------------------------------
// source:database
// ----------------------------------
2019-12-19 14:07:55 -08:00
{
2020-03-08 14:50:15 -07:00
displayName : 'Workflow ID' ,
2019-12-19 14:07:55 -08:00
name : 'workflowId' ,
2020-03-08 14:50:15 -07:00
type : 'string' ,
2020-01-02 15:13:53 -08:00
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
source : [ 'database' ] ,
2020-01-02 15:13:53 -08:00
} ,
} ,
2019-12-19 14:07:55 -08:00
default : '' ,
required : true ,
2023-10-06 08:04:33 -07:00
hint : 'Can be found in the URL of the workflow' ,
description :
"Note on using an expression here: if this node is set to run once with all items, they will all be sent to the <em>same</em> workflow. That workflow's ID will be calculated by evaluating the expression for the <strong>first input item</strong>." ,
2019-12-19 14:07:55 -08:00
} ,
2020-01-02 15:13:53 -08:00
// ----------------------------------
// source:localFile
// ----------------------------------
{
displayName : 'Workflow Path' ,
name : 'workflowPath' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
source : [ 'localFile' ] ,
2020-01-02 15:13:53 -08:00
} ,
} ,
default : '' ,
placeholder : '/data/workflow.json' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'The path to local JSON workflow file to execute' ,
2020-01-02 15:13:53 -08:00
} ,
// ----------------------------------
// source:parameter
// ----------------------------------
{
displayName : 'Workflow JSON' ,
name : 'workflowJson' ,
2023-12-29 01:49:27 -08:00
type : 'json' ,
2020-01-02 15:13:53 -08:00
typeOptions : {
rows : 10 ,
} ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
source : [ 'parameter' ] ,
2020-01-02 15:13:53 -08:00
} ,
} ,
default : '\n\n\n' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'The workflow JSON code to execute' ,
2020-01-02 15:13:53 -08:00
} ,
// ----------------------------------
// source:url
// ----------------------------------
{
displayName : 'Workflow URL' ,
name : 'workflowUrl' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
source : [ 'url' ] ,
2020-01-02 15:13:53 -08:00
} ,
} ,
default : '' ,
placeholder : 'https://example.com/workflow.json' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'The URL from which to load the workflow from' ,
2020-01-02 15:13:53 -08:00
} ,
2022-04-13 23:51:29 -07:00
{
2022-08-01 13:47:55 -07:00
displayName :
2022-12-05 01:08:06 -08:00
'Any data you pass into this node will be output by the Execute Workflow Trigger. <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflow/" target="_blank">More info</a>' ,
2022-04-13 23:51:29 -07:00
name : 'executeWorkflowNotice' ,
type : 'notice' ,
default : '' ,
} ,
2023-10-06 08:04:33 -07:00
{
displayName : 'Mode' ,
name : 'mode' ,
type : 'options' ,
noDataExpression : true ,
options : [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name : 'Run once with all items' ,
value : 'once' ,
description : 'Pass all items into a single execution of the sub-workflow' ,
} ,
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name : 'Run once for each item' ,
value : 'each' ,
description : 'Call the sub-workflow individually for each item' ,
} ,
] ,
default : 'once' ,
} ,
2024-01-19 05:31:54 -08:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
default : { } ,
placeholder : 'Add Option' ,
options : [
{
displayName : 'Wait For Sub-Workflow Completion' ,
name : 'waitForSubWorkflow' ,
type : 'boolean' ,
default : true ,
description :
'Whether the main workflow should wait for the sub-workflow to complete its execution before proceeding' ,
} ,
] ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-12-19 14:07:55 -08:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
2020-01-02 15:13:53 -08:00
const source = this . getNodeParameter ( 'source' , 0 ) as string ;
2023-10-06 08:04:33 -07:00
const mode = this . getNodeParameter ( 'mode' , 0 , false ) as string ;
const items = this . getInputData ( ) ;
2020-01-02 15:13:53 -08:00
2023-10-06 08:04:33 -07:00
if ( mode === 'each' ) {
2024-01-19 05:31:54 -08:00
let returnData : INodeExecutionData [ ] [ ] = [ ] ;
2021-07-19 23:58:54 -07:00
2023-10-06 08:04:33 -07:00
for ( let i = 0 ; i < items . length ; i ++ ) {
2021-07-19 23:58:54 -07:00
try {
2024-01-19 05:31:54 -08:00
const waitForSubWorkflow = this . getNodeParameter (
'options.waitForSubWorkflow' ,
i ,
true ,
) as boolean ;
2023-10-06 08:04:33 -07:00
const workflowInfo = await getWorkflowInfo . call ( this , source , i ) ;
2024-01-19 05:31:54 -08:00
if ( waitForSubWorkflow ) {
const workflowResult : INodeExecutionData [ ] [ ] = await this . executeWorkflow (
workflowInfo ,
[ items [ i ] ] ,
) ;
2023-10-06 08:04:33 -07:00
2024-01-19 05:31:54 -08:00
for ( const [ outputIndex , outputData ] of workflowResult . entries ( ) ) {
for ( const item of outputData ) {
item . pairedItem = { item : i } ;
}
if ( returnData [ outputIndex ] === undefined ) {
returnData [ outputIndex ] = [ ] ;
}
2023-10-06 08:04:33 -07:00
2024-01-19 05:31:54 -08:00
returnData [ outputIndex ] . push ( . . . outputData ) ;
}
} else {
void this . executeWorkflow ( workflowInfo , [ items [ i ] ] ) ;
returnData = [ items ] ;
2023-10-06 08:04:33 -07:00
}
2021-07-19 23:58:54 -07:00
} catch ( error ) {
2024-06-19 22:45:00 -07:00
if ( this . continueOnFail ( error ) ) {
2023-10-06 08:04:33 -07:00
return [ [ { json : { error : error.message } , pairedItem : { item : i } } ] ] ;
2021-07-19 23:58:54 -07:00
}
2023-10-06 08:04:33 -07:00
throw new NodeOperationError ( this . getNode ( ) , error , {
message : ` Error executing workflow with item at index ${ i } ` ,
description : error.message ,
itemIndex : i ,
} ) ;
2020-01-02 15:13:53 -08:00
}
2021-07-19 23:58:54 -07:00
}
2020-01-02 15:13:53 -08:00
2023-10-06 08:04:33 -07:00
return returnData ;
} else {
try {
2024-01-19 05:31:54 -08:00
const waitForSubWorkflow = this . getNodeParameter (
'options.waitForSubWorkflow' ,
0 ,
true ,
) as boolean ;
2023-10-06 08:04:33 -07:00
const workflowInfo = await getWorkflowInfo . call ( this , source ) ;
2024-01-19 05:31:54 -08:00
if ( ! waitForSubWorkflow ) {
void this . executeWorkflow ( workflowInfo , items ) ;
return [ items ] ;
}
2023-10-06 08:04:33 -07:00
const workflowResult : INodeExecutionData [ ] [ ] = await this . executeWorkflow (
workflowInfo ,
items ,
) ;
2020-01-02 15:13:53 -08:00
2024-04-17 01:57:51 -07:00
const fallbackPairedItemData = generatePairedItemData ( items . length ) ;
2021-07-19 23:58:54 -07:00
2023-10-06 08:04:33 -07:00
for ( const output of workflowResult ) {
2024-04-17 01:57:51 -07:00
const sameLength = output . length === items . length ;
for ( const [ itemIndex , item ] of output . entries ( ) ) {
if ( item . pairedItem ) continue ;
if ( sameLength ) {
item . pairedItem = { item : itemIndex } ;
} else {
item . pairedItem = fallbackPairedItemData ;
}
2023-10-06 08:04:33 -07:00
}
}
return workflowResult ;
} catch ( error ) {
const pairedItem = generatePairedItemData ( items . length ) ;
2024-06-19 22:45:00 -07:00
if ( this . continueOnFail ( error ) ) {
2023-10-06 08:04:33 -07:00
return [ [ { json : { error : error.message } , pairedItem } ] ] ;
}
throw error ;
}
2021-07-19 23:58:54 -07:00
}
2019-12-19 14:07:55 -08:00
}
}