2022-08-01 13:47:55 -07:00
import { readFile as fsReadFile } from 'fs/promises' ;
2020-01-02 15:13:53 -08:00
2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IExecuteFunctions ,
2020-10-01 05:01:39 -07:00
IExecuteWorkflowInfo ,
2019-12-19 14:07:55 -08:00
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
2020-01-02 15:13:53 -08:00
IWorkflowBase ,
2019-12-19 14:07:55 -08:00
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { NodeOperationError } from 'n8n-workflow' ;
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' ,
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 ,
2022-05-06 14:01:25 -07:00
description : 'The workflow to execute' ,
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' ,
type : 'string' ,
typeOptions : {
2021-12-23 02:41:46 -08:00
editor : 'json' ,
2020-01-02 15:13:53 -08:00
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 : '' ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-12-19 14:07:55 -08:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
2020-01-02 15:13:53 -08:00
const source = this . getNodeParameter ( 'source' , 0 ) as string ;
const workflowInfo : IExecuteWorkflowInfo = { } ;
2021-07-19 23:58:54 -07:00
try {
if ( source === 'database' ) {
// Read workflow from database
workflowInfo . id = this . getNodeParameter ( 'workflowId' , 0 ) as string ;
} else if ( source === 'localFile' ) {
// Read workflow from filesystem
const workflowPath = this . getNodeParameter ( 'workflowPath' , 0 ) as string ;
let workflowJson ;
try {
2022-12-02 12:54:28 -08:00
workflowJson = await fsReadFile ( workflowPath , { encoding : 'utf8' } ) ;
2021-07-19 23:58:54 -07:00
} catch ( error ) {
if ( error . code === 'ENOENT' ) {
2022-08-01 13:47:55 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
` The file " ${ workflowPath } " could not be found. ` ,
) ;
2021-07-19 23:58:54 -07:00
}
throw error ;
2020-01-02 15:13:53 -08:00
}
2021-07-19 23:58:54 -07:00
workflowInfo . code = JSON . parse ( workflowJson ) as IWorkflowBase ;
} else if ( source === 'parameter' ) {
// Read workflow from parameter
const workflowJson = this . getNodeParameter ( 'workflowJson' , 0 ) as string ;
workflowInfo . code = JSON . parse ( workflowJson ) as IWorkflowBase ;
} else if ( source === 'url' ) {
// Read workflow from url
const workflowUrl = this . getNodeParameter ( 'workflowUrl' , 0 ) as string ;
2020-01-02 15:13:53 -08:00
2021-07-19 23:58:54 -07:00
const requestOptions = {
headers : {
2022-08-01 13:47:55 -07:00
accept : 'application/json,text/*;q=0.99' ,
2021-07-19 23:58:54 -07:00
} ,
method : 'GET' ,
uri : workflowUrl ,
json : true ,
gzip : true ,
} ;
2020-01-02 15:13:53 -08:00
2021-07-19 23:58:54 -07:00
const response = await this . helpers . request ( requestOptions ) ;
workflowInfo . code = response ;
}
2020-01-02 15:13:53 -08:00
2021-07-19 23:58:54 -07:00
const receivedData = await this . executeWorkflow ( workflowInfo , items ) ;
2020-01-02 15:13:53 -08:00
2021-07-19 23:58:54 -07:00
return receivedData ;
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
2023-09-05 03:59:02 -07:00
return [ [ { json : { error : error.message } } ] ] ;
2021-07-19 23:58:54 -07:00
}
throw error ;
}
2019-12-19 14:07:55 -08:00
}
}