mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(Simulate Node): New node (no-changelog) (#9109)
This commit is contained in:
parent
c4bf5b2b92
commit
6b6e8dfc33
|
@ -23,6 +23,7 @@ import type {
|
|||
INodeTypes,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
IExecuteData,
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
import { ICredentialsHelper, NodeHelpers, Workflow, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
|
@ -57,6 +58,9 @@ const mockNodesData: INodeTypeData = {
|
|||
};
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
getKnownTypes(): IDataObject {
|
||||
return {};
|
||||
},
|
||||
getByName(nodeType: string): INodeType | IVersionedNodeType {
|
||||
return mockNodesData[nodeType]?.type;
|
||||
},
|
||||
|
|
|
@ -54,6 +54,10 @@ export class NodeTypes implements INodeTypes {
|
|||
}
|
||||
}
|
||||
|
||||
getKnownTypes() {
|
||||
return this.loadNodesAndCredentials.knownNodes;
|
||||
}
|
||||
|
||||
private getNode(type: string): LoadedClass<INodeType | IVersionedNodeType> {
|
||||
const { loadedNodes, knownNodes } = this.loadNodesAndCredentials;
|
||||
if (type in loadedNodes) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import type {
|
|||
INodeTypeData,
|
||||
INodeTypes,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
VersionedNodeType,
|
||||
|
@ -54,6 +55,9 @@ const mockNodesData: INodeTypeData = {
|
|||
};
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
getKnownTypes(): IDataObject {
|
||||
return {};
|
||||
},
|
||||
getByName(nodeType: string): INodeType | IVersionedNodeType {
|
||||
return mockNodesData[nodeType]?.type;
|
||||
},
|
||||
|
|
|
@ -2839,6 +2839,7 @@ const getCommonWorkflowFunctions = (
|
|||
}
|
||||
return output;
|
||||
},
|
||||
getKnownNodeTypes: () => workflow.nodeTypes.getKnownTypes(),
|
||||
getRestApiUrl: () => additionalData.restApiUrl,
|
||||
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
|
||||
getInstanceId: () => Container.get(InstanceSettings).instanceId,
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
|
||||
<NodeIcon
|
||||
class="node-icon"
|
||||
:node-type="nodeType"
|
||||
:node-type="iconNodeType"
|
||||
:size="40"
|
||||
:shrink="false"
|
||||
:color-default="iconColorDefault"
|
||||
|
@ -186,6 +186,8 @@ import {
|
|||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
||||
NOT_DUPLICATABE_NODE_TYPES,
|
||||
SIMULATE_NODE_TYPE,
|
||||
SIMULATE_TRIGGER_NODE_TYPE,
|
||||
WAIT_TIME_UNLIMITED,
|
||||
} from '@/constants';
|
||||
import { nodeBase } from '@/mixins/nodeBase';
|
||||
|
@ -586,6 +588,25 @@ export default defineComponent({
|
|||
this.contextMenu.target.value.node.name === this.data?.name
|
||||
);
|
||||
},
|
||||
iconNodeType() {
|
||||
if (
|
||||
this.data?.type === SIMULATE_NODE_TYPE ||
|
||||
this.data?.type === SIMULATE_TRIGGER_NODE_TYPE
|
||||
) {
|
||||
const icon = this.data.parameters?.icon as string;
|
||||
const iconNodeType = this.workflow.expression.getSimpleParameterValue(
|
||||
this.data,
|
||||
icon,
|
||||
'internal',
|
||||
{},
|
||||
);
|
||||
if (iconNodeType && typeof iconNodeType === 'string') {
|
||||
return this.nodeTypesStore.getNodeType(iconNodeType);
|
||||
}
|
||||
}
|
||||
|
||||
return this.nodeType;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isActive(newValue, oldValue) {
|
||||
|
|
|
@ -184,6 +184,8 @@ export const COMPRESSION_NODE_TYPE = 'n8n-nodes-base.compression';
|
|||
export const EDIT_IMAGE_NODE_TYPE = 'n8n-nodes-base.editImage';
|
||||
export const CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE =
|
||||
'@n8n/n8n-nodes-langchain.chainSummarization';
|
||||
export const SIMULATE_NODE_TYPE = 'n8n-nodes-base.simulate';
|
||||
export const SIMULATE_TRIGGER_NODE_TYPE = 'n8n-nodes-base.simulateTrigger';
|
||||
|
||||
export const CREDENTIAL_ONLY_NODE_PREFIX = 'n8n-creds-base';
|
||||
export const CREDENTIAL_ONLY_HTTP_NODE_VERSION = 4.1;
|
||||
|
|
11
packages/nodes-base/nodes/Simulate/Simulate.node.json
Normal file
11
packages/nodes-base/nodes/Simulate/Simulate.node.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.simulate",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {},
|
||||
"alias": ["placeholder", "stub", "dummy"],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Helpers"]
|
||||
}
|
||||
}
|
131
packages/nodes-base/nodes/Simulate/Simulate.node.ts
Normal file
131
packages/nodes-base/nodes/Simulate/Simulate.node.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { sleep, jsonParse, NodeOperationError } from 'n8n-workflow';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { loadOptions } from './methods';
|
||||
import {
|
||||
executionDurationProperty,
|
||||
iconSelector,
|
||||
jsonOutputProperty,
|
||||
subtitleProperty,
|
||||
} from './descriptions';
|
||||
|
||||
export class Simulate implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Simulate',
|
||||
hidden: true,
|
||||
name: 'simulate',
|
||||
group: ['organization'],
|
||||
version: 1,
|
||||
description: 'Simulate a node',
|
||||
subtitle: '={{$parameter.subtitle || undefined}}',
|
||||
icon: 'fa:arrow-right',
|
||||
defaults: {
|
||||
name: 'Simulate',
|
||||
color: '#b0b0b0',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
iconSelector,
|
||||
subtitleProperty,
|
||||
{
|
||||
displayName: 'Output',
|
||||
name: 'output',
|
||||
type: 'options',
|
||||
default: 'all',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Returns all input items',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Specify how many of input items to return',
|
||||
value: 'specify',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Specify output as JSON',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Number of Items',
|
||||
name: 'numberOfItems',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description:
|
||||
'Number input of items to return, if greater then input length all items will be returned',
|
||||
displayOptions: {
|
||||
show: {
|
||||
output: ['specify'],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
...jsonOutputProperty,
|
||||
displayOptions: {
|
||||
show: {
|
||||
output: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
executionDurationProperty,
|
||||
],
|
||||
};
|
||||
|
||||
methods = { loadOptions };
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
let returnItems: INodeExecutionData[] = [];
|
||||
|
||||
const output = this.getNodeParameter('output', 0) as string;
|
||||
|
||||
if (output === 'all') {
|
||||
returnItems = items;
|
||||
} else if (output === 'specify') {
|
||||
const numberOfItems = this.getNodeParameter('numberOfItems', 0) as number;
|
||||
|
||||
returnItems = items.slice(0, numberOfItems);
|
||||
} else if (output === 'custom') {
|
||||
let jsonOutput = this.getNodeParameter('jsonOutput', 0);
|
||||
|
||||
if (typeof jsonOutput === 'string') {
|
||||
try {
|
||||
jsonOutput = jsonParse<IDataObject>(jsonOutput);
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.getNode(), 'Invalid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(jsonOutput)) {
|
||||
jsonOutput = [jsonOutput];
|
||||
}
|
||||
|
||||
for (const item of jsonOutput as IDataObject[]) {
|
||||
returnItems.push({ json: item });
|
||||
}
|
||||
}
|
||||
|
||||
const executionDuration = this.getNodeParameter('executionDuration', 0) as number;
|
||||
|
||||
if (executionDuration > 0) {
|
||||
await sleep(executionDuration);
|
||||
}
|
||||
|
||||
return [returnItems];
|
||||
}
|
||||
}
|
11
packages/nodes-base/nodes/Simulate/SimulateTrigger.node.json
Normal file
11
packages/nodes-base/nodes/Simulate/SimulateTrigger.node.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.simulateTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {},
|
||||
"alias": ["placeholder", "stub", "dummy"],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Helpers"]
|
||||
}
|
||||
}
|
79
packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts
Normal file
79
packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { sleep, NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
import type {
|
||||
IDataObject,
|
||||
ITriggerFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
executionDurationProperty,
|
||||
iconSelector,
|
||||
jsonOutputProperty,
|
||||
subtitleProperty,
|
||||
} from './descriptions';
|
||||
import { loadOptions } from './methods';
|
||||
|
||||
export class SimulateTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
hidden: true,
|
||||
displayName: 'Simulate Trigger',
|
||||
name: 'simulateTrigger',
|
||||
subtitle: '={{$parameter.subtitle || undefined}}',
|
||||
icon: 'fa:arrow-right',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Simulate a trigger node',
|
||||
defaults: {
|
||||
name: 'Simulate Trigger',
|
||||
color: '#b0b0b0',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{ ...iconSelector, default: 'n8n-nodes-base.manualTrigger' },
|
||||
subtitleProperty,
|
||||
{ ...jsonOutputProperty, displayName: 'Output (JSON)' },
|
||||
executionDurationProperty,
|
||||
],
|
||||
};
|
||||
|
||||
methods = { loadOptions };
|
||||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
const returnItems: INodeExecutionData[] = [];
|
||||
|
||||
let jsonOutput = this.getNodeParameter('jsonOutput', 0);
|
||||
|
||||
if (typeof jsonOutput === 'string') {
|
||||
try {
|
||||
jsonOutput = jsonParse<IDataObject>(jsonOutput);
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.getNode(), 'Invalid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(jsonOutput)) {
|
||||
jsonOutput = [jsonOutput];
|
||||
}
|
||||
|
||||
for (const item of jsonOutput as IDataObject[]) {
|
||||
returnItems.push({ json: item });
|
||||
}
|
||||
|
||||
const executionDuration = this.getNodeParameter('executionDuration', 0) as number;
|
||||
|
||||
if (executionDuration > 0) {
|
||||
await sleep(executionDuration);
|
||||
}
|
||||
|
||||
const manualTriggerFunction = async () => {
|
||||
this.emit([returnItems]);
|
||||
};
|
||||
|
||||
return {
|
||||
manualTriggerFunction,
|
||||
};
|
||||
}
|
||||
}
|
44
packages/nodes-base/nodes/Simulate/descriptions.ts
Normal file
44
packages/nodes-base/nodes/Simulate/descriptions.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const iconSelector: INodeProperties = {
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Icon to Display on Canvas',
|
||||
name: 'icon',
|
||||
type: 'options',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description: 'Select a type of node to show corresponding icon',
|
||||
default: 'n8n-nodes-base.noOp',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getNodeTypes',
|
||||
},
|
||||
};
|
||||
|
||||
export const subtitleProperty: INodeProperties = {
|
||||
displayName: 'Subtitle',
|
||||
name: 'subtitle',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: "e.g. 'record: read'",
|
||||
};
|
||||
|
||||
export const jsonOutputProperty: INodeProperties = {
|
||||
displayName: 'JSON',
|
||||
name: 'jsonOutput',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '[\n {\n "my_field_1": "value",\n "my_field_2": 1\n }\n]',
|
||||
validateType: 'array',
|
||||
};
|
||||
|
||||
export const executionDurationProperty: INodeProperties = {
|
||||
displayName: 'Execution Duration (MS)',
|
||||
name: 'executionDuration',
|
||||
type: 'number',
|
||||
default: 150,
|
||||
description: 'Execution duration in milliseconds',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
};
|
1
packages/nodes-base/nodes/Simulate/methods/index.ts
Normal file
1
packages/nodes-base/nodes/Simulate/methods/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * as loadOptions from './loadOptions';
|
26
packages/nodes-base/nodes/Simulate/methods/loadOptions.ts
Normal file
26
packages/nodes-base/nodes/Simulate/methods/loadOptions.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||
|
||||
export async function getNodeTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const types = this.getKnownNodeTypes() as {
|
||||
[key: string]: {
|
||||
className: string;
|
||||
};
|
||||
};
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
let typeNames = Object.keys(types);
|
||||
|
||||
if (this.getNode().type === 'n8n-nodes-base.simulateTrigger') {
|
||||
typeNames = typeNames.filter((type) => type.toLowerCase().includes('trigger'));
|
||||
}
|
||||
|
||||
for (const type of typeNames) {
|
||||
returnData.push({
|
||||
name: types[type].className,
|
||||
value: type,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
|
@ -717,6 +717,8 @@
|
|||
"dist/nodes/Shopify/Shopify.node.js",
|
||||
"dist/nodes/Shopify/ShopifyTrigger.node.js",
|
||||
"dist/nodes/Signl4/Signl4.node.js",
|
||||
"dist/nodes/Simulate/Simulate.node.js",
|
||||
"dist/nodes/Simulate/SimulateTrigger.node.js",
|
||||
"dist/nodes/Slack/Slack.node.js",
|
||||
"dist/nodes/Sms77/Sms77.node.js",
|
||||
"dist/nodes/Snowflake/Snowflake.node.js",
|
||||
|
|
|
@ -815,6 +815,7 @@ export interface FunctionsBase {
|
|||
getInstanceId(): string;
|
||||
getChildNodes(nodeName: string): NodeTypeAndVersion[];
|
||||
getParentNodes(nodeName: string): NodeTypeAndVersion[];
|
||||
getKnownNodeTypes(): IDataObject;
|
||||
getMode?: () => WorkflowExecuteMode;
|
||||
getActivationMode?: () => WorkflowActivateMode;
|
||||
|
||||
|
@ -1839,6 +1840,7 @@ export type WebhookResponseMode = 'onReceived' | 'lastNode' | 'responseNode';
|
|||
export interface INodeTypes {
|
||||
getByName(nodeType: string): INodeType | IVersionedNodeType;
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType;
|
||||
getKnownTypes(): IDataObject;
|
||||
}
|
||||
|
||||
export type LoadingDetails = {
|
||||
|
|
Loading…
Reference in a new issue