2019-06-23 03:35:23 -07:00
import {
IContextObject ,
INode ,
2020-10-22 06:46:03 -07:00
INodeCredentialDescription ,
2019-06-23 03:35:23 -07:00
INodeExecutionData ,
INodeIssueObjectProperty ,
2020-10-22 06:46:03 -07:00
INodeIssues ,
2019-06-23 03:35:23 -07:00
INodeParameters ,
INodeProperties ,
INodePropertyCollection ,
INodeType ,
2019-07-13 10:50:41 -07:00
IParameterDependencies ,
2019-06-23 03:35:23 -07:00
IRunExecutionData ,
IWebhookData ,
2019-07-13 10:50:41 -07:00
IWorkflowExecuteAdditionalData ,
2019-06-23 03:35:23 -07:00
NodeParameterValue ,
WebhookHttpMethod ,
} from './Interfaces' ;
import {
Workflow
} from './Workflow' ;
2021-05-19 16:07:26 -07:00
import { get , isEqual } from 'lodash' ;
2019-06-23 03:35:23 -07:00
2019-12-31 12:19:37 -08:00
/ * *
* Gets special parameters which should be added to nodeTypes depending
* on their type or configuration
*
* @export
* @param { INodeType } nodeType
* @returns
* /
export function getSpecialNodeParameters ( nodeType : INodeType ) {
if ( nodeType . description . polling === true ) {
return [
{
displayName : 'Poll Times' ,
name : 'pollTimes' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
multipleValueButtonText : 'Add Poll Time' ,
} ,
default : { } ,
description : 'Time at which polling should occur.' ,
placeholder : 'Add Poll Time' ,
options : [
{
name : 'item' ,
displayName : 'Item' ,
values : [
{
displayName : 'Mode' ,
name : 'mode' ,
type : 'options' ,
options : [
{
name : 'Every Minute' ,
value : 'everyMinute' ,
} ,
{
name : 'Every Hour' ,
value : 'everyHour' ,
} ,
{
name : 'Every Day' ,
value : 'everyDay' ,
} ,
{
name : 'Every Week' ,
value : 'everyWeek' ,
} ,
{
name : 'Every Month' ,
value : 'everyMonth' ,
} ,
2019-12-31 15:41:47 -08:00
{
name : 'Every X' ,
value : 'everyX' ,
} ,
2019-12-31 12:19:37 -08:00
{
name : 'Custom' ,
value : 'custom' ,
} ,
] ,
default : 'everyDay' ,
description : 'How often to trigger.' ,
} ,
{
displayName : 'Hour' ,
name : 'hour' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 23 ,
} ,
displayOptions : {
hide : {
mode : [
'custom' ,
'everyHour' ,
'everyMinute' ,
2019-12-31 15:41:47 -08:00
'everyX' ,
2019-12-31 12:19:37 -08:00
] ,
} ,
} ,
default : 14 ,
description : 'The hour of the day to trigger (24h format).' ,
} ,
{
displayName : 'Minute' ,
name : 'minute' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 59 ,
} ,
displayOptions : {
hide : {
mode : [
'custom' ,
'everyMinute' ,
2019-12-31 15:41:47 -08:00
'everyX' ,
2019-12-31 12:19:37 -08:00
] ,
} ,
} ,
default : 0 ,
description : 'The minute of the day to trigger.' ,
} ,
{
displayName : 'Day of Month' ,
name : 'dayOfMonth' ,
type : 'number' ,
displayOptions : {
show : {
mode : [
'everyMonth' ,
] ,
} ,
} ,
typeOptions : {
minValue : 1 ,
maxValue : 31 ,
} ,
default : 1 ,
description : 'The day of the month to trigger.' ,
} ,
{
displayName : 'Weekday' ,
name : 'weekday' ,
type : 'options' ,
displayOptions : {
show : {
mode : [
'everyWeek' ,
] ,
} ,
} ,
options : [
{
name : 'Monday' ,
value : '1' ,
} ,
{
name : 'Tuesday' ,
value : '2' ,
} ,
{
name : 'Wednesday' ,
value : '3' ,
} ,
{
name : 'Thursday' ,
value : '4' ,
} ,
{
name : 'Friday' ,
value : '5' ,
} ,
{
name : 'Saturday' ,
value : '6' ,
} ,
{
name : 'Sunday' ,
value : '0' ,
} ,
] ,
default : '1' ,
description : 'The weekday to trigger.' ,
} ,
{
displayName : 'Cron Expression' ,
name : 'cronExpression' ,
type : 'string' ,
displayOptions : {
show : {
mode : [
'custom' ,
] ,
} ,
} ,
default : '* * * * * *' ,
description : 'Use custom cron expression. Values and ranges as follows:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>' ,
} ,
2019-12-31 15:41:47 -08:00
{
displayName : 'Value' ,
name : 'value' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 1000 ,
} ,
displayOptions : {
show : {
mode : [
'everyX' ,
] ,
} ,
} ,
default : 2 ,
description : 'All how many X minutes/hours it should trigger.' ,
} ,
{
displayName : 'Unit' ,
name : 'unit' ,
type : 'options' ,
displayOptions : {
show : {
mode : [
'everyX' ,
] ,
} ,
} ,
options : [
{
name : 'Minutes' ,
2020-10-22 06:46:03 -07:00
value : 'minutes' ,
2019-12-31 15:41:47 -08:00
} ,
{
name : 'Hours' ,
2020-10-22 06:46:03 -07:00
value : 'hours' ,
2019-12-31 15:41:47 -08:00
} ,
] ,
default : 'hours' ,
description : 'If it should trigger all X minutes or hours.' ,
} ,
2019-12-31 12:19:37 -08:00
] ,
} ,
] ,
} ,
] ;
}
return [ ] ;
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns if the parameter should be displayed or not
*
* @export
* @param { INodeParameters } nodeValues The data on the node which decides if the parameter
* should be displayed
* @param { ( INodeProperties | INodeCredentialDescription ) } parameter The parameter to check if it should be displayed
* @param { INodeParameters } [ nodeValuesRoot ] The root node - parameter - data
* @returns
* /
export function displayParameter ( nodeValues : INodeParameters , parameter : INodeProperties | INodeCredentialDescription , nodeValuesRoot? : INodeParameters ) {
if ( ! parameter . displayOptions ) {
return true ;
}
nodeValuesRoot = nodeValuesRoot || nodeValues ;
let value ;
2020-05-08 10:49:57 -07:00
const values : any [ ] = [ ] ; // tslint:disable-line:no-any
2019-06-23 03:35:23 -07:00
if ( parameter . displayOptions . show ) {
// All the defined rules have to match to display parameter
for ( const propertyName of Object . keys ( parameter . displayOptions . show ) ) {
if ( propertyName . charAt ( 0 ) === '/' ) {
// Get the value from the root of the node
2020-01-10 10:57:01 -08:00
value = get ( nodeValuesRoot , propertyName . slice ( 1 ) ) ;
2019-06-23 03:35:23 -07:00
} else {
// Get the value from current level
2020-01-10 10:57:01 -08:00
value = get ( nodeValues , propertyName ) ;
2019-06-23 03:35:23 -07:00
}
2020-05-08 10:49:57 -07:00
values . length = 0 ;
if ( ! Array . isArray ( value ) ) {
values . push ( value ) ;
} else {
values . push . apply ( values , value ) ;
}
2021-05-15 15:51:14 -07:00
if ( values . some ( v = > ( typeof v ) === 'string' && ( v as string ) . charAt ( 0 ) === '=' ) ) {
return true ;
}
2020-05-08 10:49:57 -07:00
if ( values . length === 0 || ! parameter . displayOptions . show [ propertyName ] . some ( v = > values . includes ( v ) ) ) {
2019-06-23 03:35:23 -07:00
return false ;
}
}
}
if ( parameter . displayOptions . hide ) {
2021-08-29 04:58:20 -07:00
// Any of the defined hide rules have to match to hide the parameter
2019-06-23 03:35:23 -07:00
for ( const propertyName of Object . keys ( parameter . displayOptions . hide ) ) {
if ( propertyName . charAt ( 0 ) === '/' ) {
// Get the value from the root of the node
2020-01-10 10:57:01 -08:00
value = get ( nodeValuesRoot , propertyName . slice ( 1 ) ) ;
2019-06-23 03:35:23 -07:00
} else {
// Get the value from current level
2020-01-10 10:57:01 -08:00
value = get ( nodeValues , propertyName ) ;
2019-06-23 03:35:23 -07:00
}
2020-05-08 10:49:57 -07:00
values . length = 0 ;
if ( ! Array . isArray ( value ) ) {
values . push ( value ) ;
} else {
values . push . apply ( values , value ) ;
}
if ( values . length !== 0 && parameter . displayOptions . hide [ propertyName ] . some ( v = > values . includes ( v ) ) ) {
2019-06-23 03:35:23 -07:00
return false ;
}
}
}
return true ;
}
/ * *
* Returns if the given parameter should be displayed or not considering the path
* to the properties
*
* @export
* @param { INodeParameters } nodeValues The data on the node which decides if the parameter
* should be displayed
* @param { ( INodeProperties | INodeCredentialDescription ) } parameter The parameter to check if it should be displayed
* @param { string } path The path to the property
* @returns
* /
export function displayParameterPath ( nodeValues : INodeParameters , parameter : INodeProperties | INodeCredentialDescription , path : string ) {
let resolvedNodeValues = nodeValues ;
if ( path !== '' ) {
resolvedNodeValues = get (
nodeValues ,
2020-10-22 09:00:28 -07:00
path ,
2019-06-23 03:35:23 -07:00
) as INodeParameters ;
}
// Get the root parameter data
let nodeValuesRoot = nodeValues ;
if ( path && path . split ( '.' ) . indexOf ( 'parameters' ) === 0 ) {
nodeValuesRoot = get (
nodeValues ,
2020-10-22 09:00:28 -07:00
'parameters' ,
2019-06-23 03:35:23 -07:00
) as INodeParameters ;
}
return displayParameter ( resolvedNodeValues , parameter , nodeValuesRoot ) ;
}
/ * *
* Returns the context data
*
* @export
* @param { IRunExecutionData } runExecutionData The run execution data
* @param { string } type The data type . "node" / "flow"
* @param { INode } [ node ] If type "node" is set the node to return the context of has to be supplied
* @returns { IContextObject }
* /
export function getContext ( runExecutionData : IRunExecutionData , type : string , node? : INode ) : IContextObject {
if ( runExecutionData . executionData === undefined ) {
// TODO: Should not happen leave it for test now
throw new Error ( 'The "executionData" is not initialized!' ) ;
}
let key : string ;
if ( type === 'flow' ) {
key = 'flow' ;
} else if ( type === 'node' ) {
if ( node === undefined ) {
throw new Error ( ` The request data of context type "node" the node parameter has to be set! ` ) ;
}
key = ` node: ${ node . name } ` ;
} else {
throw new Error ( ` The context type " ${ type } " is not know. Only "flow" and node" are supported! ` ) ;
}
if ( runExecutionData . executionData . contextData [ key ] === undefined ) {
runExecutionData . executionData . contextData [ key ] = { } ;
}
return runExecutionData . executionData . contextData [ key ] ;
}
2019-07-13 10:50:41 -07:00
/ * *
* Returns which parameters are dependent on which
*
* @export
* @param { INodeProperties [ ] } nodePropertiesArray
* @returns { IParameterDependencies }
* /
export function getParamterDependencies ( nodePropertiesArray : INodeProperties [ ] ) : IParameterDependencies {
const dependencies : IParameterDependencies = { } ;
let displayRule : string ;
let parameterName : string ;
for ( const nodeProperties of nodePropertiesArray ) {
if ( dependencies [ nodeProperties . name ] === undefined ) {
dependencies [ nodeProperties . name ] = [ ] ;
}
if ( nodeProperties . displayOptions === undefined ) {
// Does not have any dependencies
continue ;
}
for ( displayRule of Object . keys ( nodeProperties . displayOptions ) ) {
// @ts-ignore
for ( parameterName of Object . keys ( nodeProperties . displayOptions [ displayRule ] ) ) {
if ( ! dependencies [ nodeProperties . name ] . includes ( parameterName ) ) {
dependencies [ nodeProperties . name ] . push ( parameterName ) ;
}
}
}
}
return dependencies ;
}
/ * *
* Returns in which order the parameters should be resolved
2021-08-29 04:58:20 -07:00
* to have the parameters available they depend on
2019-07-13 10:50:41 -07:00
*
* @export
* @param { INodeProperties [ ] } nodePropertiesArray
* @param { IParameterDependencies } parameterDependencies
* @returns { number [ ] }
* /
2021-08-29 04:58:20 -07:00
export function getParameterResolveOrder ( nodePropertiesArray : INodeProperties [ ] , parameterDependencies : IParameterDependencies ) : number [ ] {
2019-07-13 10:50:41 -07:00
const executionOrder : number [ ] = [ ] ;
const indexToResolve = Array . from ( { length : nodePropertiesArray.length } , ( v , k ) = > k ) ;
const resolvedParamters : string [ ] = [ ] ;
let index : number ;
let property : INodeProperties ;
let lastIndexLength = indexToResolve . length ;
let lastIndexReduction = - 1 ;
let itterations = 0 ;
while ( indexToResolve . length !== 0 ) {
itterations += 1 ;
index = indexToResolve . shift ( ) as number ;
property = nodePropertiesArray [ index ] ;
if ( parameterDependencies [ property . name ] . length === 0 ) {
// Does not have any dependencies so simply add
executionOrder . push ( index ) ;
resolvedParamters . push ( property . name ) ;
continue ;
}
// Parameter has dependencies
for ( const dependency of parameterDependencies [ property . name ] ) {
if ( ! resolvedParamters . includes ( dependency ) ) {
if ( dependency . charAt ( 0 ) === '/' ) {
// Assume that root level depenencies are resolved
continue ;
}
// Dependencies for that paramter are still missing so
// try to add again later
indexToResolve . push ( index ) ;
continue ;
}
}
// All dependencies got found so add
executionOrder . push ( index ) ;
resolvedParamters . push ( property . name ) ;
if ( indexToResolve . length < lastIndexLength ) {
lastIndexReduction = itterations ;
}
if ( itterations > lastIndexReduction + nodePropertiesArray . length ) {
2021-08-29 04:58:20 -07:00
throw new Error ( 'Could not resolve parameter depenencies. Max iterations reached! Hint: If `displayOptions` are specified in any child parameter of a parent `collection` or `fixedCollection`, remove the `displayOptions` from the child parameter.' ) ;
2019-07-13 10:50:41 -07:00
}
lastIndexLength = indexToResolve . length ;
}
return executionOrder ;
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns the node parameter values . Depending on the settings it either just returns the none
* default values or it applies all the default values .
*
* @export
* @param { INodeProperties [ ] } nodePropertiesArray The properties which exist and their settings
* @param { INodeParameters } nodeValues The node parameter data
* @param { boolean } returnDefaults If default values get added or only none default values returned
* @param { boolean } returnNoneDisplayed If also values which should not be displayed should be returned
* @param { boolean } [ onlySimpleTypes = false ] If only simple types should be resolved
* @param { boolean } [ dataIsResolved = false ] If nodeValues are already fully resolved ( so that all default values got added already )
* @param { INodeParameters } [ nodeValuesRoot ] The root node - parameter - data
* @returns { ( INodeParameters | null ) }
* /
2019-07-13 10:50:41 -07:00
export function getNodeParameters ( nodePropertiesArray : INodeProperties [ ] , nodeValues : INodeParameters , returnDefaults : boolean , returnNoneDisplayed : boolean , onlySimpleTypes = false , dataIsResolved = false , nodeValuesRoot? : INodeParameters , parentType? : string , parameterDependencies? : IParameterDependencies ) : INodeParameters | null {
if ( parameterDependencies === undefined ) {
parameterDependencies = getParamterDependencies ( nodePropertiesArray ) ;
}
2019-07-12 11:58:52 -07:00
// Get the parameter names which get used multiple times as for this
// ones we have to always check which ones get displayed and which ones not
const duplicateParameterNames : string [ ] = [ ] ;
const parameterNames : string [ ] = [ ] ;
for ( const nodeProperties of nodePropertiesArray ) {
if ( parameterNames . includes ( nodeProperties . name ) ) {
if ( ! duplicateParameterNames . includes ( nodeProperties . name ) ) {
duplicateParameterNames . push ( nodeProperties . name ) ;
}
} else {
parameterNames . push ( nodeProperties . name ) ;
}
}
2019-06-23 03:35:23 -07:00
const nodeParameters : INodeParameters = { } ;
2019-07-13 10:50:41 -07:00
const nodeParametersFull : INodeParameters = { } ;
2019-06-23 03:35:23 -07:00
2019-07-13 10:50:41 -07:00
let nodeValuesDisplayCheck = nodeParametersFull ;
2019-06-23 03:35:23 -07:00
if ( dataIsResolved !== true && returnNoneDisplayed === false ) {
2019-07-13 10:50:41 -07:00
nodeValuesDisplayCheck = getNodeParameters ( nodePropertiesArray , nodeValues , true , true , true , true , nodeValuesRoot , parentType , parameterDependencies ) as INodeParameters ;
2019-06-23 03:35:23 -07:00
}
nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck ;
2019-07-13 10:50:41 -07:00
// Go through the parameters in order of their dependencies
2021-08-29 04:58:20 -07:00
const parameterItterationOrderIndex = getParameterResolveOrder ( nodePropertiesArray , parameterDependencies ) ;
2019-07-13 10:50:41 -07:00
for ( const parameterIndex of parameterItterationOrderIndex ) {
const nodeProperties = nodePropertiesArray [ parameterIndex ] ;
2019-06-23 03:35:23 -07:00
if ( nodeValues [ nodeProperties . name ] === undefined && ( returnDefaults === false || parentType === 'collection' ) ) {
// The value is not defined so go to the next
continue ;
}
if ( returnNoneDisplayed === false && ! displayParameter ( nodeValuesDisplayCheck , nodeProperties , nodeValuesRoot ) ) {
if ( returnNoneDisplayed === false ) {
continue ;
}
if ( returnDefaults === false ) {
continue ;
}
}
if ( ! [ 'collection' , 'fixedCollection' ] . includes ( nodeProperties . type ) ) {
// Is a simple property so can be set as it is
2019-07-12 11:58:52 -07:00
if ( duplicateParameterNames . includes ( nodeProperties . name ) ) {
if ( ! displayParameter ( nodeValuesDisplayCheck , nodeProperties , nodeValuesRoot ) ) {
continue ;
}
}
2019-06-23 03:35:23 -07:00
if ( returnDefaults === true ) {
// Set also when it has the default value
2019-11-30 15:24:25 -08:00
if ( [ 'boolean' , 'number' , 'options' ] . includes ( nodeProperties . type ) ) {
// Boolean, numbers and options are special as false and 0 are valid values
2019-06-23 03:35:23 -07:00
// and should not be replaced with default value
nodeParameters [ nodeProperties . name ] = nodeValues [ nodeProperties . name ] !== undefined ? nodeValues [ nodeProperties . name ] : nodeProperties . default ;
} else {
nodeParameters [ nodeProperties . name ] = nodeValues [ nodeProperties . name ] || nodeProperties . default ;
}
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2021-05-19 16:07:26 -07:00
} else if ( ( nodeValues [ nodeProperties . name ] !== nodeProperties . default && typeof nodeValues [ nodeProperties . name ] !== 'object' ) ||
( typeof nodeValues [ nodeProperties . name ] === 'object' && ! isEqual ( nodeValues [ nodeProperties . name ] , nodeProperties . default ) ) ||
( nodeValues [ nodeProperties . name ] !== undefined && parentType === 'collection' ) ) {
2019-06-23 03:35:23 -07:00
// Set only if it is different to the default value
nodeParameters [ nodeProperties . name ] = nodeValues [ nodeProperties . name ] ;
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
continue ;
}
}
if ( onlySimpleTypes === true ) {
// It is only supposed to resolve the simple types. So continue.
continue ;
}
// Is a complex property so check lower levels
let tempValue : INodeParameters | null ;
if ( nodeProperties . type === 'collection' ) {
// Is collection
if ( nodeProperties . typeOptions !== undefined && nodeProperties . typeOptions . multipleValues === true ) {
// Multiple can be set so will be an array
// Return directly the values like they are
if ( nodeValues [ nodeProperties . name ] !== undefined ) {
nodeParameters [ nodeProperties . name ] = nodeValues [ nodeProperties . name ] ;
} else if ( returnDefaults === true ) {
2021-05-19 16:44:27 -07:00
// Does not have values defined but defaults should be returned
2021-05-19 17:26:29 -07:00
if ( Array . isArray ( nodeProperties . default ) ) {
nodeParameters [ nodeProperties . name ] = JSON . parse ( JSON . stringify ( nodeProperties . default ) ) ;
} else {
// As it is probably wrong for many nodes, do we keep on returning an empty array if
// anything else than an array is set as default
nodeParameters [ nodeProperties . name ] = [ ] ;
}
2019-06-23 03:35:23 -07:00
}
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
} else {
if ( nodeValues [ nodeProperties . name ] !== undefined ) {
// Has values defined so get them
const tempNodeParameters = getNodeParameters ( nodeProperties . options as INodeProperties [ ] , nodeValues [ nodeProperties . name ] as INodeParameters , returnDefaults , returnNoneDisplayed , false , false , nodeValuesRoot , nodeProperties . type ) ;
if ( tempNodeParameters !== null ) {
nodeParameters [ nodeProperties . name ] = tempNodeParameters ;
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
}
} else if ( returnDefaults === true ) {
// Does not have values defined but defaults should be returned
nodeParameters [ nodeProperties . name ] = JSON . parse ( JSON . stringify ( nodeProperties . default ) ) ;
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
}
}
} else if ( nodeProperties . type === 'fixedCollection' ) {
// Is fixedCollection
const collectionValues : INodeParameters = { } ;
let tempNodeParameters : INodeParameters ;
let tempNodePropertiesArray : INodeProperties [ ] ;
let nodePropertyOptions : INodePropertyCollection | undefined ;
let propertyValues = nodeValues [ nodeProperties . name ] ;
if ( returnDefaults === true ) {
if ( propertyValues === undefined ) {
propertyValues = JSON . parse ( JSON . stringify ( nodeProperties . default ) ) ;
}
}
2021-01-23 11:00:32 -08:00
// Iterate over all collections
2020-10-26 01:26:07 -07:00
for ( const itemName of Object . keys ( propertyValues || { } ) ) {
2019-06-23 03:35:23 -07:00
if ( nodeProperties . typeOptions !== undefined && nodeProperties . typeOptions . multipleValues === true ) {
// Multiple can be set so will be an array
const tempArrayValue : INodeParameters [ ] = [ ] ;
2021-01-23 11:00:32 -08:00
// Iterate over all items as it contains multiple ones
2019-06-23 03:35:23 -07:00
for ( const nodeValue of ( propertyValues as INodeParameters ) [ itemName ] as INodeParameters [ ] ) {
nodePropertyOptions = nodeProperties ! . options ! . find ( ( nodePropertyOptions ) = > nodePropertyOptions . name === itemName ) as INodePropertyCollection ;
if ( nodePropertyOptions === undefined ) {
throw new Error ( ` Could not find property option " ${ itemName } " for " ${ nodeProperties . name } " ` ) ;
}
tempNodePropertiesArray = ( nodePropertyOptions as INodePropertyCollection ) . values ! ;
tempValue = getNodeParameters ( tempNodePropertiesArray , nodeValue as INodeParameters , returnDefaults , returnNoneDisplayed , false , false , nodeValuesRoot , nodeProperties . type ) ;
if ( tempValue !== null ) {
tempArrayValue . push ( tempValue ) ;
}
}
collectionValues [ itemName ] = tempArrayValue ;
} else {
// Only one can be set so is an object of objects
tempNodeParameters = { } ;
// Get the options of the current item
const nodePropertyOptions = nodeProperties ! . options ! . find ( ( data ) = > data . name === itemName ) ;
if ( nodePropertyOptions !== undefined ) {
tempNodePropertiesArray = ( nodePropertyOptions as INodePropertyCollection ) . values ! ;
tempValue = getNodeParameters ( tempNodePropertiesArray , ( nodeValues [ nodeProperties . name ] as INodeParameters ) [ itemName ] as INodeParameters , returnDefaults , returnNoneDisplayed , false , false , nodeValuesRoot , nodeProperties . type ) ;
if ( tempValue !== null ) {
Object . assign ( tempNodeParameters , tempValue ) ;
}
}
if ( Object . keys ( tempNodeParameters ) . length !== 0 ) {
collectionValues [ itemName ] = tempNodeParameters ;
}
}
}
if ( Object . keys ( collectionValues ) . length !== 0 || returnDefaults === true ) {
// Set only if value got found
if ( returnDefaults === true ) {
// Set also when it has the default value
if ( collectionValues === undefined ) {
nodeParameters [ nodeProperties . name ] = JSON . parse ( JSON . stringify ( nodeProperties . default ) ) ;
} else {
nodeParameters [ nodeProperties . name ] = collectionValues ;
}
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
} else if ( collectionValues !== nodeProperties . default ) {
// Set only if values got found and it is not the default
nodeParameters [ nodeProperties . name ] = collectionValues ;
2019-07-13 10:50:41 -07:00
nodeParametersFull [ nodeProperties . name ] = nodeParameters [ nodeProperties . name ] ;
2019-06-23 03:35:23 -07:00
}
}
}
}
return nodeParameters ;
}
/ * *
* Brings the output data in a format that can be returned from a node
*
* @export
* @param { INodeExecutionData [ ] } outputData
* @param { number } [ outputIndex = 0 ]
* @returns { Promise < INodeExecutionData [ ] [ ] > }
* /
export async function prepareOutputData ( outputData : INodeExecutionData [ ] , outputIndex = 0 ) : Promise < INodeExecutionData [ ] [ ] > {
// TODO: Check if node has output with that index
const returnData = [ ] ;
for ( let i = 0 ; i < outputIndex ; i ++ ) {
returnData . push ( [ ] ) ;
}
returnData . push ( outputData ) ;
return returnData ;
}
/ * *
* Returns all the webhooks which should be created for the give node
*
* @export
*
* @param { INode } node
* @returns { IWebhookData [ ] }
* /
2021-08-21 05:11:32 -07:00
export function getNodeWebhooks ( workflow : Workflow , node : INode , additionalData : IWorkflowExecuteAdditionalData , ignoreRestartWehbooks = false ) : IWebhookData [ ] {
2019-06-23 03:35:23 -07:00
if ( node . disabled === true ) {
// Node is disabled so webhooks will also not be enabled
return [ ] ;
}
const nodeType = workflow . nodeTypes . getByName ( node . type ) as INodeType ;
if ( nodeType . description . webhooks === undefined ) {
// Node does not have any webhooks so return
return [ ] ;
}
2020-05-03 08:55:14 -07:00
const workflowId = workflow . id || '__UNSAVED__' ;
2021-01-29 00:31:40 -08:00
const mode = 'internal' ;
2020-05-03 08:55:14 -07:00
2019-06-23 03:35:23 -07:00
const returnData : IWebhookData [ ] = [ ] ;
for ( const webhookDescription of nodeType . description . webhooks ) {
2021-08-21 05:11:32 -07:00
if ( ignoreRestartWehbooks === true && webhookDescription . restartWebhook === true ) {
continue ;
}
let nodeWebhookPath = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'path' ] , mode , { } ) ;
2019-06-23 03:35:23 -07:00
if ( nodeWebhookPath === undefined ) {
// TODO: Use a proper logger
2020-05-03 08:55:14 -07:00
console . error ( ` No webhook path could be found for node " ${ node . name } " in workflow " ${ workflowId } ". ` ) ;
2019-06-23 03:35:23 -07:00
continue ;
}
2019-08-01 09:22:48 -07:00
nodeWebhookPath = nodeWebhookPath . toString ( ) ;
2021-02-09 00:14:40 -08:00
if ( nodeWebhookPath . startsWith ( '/' ) ) {
2019-06-23 03:35:23 -07:00
nodeWebhookPath = nodeWebhookPath . slice ( 1 ) ;
}
2021-02-09 00:14:40 -08:00
if ( nodeWebhookPath . endsWith ( '/' ) ) {
nodeWebhookPath = nodeWebhookPath . slice ( 0 , - 1 ) ;
}
2019-06-23 03:35:23 -07:00
2021-08-21 05:11:32 -07:00
const isFullPath : boolean = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'isFullPath' ] , 'internal' , { } , false ) as boolean ;
const restartWebhook : boolean = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'restartWebhook' ] , 'internal' , { } , false ) as boolean ;
const path = getNodeWebhookPath ( workflowId , node , nodeWebhookPath , isFullPath , restartWebhook ) ;
2019-06-23 03:35:23 -07:00
2021-08-21 05:11:32 -07:00
const httpMethod = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'httpMethod' ] , mode , { } , 'GET' ) ;
2019-06-23 03:35:23 -07:00
if ( httpMethod === undefined ) {
// TODO: Use a proper logger
2020-05-03 08:55:14 -07:00
console . error ( ` The webhook " ${ path } " for node " ${ node . name } " in workflow " ${ workflowId } " could not be added because the httpMethod is not defined. ` ) ;
2019-06-23 03:35:23 -07:00
continue ;
}
2021-01-23 11:00:32 -08:00
let webhookId : string | undefined ;
if ( ( path . startsWith ( ':' ) || path . includes ( '/:' ) ) && node . webhookId ) {
webhookId = node . webhookId ;
}
2019-06-23 03:35:23 -07:00
returnData . push ( {
2019-08-01 09:22:48 -07:00
httpMethod : httpMethod.toString ( ) as WebhookHttpMethod ,
2019-06-23 03:35:23 -07:00
node : node.name ,
path ,
webhookDescription ,
2020-05-03 08:55:14 -07:00
workflowId ,
2019-06-23 03:35:23 -07:00
workflowExecuteAdditionalData : additionalData ,
2021-01-23 11:00:32 -08:00
webhookId ,
2019-06-23 03:35:23 -07:00
} ) ;
}
return returnData ;
}
2020-05-27 16:32:49 -07:00
export function getNodeWebhooksBasic ( workflow : Workflow , node : INode ) : IWebhookData [ ] {
if ( node . disabled === true ) {
// Node is disabled so webhooks will also not be enabled
return [ ] ;
}
const nodeType = workflow . nodeTypes . getByName ( node . type ) as INodeType ;
if ( nodeType . description . webhooks === undefined ) {
// Node does not have any webhooks so return
return [ ] ;
}
const workflowId = workflow . id || '__UNSAVED__' ;
2021-01-29 00:31:40 -08:00
const mode = 'internal' ;
2020-05-27 16:32:49 -07:00
const returnData : IWebhookData [ ] = [ ] ;
for ( const webhookDescription of nodeType . description . webhooks ) {
2021-08-21 05:11:32 -07:00
let nodeWebhookPath = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'path' ] , mode , { } ) ;
2020-05-27 16:32:49 -07:00
if ( nodeWebhookPath === undefined ) {
// TODO: Use a proper logger
console . error ( ` No webhook path could be found for node " ${ node . name } " in workflow " ${ workflowId } ". ` ) ;
continue ;
}
nodeWebhookPath = nodeWebhookPath . toString ( ) ;
2021-02-09 00:14:40 -08:00
if ( nodeWebhookPath . startsWith ( '/' ) ) {
2020-05-27 16:32:49 -07:00
nodeWebhookPath = nodeWebhookPath . slice ( 1 ) ;
}
2021-02-09 00:14:40 -08:00
if ( nodeWebhookPath . endsWith ( '/' ) ) {
nodeWebhookPath = nodeWebhookPath . slice ( 0 , - 1 ) ;
}
2020-05-27 16:32:49 -07:00
2021-08-21 05:11:32 -07:00
const isFullPath : boolean = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'isFullPath' ] , mode , { } , false ) as boolean ;
2020-05-27 16:32:49 -07:00
2020-06-10 06:39:15 -07:00
const path = getNodeWebhookPath ( workflowId , node , nodeWebhookPath , isFullPath ) ;
2021-08-21 05:11:32 -07:00
const httpMethod = workflow . expression . getSimpleParameterValue ( node , webhookDescription [ 'httpMethod' ] , mode , { } ) ;
2020-05-27 16:32:49 -07:00
if ( httpMethod === undefined ) {
// TODO: Use a proper logger
console . error ( ` The webhook " ${ path } " for node " ${ node . name } " in workflow " ${ workflowId } " could not be added because the httpMethod is not defined. ` ) ;
continue ;
}
//@ts-ignore
returnData . push ( {
httpMethod : httpMethod.toString ( ) as WebhookHttpMethod ,
node : node.name ,
path ,
webhookDescription ,
workflowId ,
} ) ;
}
return returnData ;
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns the webhook path
*
* @export
* @param { string } workflowId
* @param { string } nodeTypeName
* @param { string } path
* @returns { string }
* /
2021-08-21 05:11:32 -07:00
export function getNodeWebhookPath ( workflowId : string , node : INode , path : string , isFullPath? : boolean , restartWebhook? : boolean ) : string {
2020-05-27 16:32:49 -07:00
let webhookPath = '' ;
2021-08-21 05:11:32 -07:00
if ( restartWebhook === true ) {
return path ;
} else if ( node . webhookId === undefined ) {
2020-05-27 16:32:49 -07:00
webhookPath = ` ${ workflowId } / ${ encodeURIComponent ( node . name . toLowerCase ( ) ) } / ${ path } ` ;
} else {
2020-06-10 06:39:15 -07:00
if ( isFullPath === true ) {
2020-05-27 16:32:49 -07:00
return path ;
}
2020-06-10 07:17:16 -07:00
webhookPath = ` ${ node . webhookId } / ${ path } ` ;
2020-05-27 16:32:49 -07:00
}
return webhookPath ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns the webhook URL
*
* @export
* @param { string } baseUrl
* @param { string } workflowId
* @param { string } nodeTypeName
* @param { string } path
2020-06-10 06:39:15 -07:00
* @param { boolean } isFullPath
2019-06-23 03:35:23 -07:00
* @returns { string }
* /
2020-06-10 06:39:15 -07:00
export function getNodeWebhookUrl ( baseUrl : string , workflowId : string , node : INode , path : string , isFullPath? : boolean ) : string {
2021-01-23 11:00:32 -08:00
if ( ( path . startsWith ( ':' ) || path . includes ( '/:' ) ) && node . webhookId ) {
// setting this to false to prefix the webhookId
isFullPath = false ;
}
if ( path . startsWith ( '/' ) ) {
path = path . slice ( 1 ) ;
}
2020-06-10 06:39:15 -07:00
return ` ${ baseUrl } / ${ getNodeWebhookPath ( workflowId , node , path , isFullPath ) } ` ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns all the parameter - issues of the node
*
* @export
* @param { INodeProperties [ ] } nodePropertiesArray The properties of the node
* @param { INode } node The data of the node
* @returns { ( INodeIssues | null ) }
* /
export function getNodeParametersIssues ( nodePropertiesArray : INodeProperties [ ] , node : INode ) : INodeIssues | null {
const foundIssues : INodeIssues = { } ;
let propertyIssues : INodeIssues ;
2019-07-07 10:17:34 -07:00
if ( node . disabled === true ) {
// Ignore issues on disabled nodes
return null ;
}
2019-06-23 03:35:23 -07:00
for ( const nodeProperty of nodePropertiesArray ) {
propertyIssues = getParameterIssues ( nodeProperty , node . parameters , '' ) ;
mergeIssues ( foundIssues , propertyIssues ) ;
}
if ( Object . keys ( foundIssues ) . length === 0 ) {
return null ;
}
return foundIssues ;
}
/ * *
* Returns the issues of the node as string
*
* @export
* @param { INodeIssues } issues The issues of the node
* @param { INode } node The node
* @returns { string [ ] }
* /
export function nodeIssuesToString ( issues : INodeIssues , node? : INode ) : string [ ] {
const nodeIssues = [ ] ;
if ( issues . execution !== undefined ) {
nodeIssues . push ( ` Execution Error. ` ) ;
}
const objectProperties = [
'parameters' ,
'credentials' ,
] ;
let issueText : string , parameterName : string ;
for ( const propertyName of objectProperties ) {
if ( issues [ propertyName ] !== undefined ) {
for ( parameterName of Object . keys ( issues [ propertyName ] as object ) ) {
for ( issueText of ( issues [ propertyName ] as INodeIssueObjectProperty ) [ parameterName ] ) {
nodeIssues . push ( issueText ) ;
}
}
}
}
if ( issues . typeUnknown !== undefined ) {
if ( node !== undefined ) {
nodeIssues . push ( ` Node Type " ${ node . type } " is not known. ` ) ;
} else {
nodeIssues . push ( ` Node Type is not known. ` ) ;
}
}
return nodeIssues ;
}
/ * *
* Adds an issue if the parameter is not defined
*
* @export
* @param { INodeIssues } foundIssues The already found issues
* @param { INodeProperties } nodeProperties The properties of the node
* @param { NodeParameterValue } value The value of the parameter
* /
export function addToIssuesIfMissing ( foundIssues : INodeIssues , nodeProperties : INodeProperties , value : NodeParameterValue ) {
// TODO: Check what it really has when undefined
if ( ( nodeProperties . type === 'string' && ( value === '' || value === undefined ) ) ||
( nodeProperties . type === 'multiOptions' && Array . isArray ( value ) && value . length === 0 ) ||
( nodeProperties . type === 'dateTime' && value === undefined ) ) {
// Parameter is requried but empty
if ( foundIssues . parameters === undefined ) {
foundIssues . parameters = { } ;
}
if ( foundIssues . parameters [ nodeProperties . name ] === undefined ) {
foundIssues . parameters [ nodeProperties . name ] = [ ] ;
}
foundIssues . parameters [ nodeProperties . name ] . push ( ` Parameter " ${ nodeProperties . displayName } " is required. ` ) ;
}
}
/ * *
* Returns the parameter value
*
* @export
* @param { INodeParameters } nodeValues The values of the node
* @param { string } parameterName The name of the parameter to return the value of
* @param { string } path The path to the properties
* @returns
* /
export function getParameterValueByPath ( nodeValues : INodeParameters , parameterName : string , path : string ) {
return get (
nodeValues ,
2020-10-22 09:00:28 -07:00
path ? path + '.' + parameterName : parameterName ,
2019-06-23 03:35:23 -07:00
) ;
}
/ * *
* Returns all the issues with the given node - values
*
* @export
* @param { INodeProperties } nodeProperties The properties of the node
* @param { INodeParameters } nodeValues The values of the node
* @param { string } path The path to the properties
* @returns { INodeIssues }
* /
export function getParameterIssues ( nodeProperties : INodeProperties , nodeValues : INodeParameters , path : string ) : INodeIssues {
const foundIssues : INodeIssues = { } ;
let value ;
if ( nodeProperties . required === true ) {
if ( displayParameterPath ( nodeValues , nodeProperties , path ) ) {
value = getParameterValueByPath ( nodeValues , nodeProperties . name , path ) ;
if ( nodeProperties . typeOptions !== undefined && nodeProperties . typeOptions . multipleValues !== undefined ) {
// Multiple can be set so will be an array
if ( Array . isArray ( value ) ) {
for ( const singleValue of value as NodeParameterValue [ ] ) {
addToIssuesIfMissing ( foundIssues , nodeProperties , singleValue as NodeParameterValue ) ;
}
}
} else {
// Only one can be set so will be a single value
addToIssuesIfMissing ( foundIssues , nodeProperties , value as NodeParameterValue ) ;
}
}
}
// Check if there are any child parameters
if ( nodeProperties . options === undefined ) {
// There are none so nothing else to check
return foundIssues ;
}
// Check the child parameters
// Important:
// Checks the child properties only if the property is defined on current level.
// That means that the required flag works only for the current level only. If
// it is set on a lower level it means that the property is only required in case
// the parent property got set.
let basePath = path ? ` ${ path } . ` : '' ;
const checkChildNodeProperties : Array < {
basePath : string ;
data : INodeProperties ;
} > = [ ] ;
// Collect all the properties to check
if ( nodeProperties . type === 'collection' ) {
for ( const option of nodeProperties . options ) {
checkChildNodeProperties . push ( {
basePath ,
data : option as INodeProperties ,
} ) ;
}
} else if ( nodeProperties . type === 'fixedCollection' ) {
basePath = basePath ? ` ${ basePath } . ` : '' + nodeProperties . name + '.' ;
let propertyOptions : INodePropertyCollection ;
for ( propertyOptions of nodeProperties . options as INodePropertyCollection [ ] ) {
// Check if the option got set and if not skip it
value = getParameterValueByPath ( nodeValues , propertyOptions . name , basePath . slice ( 0 , - 1 ) ) ;
if ( value === undefined ) {
continue ;
}
if ( nodeProperties . typeOptions !== undefined && nodeProperties . typeOptions . multipleValues !== undefined ) {
// Multiple can be set so will be an array of objects
if ( Array . isArray ( value ) ) {
for ( let i = 0 ; i < ( value as INodeParameters [ ] ) . length ; i ++ ) {
for ( const option of propertyOptions . values ) {
checkChildNodeProperties . push ( {
basePath : ` ${ basePath } ${ propertyOptions . name } [ ${ i } ] ` ,
data : option as INodeProperties ,
} ) ;
}
}
}
} else {
// Only one can be set so will be an object
for ( const option of propertyOptions . values ) {
checkChildNodeProperties . push ( {
basePath : basePath + propertyOptions . name ,
data : option as INodeProperties ,
} ) ;
}
}
}
} else {
// For all other types there is nothing to check so return
return foundIssues ;
}
let propertyIssues ;
for ( const optionData of checkChildNodeProperties ) {
propertyIssues = getParameterIssues ( optionData . data as INodeProperties , nodeValues , optionData . basePath ) ;
mergeIssues ( foundIssues , propertyIssues ) ;
}
return foundIssues ;
}
/ * *
* Merges multiple NodeIssues together
*
* @export
* @param { INodeIssues } destination The issues to merge into
* @param { ( INodeIssues | null ) } source The issues to merge
* @returns
* /
export function mergeIssues ( destination : INodeIssues , source : INodeIssues | null ) {
if ( source === null ) {
// Nothing to merge
return ;
}
if ( source . execution === true ) {
destination . execution = true ;
}
const objectProperties = [
'parameters' ,
'credentials' ,
] ;
let destinationProperty : INodeIssueObjectProperty ;
for ( const propertyName of objectProperties ) {
if ( source [ propertyName ] !== undefined ) {
if ( destination [ propertyName ] === undefined ) {
destination [ propertyName ] = { } ;
}
let parameterName : string ;
for ( parameterName of Object . keys ( source [ propertyName ] as INodeIssueObjectProperty ) ) {
destinationProperty = destination [ propertyName ] as INodeIssueObjectProperty ;
if ( destinationProperty [ parameterName ] === undefined ) {
destinationProperty [ parameterName ] = [ ] ;
}
destinationProperty [ parameterName ] . push . apply ( destinationProperty [ parameterName ] , ( source [ propertyName ] as INodeIssueObjectProperty ) [ parameterName ] ) ;
}
}
}
if ( source . typeUnknown === true ) {
destination . typeUnknown = true ;
}
}
2020-05-16 10:05:40 -07:00
/ * *
* Merges the given node properties
*
* @export
* @param { INodeProperties [ ] } mainProperties
* @param { INodeProperties [ ] } addProperties
* /
export function mergeNodeProperties ( mainProperties : INodeProperties [ ] , addProperties : INodeProperties [ ] ) : void {
let existingIndex : number ;
for ( const property of addProperties ) {
existingIndex = mainProperties . findIndex ( element = > element . name === property . name ) ;
if ( existingIndex === - 1 ) {
// Property does not exist yet, so add
mainProperties . push ( property ) ;
} else {
// Property exists already, so overwrite
mainProperties [ existingIndex ] = property ;
}
}
}