mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Execute sub-workflow UX and copy updates (no-changelog) (#12834)
This commit is contained in:
parent
13652c5ee2
commit
de49c23971
|
@ -1,4 +1,3 @@
|
||||||
import { loadWorkflowInputMappings } from 'n8n-nodes-base/dist/utils/workflowInputsResourceMapping/GenericFunctions';
|
|
||||||
import type {
|
import type {
|
||||||
INodeTypeBaseDescription,
|
INodeTypeBaseDescription,
|
||||||
ISupplyDataFunctions,
|
ISupplyDataFunctions,
|
||||||
|
@ -7,6 +6,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { localResourceMapping } from './methods';
|
||||||
import { WorkflowToolService } from './utils/WorkflowToolService';
|
import { WorkflowToolService } from './utils/WorkflowToolService';
|
||||||
import { versionDescription } from './versionDescription';
|
import { versionDescription } from './versionDescription';
|
||||||
|
|
||||||
|
@ -21,9 +21,7 @@ export class ToolWorkflowV2 implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
methods = {
|
methods = {
|
||||||
localResourceMapping: {
|
localResourceMapping,
|
||||||
loadWorkflowInputMappings,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * as localResourceMapping from './localResourceMapping';
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { loadWorkflowInputMappings } from 'n8n-nodes-base/dist/utils/workflowInputsResourceMapping/GenericFunctions';
|
||||||
|
import type { ILocalLoadOptionsFunctions, ResourceMapperFields } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function loadSubWorkflowInputs(
|
||||||
|
this: ILocalLoadOptionsFunctions,
|
||||||
|
): Promise<ResourceMapperFields> {
|
||||||
|
const { fields, dataMode, subworkflowInfo } = await loadWorkflowInputMappings.bind(this)();
|
||||||
|
let emptyFieldsNotice: string | undefined;
|
||||||
|
if (fields.length === 0) {
|
||||||
|
const subworkflowLink = subworkflowInfo?.id
|
||||||
|
? `<a href="/workflow/${subworkflowInfo?.id}" target="_blank">sub-workflow’s trigger</a>`
|
||||||
|
: 'sub-workflow’s trigger';
|
||||||
|
|
||||||
|
switch (dataMode) {
|
||||||
|
case 'passthrough':
|
||||||
|
emptyFieldsNotice = `This sub-workflow will consume all input data passed to it. Define specific expected input in the ${subworkflowLink}.`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
emptyFieldsNotice = `This sub-workflow will not receive any input when called by your AI node. Define your expected input in the ${subworkflowLink}.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { fields, emptyFieldsNotice };
|
||||||
|
}
|
|
@ -107,7 +107,7 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
loadOptionsDependsOn: ['workflowId.value'],
|
loadOptionsDependsOn: ['workflowId.value'],
|
||||||
resourceMapper: {
|
resourceMapper: {
|
||||||
localResourceMapperMethod: 'loadWorkflowInputMappings',
|
localResourceMapperMethod: 'loadSubWorkflowInputs',
|
||||||
valuesLabel: 'Workflow Inputs',
|
valuesLabel: 'Workflow Inputs',
|
||||||
mode: 'map',
|
mode: 'map',
|
||||||
fieldWords: {
|
fieldWords: {
|
||||||
|
|
|
@ -35,6 +35,8 @@ export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions {
|
||||||
|
|
||||||
if (selectedWorkflowNode) {
|
if (selectedWorkflowNode) {
|
||||||
const selectedSingleNodeWorkflow = new Workflow({
|
const selectedSingleNodeWorkflow = new Workflow({
|
||||||
|
id: dbWorkflow.id,
|
||||||
|
name: dbWorkflow.name,
|
||||||
nodes: [selectedWorkflowNode],
|
nodes: [selectedWorkflowNode],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
|
|
|
@ -0,0 +1,291 @@
|
||||||
|
import {
|
||||||
|
NodeConnectionType,
|
||||||
|
type INode,
|
||||||
|
type INodeProperties,
|
||||||
|
type INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const WORKFLOW_INPUTS_TEST_PARAMETER_PATH = 'parameters.workflowInputs';
|
||||||
|
|
||||||
|
export const WORKFLOW_INPUTS_TEST_PARAMETER: INodeProperties = {
|
||||||
|
displayName: 'Workflow Inputs',
|
||||||
|
name: 'workflowInputs',
|
||||||
|
type: 'resourceMapper',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: { mappingMode: 'defineBelow', value: null },
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['workflowId.value'],
|
||||||
|
resourceMapper: {
|
||||||
|
localResourceMapperMethod: 'loadWorkflowInputMappings',
|
||||||
|
valuesLabel: 'Workflow Inputs',
|
||||||
|
mode: 'map',
|
||||||
|
fieldWords: { singular: 'input', plural: 'inputs' },
|
||||||
|
addAllFields: true,
|
||||||
|
multiKeyMatch: false,
|
||||||
|
supportAutoMap: false,
|
||||||
|
showTypeConversionOptions: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WORKFLOW_INPUTS_TEST_NODE: INode = {
|
||||||
|
parameters: {
|
||||||
|
operation: 'call_workflow',
|
||||||
|
source: 'database',
|
||||||
|
workflowId: {
|
||||||
|
__rl: true,
|
||||||
|
value: 'test123',
|
||||||
|
mode: 'list',
|
||||||
|
cachedResultName: 'Workflow inputs—test',
|
||||||
|
},
|
||||||
|
workflowInputs: {
|
||||||
|
_custom: {
|
||||||
|
type: 'reactive',
|
||||||
|
stateTypeName: 'Reactive',
|
||||||
|
value: {
|
||||||
|
mappingMode: 'defineBelow',
|
||||||
|
value: {},
|
||||||
|
matchingColumns: [],
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
id: 'firstName',
|
||||||
|
displayName: 'First Name',
|
||||||
|
required: false,
|
||||||
|
defaultMatch: false,
|
||||||
|
display: true,
|
||||||
|
canBeUsedToMatch: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lastName',
|
||||||
|
displayName: 'Last Name',
|
||||||
|
required: false,
|
||||||
|
defaultMatch: false,
|
||||||
|
display: true,
|
||||||
|
canBeUsedToMatch: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attemptToConvertTypes: false,
|
||||||
|
convertFieldsToString: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mode: 'once',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
type: 'n8n-nodes-base.executeWorkflow',
|
||||||
|
typeVersion: 1.2,
|
||||||
|
position: [220, 0],
|
||||||
|
id: 'test-123',
|
||||||
|
name: 'Execute Workflow',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EXECUTE_WORKFLOW_NODE_TYPE_TEST: INodeTypeDescription = {
|
||||||
|
displayName: 'Execute Sub-workflow',
|
||||||
|
icon: 'fa:sign-in-alt',
|
||||||
|
iconColor: 'orange-red',
|
||||||
|
group: ['transform'],
|
||||||
|
version: [1, 1.1, 1.2],
|
||||||
|
subtitle: '={{"Workflow: " + $parameter["workflowId"]}}',
|
||||||
|
description: 'Execute another workflow',
|
||||||
|
defaults: { name: 'Execute Workflow', color: '#ff6d5a' },
|
||||||
|
inputs: [NodeConnectionType.Main],
|
||||||
|
outputs: [NodeConnectionType.Main],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'hidden',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: 'call_workflow',
|
||||||
|
options: [{ name: 'Execute a Sub-Workflow', value: 'call_workflow' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'This node is out of date. Please upgrade by removing it and adding a new one',
|
||||||
|
name: 'outdatedVersionWarning',
|
||||||
|
type: 'notice',
|
||||||
|
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Source',
|
||||||
|
name: 'source',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Database',
|
||||||
|
value: 'database',
|
||||||
|
description: 'Load the workflow from the database by ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Local File',
|
||||||
|
value: 'localFile',
|
||||||
|
description: 'Load the workflow from a locally saved file',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Parameter',
|
||||||
|
value: 'parameter',
|
||||||
|
description: 'Load the workflow from a parameter',
|
||||||
|
},
|
||||||
|
{ name: 'URL', value: 'url', description: 'Load the workflow from an URL' },
|
||||||
|
],
|
||||||
|
default: 'database',
|
||||||
|
description: 'Where to get the workflow to execute from',
|
||||||
|
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Source',
|
||||||
|
name: 'source',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Database',
|
||||||
|
value: 'database',
|
||||||
|
description: 'Load the workflow from the database by ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Define Below',
|
||||||
|
value: 'parameter',
|
||||||
|
description: 'Pass the JSON code of a workflow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'database',
|
||||||
|
description: 'Where to get the workflow to execute from',
|
||||||
|
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow ID',
|
||||||
|
name: 'workflowId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: { show: { source: ['database'], '@version': [1] } },
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
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>.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow',
|
||||||
|
name: 'workflowId',
|
||||||
|
type: 'workflowSelector',
|
||||||
|
displayOptions: { show: { source: ['database'], '@version': [{ _cnd: { gte: 1.1 } }] } },
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
hint: "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>.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow Path',
|
||||||
|
name: 'workflowPath',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: { show: { source: ['localFile'] } },
|
||||||
|
default: '',
|
||||||
|
placeholder: '/data/workflow.json',
|
||||||
|
required: true,
|
||||||
|
description: 'The path to local JSON workflow file to execute',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow JSON',
|
||||||
|
name: 'workflowJson',
|
||||||
|
type: 'json',
|
||||||
|
typeOptions: { rows: 10 },
|
||||||
|
displayOptions: { show: { source: ['parameter'] } },
|
||||||
|
default: '\n\n\n',
|
||||||
|
required: true,
|
||||||
|
description: 'The workflow JSON code to execute',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow URL',
|
||||||
|
name: 'workflowUrl',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: { show: { source: ['url'] } },
|
||||||
|
default: '',
|
||||||
|
placeholder: 'https://example.com/workflow.json',
|
||||||
|
required: true,
|
||||||
|
description: 'The URL from which to load the workflow from',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName:
|
||||||
|
'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>',
|
||||||
|
name: 'executeWorkflowNotice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workflow Inputs',
|
||||||
|
name: 'workflowInputs',
|
||||||
|
type: 'resourceMapper',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: { mappingMode: 'defineBelow', value: null },
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['workflowId.value'],
|
||||||
|
resourceMapper: {
|
||||||
|
localResourceMapperMethod: 'loadWorkflowInputMappings',
|
||||||
|
valuesLabel: 'Workflow Inputs',
|
||||||
|
mode: 'map',
|
||||||
|
fieldWords: { singular: 'input', plural: 'inputs' },
|
||||||
|
addAllFields: true,
|
||||||
|
multiKeyMatch: false,
|
||||||
|
supportAutoMap: false,
|
||||||
|
showTypeConversionOptions: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: { source: ['database'], '@version': [{ _cnd: { gte: 1.2 } }] },
|
||||||
|
hide: { workflowId: [''] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Mode',
|
||||||
|
name: 'mode',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Run once with all items',
|
||||||
|
value: 'once',
|
||||||
|
description: 'Pass all items into a single execution of the sub-workflow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Run once for each item',
|
||||||
|
value: 'each',
|
||||||
|
description: 'Call the sub-workflow individually for each item',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'once',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
codex: {
|
||||||
|
categories: ['Core Nodes'],
|
||||||
|
subcategories: { 'Core Nodes': ['Helpers', 'Flow'] },
|
||||||
|
alias: ['n8n', 'call', 'sub', 'workflow', 'sub-workflow', 'subworkflow'],
|
||||||
|
resources: {
|
||||||
|
primaryDocumentation: [
|
||||||
|
{
|
||||||
|
url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflow/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'n8n-nodes-base.executeWorkflow',
|
||||||
|
};
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import ResourceMapper from './ResourceMapper.vue';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import {
|
||||||
|
WORKFLOW_INPUTS_TEST_PARAMETER,
|
||||||
|
WORKFLOW_INPUTS_TEST_NODE,
|
||||||
|
WORKFLOW_INPUTS_TEST_PARAMETER_PATH,
|
||||||
|
EXECUTE_WORKFLOW_NODE_TYPE_TEST,
|
||||||
|
} from './ResourceMapper.test.constants';
|
||||||
|
import { mockedStore, waitAllPromises } from '@/__tests__/utils';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
|
||||||
|
vi.mock('vue-router', async () => {
|
||||||
|
const actual = await vi.importActual('vue-router');
|
||||||
|
const params = {};
|
||||||
|
const location = {};
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useRouter: () => ({
|
||||||
|
push: vi.fn(),
|
||||||
|
}),
|
||||||
|
useRoute: () => ({
|
||||||
|
params,
|
||||||
|
location,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let nodeTypesStore: ReturnType<typeof mockedStore<typeof useNodeTypesStore>>;
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(ResourceMapper, {
|
||||||
|
props: {
|
||||||
|
inputSize: 'small',
|
||||||
|
labelSize: 'small',
|
||||||
|
dependentParametersValues: '-1',
|
||||||
|
isReadonly: false,
|
||||||
|
teleported: false,
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
ParameterInputFull: { template: '<div data-test-id="field-input"></div>' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ResourceMapper::Workflow Inputs', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createTestingPinia();
|
||||||
|
nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||||
|
nodeTypesStore.nodeTypes = {
|
||||||
|
'n8n-nodes-base.executeWorkflow': {
|
||||||
|
1.2: EXECUTE_WORKFLOW_NODE_TYPE_TEST,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', async () => {
|
||||||
|
expect(() =>
|
||||||
|
renderComponent({
|
||||||
|
props: {
|
||||||
|
parameter: WORKFLOW_INPUTS_TEST_PARAMETER,
|
||||||
|
node: WORKFLOW_INPUTS_TEST_NODE,
|
||||||
|
path: WORKFLOW_INPUTS_TEST_PARAMETER_PATH,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders workflow inputs list correctly', async () => {
|
||||||
|
nodeTypesStore.getLocalResourceMapperFields.mockResolvedValue({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'firstName',
|
||||||
|
displayName: 'First Name',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
defaultMatch: false,
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lastName',
|
||||||
|
displayName: 'Last Name',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
defaultMatch: false,
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const { getByTestId, getAllByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
parameter: WORKFLOW_INPUTS_TEST_PARAMETER,
|
||||||
|
node: WORKFLOW_INPUTS_TEST_NODE,
|
||||||
|
path: WORKFLOW_INPUTS_TEST_PARAMETER_PATH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await waitAllPromises();
|
||||||
|
expect(getByTestId('mapping-fields-container')).toBeInTheDocument();
|
||||||
|
expect(getAllByTestId('field-input')).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders provided empty fields message', async () => {
|
||||||
|
nodeTypesStore.getLocalResourceMapperFields.mockResolvedValue({
|
||||||
|
fields: [],
|
||||||
|
emptyFieldsNotice: 'Nothing <b>here</b>',
|
||||||
|
});
|
||||||
|
const { queryByTestId, queryAllByTestId, getByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
parameter: WORKFLOW_INPUTS_TEST_PARAMETER,
|
||||||
|
node: WORKFLOW_INPUTS_TEST_NODE,
|
||||||
|
path: WORKFLOW_INPUTS_TEST_PARAMETER_PATH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await waitAllPromises();
|
||||||
|
expect(queryByTestId('mapping-fields-container')).not.toBeInTheDocument();
|
||||||
|
expect(queryAllByTestId('field-input')).toHaveLength(0);
|
||||||
|
expect(getByTestId('empty-fields-notice')).toHaveTextContent('Nothing here');
|
||||||
|
});
|
||||||
|
});
|
|
@ -28,7 +28,7 @@ import { i18n as locale } from '@/plugins/i18n';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useDocumentVisibility } from '@/composables/useDocumentVisibility';
|
import { useDocumentVisibility } from '@/composables/useDocumentVisibility';
|
||||||
import { N8nButton, N8nCallout } from 'n8n-design-system';
|
import { N8nButton, N8nCallout, N8nNotice } from 'n8n-design-system';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
parameter: INodeProperties;
|
parameter: INodeProperties;
|
||||||
|
@ -74,6 +74,7 @@ const state = reactive({
|
||||||
refreshInProgress: false, // Shows inline loader when refreshing fields
|
refreshInProgress: false, // Shows inline loader when refreshing fields
|
||||||
loadingError: false,
|
loadingError: false,
|
||||||
hasStaleFields: false,
|
hasStaleFields: false,
|
||||||
|
emptyFieldsNotice: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reload fields to map when dependent parameters change
|
// Reload fields to map when dependent parameters change
|
||||||
|
@ -315,7 +316,7 @@ async function fetchFields(): Promise<ResourceMapperFields | null> {
|
||||||
const { resourceMapperMethod, localResourceMapperMethod } =
|
const { resourceMapperMethod, localResourceMapperMethod } =
|
||||||
props.parameter.typeOptions?.resourceMapper ?? {};
|
props.parameter.typeOptions?.resourceMapper ?? {};
|
||||||
|
|
||||||
let fetchedFields = null;
|
let fetchedFields: ResourceMapperFields | null = null;
|
||||||
|
|
||||||
if (typeof resourceMapperMethod === 'string') {
|
if (typeof resourceMapperMethod === 'string') {
|
||||||
const requestParams = createRequestParams(
|
const requestParams = createRequestParams(
|
||||||
|
@ -329,6 +330,9 @@ async function fetchFields(): Promise<ResourceMapperFields | null> {
|
||||||
|
|
||||||
fetchedFields = await nodeTypesStore.getLocalResourceMapperFields(requestParams);
|
fetchedFields = await nodeTypesStore.getLocalResourceMapperFields(requestParams);
|
||||||
}
|
}
|
||||||
|
if (fetchedFields?.emptyFieldsNotice) {
|
||||||
|
state.emptyFieldsNotice = fetchedFields.emptyFieldsNotice;
|
||||||
|
}
|
||||||
return fetchedFields;
|
return fetchedFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,6 +623,13 @@ defineExpose({
|
||||||
@add-field="addField"
|
@add-field="addField"
|
||||||
@refresh-field-list="initFetching(true)"
|
@refresh-field-list="initFetching(true)"
|
||||||
/>
|
/>
|
||||||
|
<N8nNotice
|
||||||
|
v-else-if="state.emptyFieldsNotice && !state.hasStaleFields"
|
||||||
|
type="info"
|
||||||
|
data-test-id="empty-fields-notice"
|
||||||
|
>
|
||||||
|
<span v-n8n-html="state.emptyFieldsNotice"></span>
|
||||||
|
</N8nNotice>
|
||||||
<N8nCallout v-else-if="state.hasStaleFields" theme="info" :iconless="true">
|
<N8nCallout v-else-if="state.hasStaleFields" theme="info" :iconless="true">
|
||||||
{{ locale.baseText('resourceMapper.staleDataWarning.notice') }}
|
{{ locale.baseText('resourceMapper.staleDataWarning.notice') }}
|
||||||
<template #trailingContent>
|
<template #trailingContent>
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const SAMPLE_SUBWORKFLOW_WORKFLOW: WorkflowDataWithTemplateId = {
|
||||||
{
|
{
|
||||||
id: 'c055762a-8fe7-4141-a639-df2372f30060',
|
id: 'c055762a-8fe7-4141-a639-df2372f30060',
|
||||||
typeVersion: 1.1,
|
typeVersion: 1.1,
|
||||||
name: 'Workflow Input Trigger',
|
name: 'When Executed by Another Workflow',
|
||||||
type: 'n8n-nodes-base.executeWorkflowTrigger',
|
type: 'n8n-nodes-base.executeWorkflowTrigger',
|
||||||
position: [260, 340],
|
position: [260, 340],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
|
@ -24,7 +24,7 @@ export const SAMPLE_SUBWORKFLOW_WORKFLOW: WorkflowDataWithTemplateId = {
|
||||||
},
|
},
|
||||||
] as INodeUi[],
|
] as INodeUi[],
|
||||||
connections: {
|
connections: {
|
||||||
'Workflow Input Trigger': {
|
'When Executed by Another Workflow': {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
|
@ -1163,7 +1163,7 @@
|
||||||
"nodeCreator.triggerHelperPanel.whatHappensNext": "What happens next?",
|
"nodeCreator.triggerHelperPanel.whatHappensNext": "What happens next?",
|
||||||
"nodeCreator.triggerHelperPanel.selectATrigger": "What triggers this workflow?",
|
"nodeCreator.triggerHelperPanel.selectATrigger": "What triggers this workflow?",
|
||||||
"nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
"nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
||||||
"nodeCreator.triggerHelperPanel.workflowTriggerDisplayName": "When called by another workflow",
|
"nodeCreator.triggerHelperPanel.workflowTriggerDisplayName": "When Executed by Another Workflow",
|
||||||
"nodeCreator.triggerHelperPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
"nodeCreator.triggerHelperPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
||||||
"nodeCreator.aiPanel.aiNodes": "AI Nodes",
|
"nodeCreator.aiPanel.aiNodes": "AI Nodes",
|
||||||
"nodeCreator.aiPanel.aiOtherNodes": "Other AI Nodes",
|
"nodeCreator.aiPanel.aiOtherNodes": "Other AI Nodes",
|
||||||
|
@ -1184,7 +1184,7 @@
|
||||||
"nodeCreator.aiPanel.whatHappensNext": "What happens next?",
|
"nodeCreator.aiPanel.whatHappensNext": "What happens next?",
|
||||||
"nodeCreator.aiPanel.selectATrigger": "Select an AI Component",
|
"nodeCreator.aiPanel.selectATrigger": "Select an AI Component",
|
||||||
"nodeCreator.aiPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
"nodeCreator.aiPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
||||||
"nodeCreator.aiPanel.workflowTriggerDisplayName": "When called by another workflow",
|
"nodeCreator.aiPanel.workflowTriggerDisplayName": "When Executed by Another Workflow",
|
||||||
"nodeCreator.aiPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
"nodeCreator.aiPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
||||||
"nodeCreator.nodeItem.triggerIconTitle": "Trigger Node",
|
"nodeCreator.nodeItem.triggerIconTitle": "Trigger Node",
|
||||||
"nodeCreator.nodeItem.aiIconTitle": "LangChain AI Node",
|
"nodeCreator.nodeItem.aiIconTitle": "LangChain AI Node",
|
||||||
|
@ -1598,7 +1598,7 @@
|
||||||
"resourceMapper.refreshFieldList": "Refresh {fieldWord} List",
|
"resourceMapper.refreshFieldList": "Refresh {fieldWord} List",
|
||||||
"resourceMapper.staleDataWarning.tooltip": "{fieldWord} are outdated. Refresh to see the changes.",
|
"resourceMapper.staleDataWarning.tooltip": "{fieldWord} are outdated. Refresh to see the changes.",
|
||||||
"resourceMapper.staleDataWarning.notice": "Refresh to see the updated fields",
|
"resourceMapper.staleDataWarning.notice": "Refresh to see the updated fields",
|
||||||
"resourceMapper.attemptToConvertTypes.displayName": "Attempt to convert types",
|
"resourceMapper.attemptToConvertTypes.displayName": "Attempt To Convert Types",
|
||||||
"resourceMapper.attemptToConvertTypes.description": "Attempt to convert types when mapping fields",
|
"resourceMapper.attemptToConvertTypes.description": "Attempt to convert types when mapping fields",
|
||||||
"runData.openSubExecutionSingle": "View sub-execution",
|
"runData.openSubExecutionSingle": "View sub-execution",
|
||||||
"runData.openSubExecutionWithId": "View sub-execution {id}",
|
"runData.openSubExecutionWithId": "View sub-execution {id}",
|
||||||
|
@ -2771,8 +2771,8 @@
|
||||||
"communityPlusModal.input.email.label": "Enter email to receive your license key",
|
"communityPlusModal.input.email.label": "Enter email to receive your license key",
|
||||||
"communityPlusModal.button.skip": "Skip",
|
"communityPlusModal.button.skip": "Skip",
|
||||||
"communityPlusModal.button.confirm": "Send me a free license key",
|
"communityPlusModal.button.confirm": "Send me a free license key",
|
||||||
"executeWorkflowTrigger.createNewSubworkflow": "Create a sub-workflow in {projectName}",
|
"executeWorkflowTrigger.createNewSubworkflow": "Create a Sub-Workflow in {projectName}",
|
||||||
"executeWorkflowTrigger.createNewSubworkflow.noProject": "Create a new sub-workflow",
|
"executeWorkflowTrigger.createNewSubworkflow.noProject": "Create a New Sub-Workflow",
|
||||||
"testDefinition.edit.descriptionPlaceholder": "Enter test description",
|
"testDefinition.edit.descriptionPlaceholder": "Enter test description",
|
||||||
"testDefinition.edit.showConfig": "Show config",
|
"testDefinition.edit.showConfig": "Show config",
|
||||||
"testDefinition.edit.hideConfig": "Hide config",
|
"testDefinition.edit.hideConfig": "Hide config",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"alias": ["n8n"],
|
"alias": ["n8n", "call", "sub", "workflow", "sub-workflow", "subworkflow"],
|
||||||
"subcategories": {
|
"subcategories": {
|
||||||
"Core Nodes": ["Helpers", "Flow"]
|
"Core Nodes": ["Helpers", "Flow"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,12 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { getWorkflowInfo } from './GenericFunctions';
|
import { getWorkflowInfo } from './GenericFunctions';
|
||||||
|
import { localResourceMapping } from './methods';
|
||||||
import { generatePairedItemData } from '../../../utils/utilities';
|
import { generatePairedItemData } from '../../../utils/utilities';
|
||||||
import {
|
import { getCurrentWorkflowInputData } from '../../../utils/workflowInputsResourceMapping/GenericFunctions';
|
||||||
getCurrentWorkflowInputData,
|
|
||||||
loadWorkflowInputMappings,
|
|
||||||
} from '../../../utils/workflowInputsResourceMapping/GenericFunctions';
|
|
||||||
export class ExecuteWorkflow implements INodeType {
|
export class ExecuteWorkflow implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Execute Workflow',
|
displayName: 'Execute Sub-workflow',
|
||||||
name: 'executeWorkflow',
|
name: 'executeWorkflow',
|
||||||
icon: 'fa:sign-in-alt',
|
icon: 'fa:sign-in-alt',
|
||||||
iconColor: 'orange-red',
|
iconColor: 'orange-red',
|
||||||
|
@ -38,7 +36,7 @@ export class ExecuteWorkflow implements INodeType {
|
||||||
default: 'call_workflow',
|
default: 'call_workflow',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'Call Another Workflow',
|
name: 'Execute a Sub-Workflow',
|
||||||
value: 'call_workflow',
|
value: 'call_workflow',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -210,7 +208,7 @@ export class ExecuteWorkflow implements INodeType {
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
loadOptionsDependsOn: ['workflowId.value'],
|
loadOptionsDependsOn: ['workflowId.value'],
|
||||||
resourceMapper: {
|
resourceMapper: {
|
||||||
localResourceMapperMethod: 'loadWorkflowInputMappings',
|
localResourceMapperMethod: 'loadSubWorkflowInputs',
|
||||||
valuesLabel: 'Workflow Inputs',
|
valuesLabel: 'Workflow Inputs',
|
||||||
mode: 'map',
|
mode: 'map',
|
||||||
fieldWords: {
|
fieldWords: {
|
||||||
|
@ -275,9 +273,7 @@ export class ExecuteWorkflow implements INodeType {
|
||||||
};
|
};
|
||||||
|
|
||||||
methods = {
|
methods = {
|
||||||
localResourceMapping: {
|
localResourceMapping,
|
||||||
loadWorkflowInputMappings,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * as localResourceMapping from './localResourceMapping';
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { ILocalLoadOptionsFunctions, ResourceMapperFields } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { loadWorkflowInputMappings } from '@utils/workflowInputsResourceMapping/GenericFunctions';
|
||||||
|
|
||||||
|
export async function loadSubWorkflowInputs(
|
||||||
|
this: ILocalLoadOptionsFunctions,
|
||||||
|
): Promise<ResourceMapperFields> {
|
||||||
|
const { fields, dataMode, subworkflowInfo } = await loadWorkflowInputMappings.bind(this)();
|
||||||
|
let emptyFieldsNotice: string | undefined;
|
||||||
|
if (fields.length === 0) {
|
||||||
|
const subworkflowLink = subworkflowInfo?.id
|
||||||
|
? `<a href="/workflow/${subworkflowInfo?.id}" target="_blank">sub-workflow’s trigger</a>`
|
||||||
|
: 'sub-workflow’s trigger';
|
||||||
|
|
||||||
|
switch (dataMode) {
|
||||||
|
case 'passthrough':
|
||||||
|
emptyFieldsNotice = `This sub-workflow will consume all input data passed to it. You can define specific expected input in the ${subworkflowLink}.`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
emptyFieldsNotice = `The sub-workflow isn't set up to accept any inputs. Change this in the ${subworkflowLink}.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { fields, emptyFieldsNotice };
|
||||||
|
}
|
|
@ -32,11 +32,16 @@ describe('ExecuteWorkflowTrigger', () => {
|
||||||
|
|
||||||
it('should filter out parent input in `Using Fields below` mode', async () => {
|
it('should filter out parent input in `Using Fields below` mode', async () => {
|
||||||
executeFns.getNodeParameter.mockReturnValueOnce(WORKFLOW_INPUTS);
|
executeFns.getNodeParameter.mockReturnValueOnce(WORKFLOW_INPUTS);
|
||||||
const mockNewParams = [
|
const mockNewParams: {
|
||||||
{ name: 'value1', type: 'string' },
|
fields: FieldValueOption[];
|
||||||
{ name: 'value2', type: 'number' },
|
noFieldsMessage?: string;
|
||||||
{ name: 'foo', type: 'string' },
|
} = {
|
||||||
] as FieldValueOption[];
|
fields: [
|
||||||
|
{ name: 'value1', type: 'string' },
|
||||||
|
{ name: 'value2', type: 'number' },
|
||||||
|
{ name: 'foo', type: 'string' },
|
||||||
|
],
|
||||||
|
};
|
||||||
const getFieldEntriesMock = (getFieldEntries as jest.Mock).mockReturnValue(mockNewParams);
|
const getFieldEntriesMock = (getFieldEntries as jest.Mock).mockReturnValue(mockNewParams);
|
||||||
|
|
||||||
const result = await new ExecuteWorkflowTrigger().execute.call(executeFns);
|
const result = await new ExecuteWorkflowTrigger().execute.call(executeFns);
|
||||||
|
|
|
@ -30,14 +30,15 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
eventTriggerDescription: '',
|
eventTriggerDescription: '',
|
||||||
maxNodes: 1,
|
maxNodes: 1,
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Workflow Input Trigger',
|
name: 'When Executed by Another Workflow',
|
||||||
color: '#ff6d5a',
|
color: '#ff6d5a',
|
||||||
},
|
},
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: [NodeConnectionType.Main],
|
outputs: [NodeConnectionType.Main],
|
||||||
hints: [
|
hints: [
|
||||||
{
|
{
|
||||||
message: 'Please make sure to define your input fields.',
|
message:
|
||||||
|
"This workflow isn't set to accept any input data. Fill out the workflow input schema or change the workflow to accept any data passed to it.",
|
||||||
// This condition checks if we have no input fields, which gets a bit awkward:
|
// This condition checks if we have no input fields, which gets a bit awkward:
|
||||||
// For WORKFLOW_INPUTS: keys() only contains `VALUES` if at least one value is provided
|
// For WORKFLOW_INPUTS: keys() only contains `VALUES` if at least one value is provided
|
||||||
// For JSON_EXAMPLE: We remove all whitespace and check if we're left with an empty object. Note that we already error if the example is not valid JSON
|
// For JSON_EXAMPLE: We remove all whitespace and check if we're left with an empty object. Note that we already error if the example is not valid JSON
|
||||||
|
@ -58,8 +59,8 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
{
|
{
|
||||||
name: 'Workflow Call',
|
name: 'Workflow Call',
|
||||||
value: 'worklfow_call',
|
value: 'worklfow_call',
|
||||||
description: 'When called by another workflow using Execute Workflow Trigger',
|
description: 'When executed by another workflow using Execute Workflow Trigger',
|
||||||
action: 'When Called by Another Workflow',
|
action: 'When executed by Another Workflow',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'worklfow_call',
|
default: 'worklfow_call',
|
||||||
|
@ -142,7 +143,7 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Workflow Inputs',
|
displayName: 'Workflow Input Schema',
|
||||||
name: WORKFLOW_INPUTS,
|
name: WORKFLOW_INPUTS,
|
||||||
placeholder: 'Add field',
|
placeholder: 'Add field',
|
||||||
type: 'fixedCollection',
|
type: 'fixedCollection',
|
||||||
|
@ -168,7 +169,8 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '',
|
default: '',
|
||||||
placeholder: 'e.g. fieldName',
|
placeholder: 'e.g. fieldName',
|
||||||
description: 'Name of the field',
|
description:
|
||||||
|
'A unique name for this workflow input, used to reference it from another workflows',
|
||||||
required: true,
|
required: true,
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
},
|
},
|
||||||
|
@ -176,7 +178,8 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
displayName: 'Type',
|
displayName: 'Type',
|
||||||
name: 'type',
|
name: 'type',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
description: 'The field value type',
|
description:
|
||||||
|
"Expected data type for this input value. Determines how this field's values are stored, validated, and displayed.",
|
||||||
options: TYPE_OPTIONS,
|
options: TYPE_OPTIONS,
|
||||||
required: true,
|
required: true,
|
||||||
default: 'string',
|
default: 'string',
|
||||||
|
@ -208,10 +211,10 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
return [inputData];
|
return [inputData];
|
||||||
} else {
|
} else {
|
||||||
const newParams = getFieldEntries(this);
|
const newParams = getFieldEntries(this);
|
||||||
const newKeys = new Set(newParams.map((x) => x.name));
|
const newKeys = new Set(newParams.fields.map((x) => x.name));
|
||||||
const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({
|
const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({
|
||||||
json: {
|
json: {
|
||||||
...Object.fromEntries(newParams.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])),
|
...Object.fromEntries(newParams.fields.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])),
|
||||||
// Need to trim to the expected schema to support legacy Execute Workflow callers passing through all their data
|
// Need to trim to the expected schema to support legacy Execute Workflow callers passing through all their data
|
||||||
// which we do not want to expose past this node.
|
// which we do not want to expose past this node.
|
||||||
..._.pickBy(row.json, (_value, key) => newKeys.has(key)),
|
..._.pickBy(row.json, (_value, key) => newKeys.has(key)),
|
||||||
|
|
|
@ -9,8 +9,8 @@ import type {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
ResourceMapperField,
|
ResourceMapperField,
|
||||||
ILocalLoadOptionsFunctions,
|
ILocalLoadOptionsFunctions,
|
||||||
ResourceMapperFields,
|
|
||||||
ISupplyDataFunctions,
|
ISupplyDataFunctions,
|
||||||
|
WorkflowInputsData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { jsonParse, NodeOperationError, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE } from 'n8n-workflow';
|
import { jsonParse, NodeOperationError, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE } from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -65,7 +65,11 @@ function parseJsonExample(context: IWorkflowNodeContext): JSONSchema7 {
|
||||||
return generateSchemaFromExample(json) as JSONSchema7;
|
return generateSchemaFromExample(json) as JSONSchema7;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFieldEntries(context: IWorkflowNodeContext): FieldValueOption[] {
|
export function getFieldEntries(context: IWorkflowNodeContext): {
|
||||||
|
dataMode: WorkflowInputsData['dataMode'];
|
||||||
|
fields: FieldValueOption[];
|
||||||
|
subworkflowInfo?: WorkflowInputsData['subworkflowInfo'];
|
||||||
|
} {
|
||||||
const inputSource = context.getNodeParameter(INPUT_SOURCE, 0, PASSTHROUGH);
|
const inputSource = context.getNodeParameter(INPUT_SOURCE, 0, PASSTHROUGH);
|
||||||
let result: FieldValueOption[] | string = 'Internal Error: Invalid input source';
|
let result: FieldValueOption[] | string = 'Internal Error: Invalid input source';
|
||||||
try {
|
try {
|
||||||
|
@ -89,7 +93,9 @@ export function getFieldEntries(context: IWorkflowNodeContext): FieldValueOption
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
return result;
|
const dataMode = String(inputSource);
|
||||||
|
const workflow = context.getWorkflow();
|
||||||
|
return { fields: result, dataMode, subworkflowInfo: { id: workflow.id } };
|
||||||
}
|
}
|
||||||
throw new NodeOperationError(context.getNode(), result);
|
throw new NodeOperationError(context.getNode(), result);
|
||||||
}
|
}
|
||||||
|
@ -140,14 +146,18 @@ export function getCurrentWorkflowInputData(this: ISupplyDataFunctions) {
|
||||||
|
|
||||||
export async function loadWorkflowInputMappings(
|
export async function loadWorkflowInputMappings(
|
||||||
this: ILocalLoadOptionsFunctions,
|
this: ILocalLoadOptionsFunctions,
|
||||||
): Promise<ResourceMapperFields> {
|
): Promise<WorkflowInputsData> {
|
||||||
const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);
|
const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);
|
||||||
let fields: ResourceMapperField[] = [];
|
let fields: ResourceMapperField[] = [];
|
||||||
|
let dataMode: string = PASSTHROUGH;
|
||||||
|
let subworkflowInfo: { id?: string } | undefined;
|
||||||
|
|
||||||
if (nodeLoadContext) {
|
if (nodeLoadContext) {
|
||||||
const fieldValues = getFieldEntries(nodeLoadContext);
|
const fieldValues = getFieldEntries(nodeLoadContext);
|
||||||
|
dataMode = fieldValues.dataMode;
|
||||||
|
subworkflowInfo = fieldValues.subworkflowInfo;
|
||||||
|
|
||||||
fields = fieldValues.map((currentWorkflowInput) => {
|
fields = fieldValues.fields.map((currentWorkflowInput) => {
|
||||||
const field: ResourceMapperField = {
|
const field: ResourceMapperField = {
|
||||||
id: currentWorkflowInput.name,
|
id: currentWorkflowInput.name,
|
||||||
displayName: currentWorkflowInput.name,
|
displayName: currentWorkflowInput.name,
|
||||||
|
@ -164,5 +174,5 @@ export async function loadWorkflowInputMappings(
|
||||||
return field;
|
return field;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { fields };
|
return { fields, dataMode, subworkflowInfo };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1030,7 +1030,7 @@ export interface ILoadOptionsFunctions extends FunctionsBase {
|
||||||
export type FieldValueOption = { name: string; type: FieldType | 'any' };
|
export type FieldValueOption = { name: string; type: FieldType | 'any' };
|
||||||
|
|
||||||
export type IWorkflowNodeContext = ExecuteFunctions.GetNodeParameterFn &
|
export type IWorkflowNodeContext = ExecuteFunctions.GetNodeParameterFn &
|
||||||
Pick<FunctionsBase, 'getNode'>;
|
Pick<FunctionsBase, 'getNode' | 'getWorkflow'>;
|
||||||
|
|
||||||
export interface ILocalLoadOptionsFunctions {
|
export interface ILocalLoadOptionsFunctions {
|
||||||
getWorkflowNodeContext(nodeType: string): Promise<IWorkflowNodeContext | null>;
|
getWorkflowNodeContext(nodeType: string): Promise<IWorkflowNodeContext | null>;
|
||||||
|
@ -2660,6 +2660,13 @@ export interface IExecutionSummaryNodeExecutionResult {
|
||||||
|
|
||||||
export interface ResourceMapperFields {
|
export interface ResourceMapperFields {
|
||||||
fields: ResourceMapperField[];
|
fields: ResourceMapperField[];
|
||||||
|
emptyFieldsNotice?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowInputsData {
|
||||||
|
fields: ResourceMapperField[];
|
||||||
|
dataMode: string;
|
||||||
|
subworkflowInfo?: { id?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceMapperField {
|
export interface ResourceMapperField {
|
||||||
|
|
Loading…
Reference in a new issue