first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,90 @@
import type { INodeProperties } from 'n8n-workflow';
export const auditOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
default: 'get',
displayOptions: {
show: {
resource: ['audit'],
},
},
options: [
{
name: 'Generate',
value: 'generate',
action: 'Generate a security audit',
description: 'Generate a security audit for this n8n instance',
routing: {
request: {
method: 'POST',
url: '/audit',
},
},
},
],
},
];
export const auditFields: INodeProperties[] = [
{
displayName: 'Additional Options',
name: 'additionalOptions',
type: 'collection',
placeholder: 'Add Filter',
displayOptions: {
show: {
resource: ['audit'],
},
},
routing: {
request: {
body: {
additionalOptions: '={{ $value }}',
},
},
},
default: {},
options: [
{
displayName: 'Categories',
name: 'categories',
description: 'Risk categories to include in the audit',
type: 'multiOptions',
default: [],
options: [
{
name: 'Credentials',
value: 'credentials',
},
{
name: 'Database',
value: 'database',
},
{
name: 'Filesystem',
value: 'filesystem',
},
{
name: 'Instance',
value: 'instance',
},
{
name: 'Nodes',
value: 'nodes',
},
],
},
{
displayName: 'Days Abandoned Workflow',
name: 'daysAbandonedWorkflow',
description: 'Days for a workflow to be considered abandoned if not executed',
type: 'number',
default: 90,
},
],
},
];
@@ -0,0 +1,169 @@
import type { INodeProperties } from 'n8n-workflow';
import { parseAndSetBodyJson } from './GenericFunctions';
export const credentialOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
default: 'create',
displayOptions: {
show: {
resource: ['credential'],
},
},
options: [
{
name: 'Create',
value: 'create',
action: 'Create a credential',
routing: {
request: {
method: 'POST',
url: '/credentials',
},
},
},
{
name: 'Delete',
value: 'delete',
action: 'Delete a credential',
routing: {
request: {
method: 'DELETE',
url: '=/credentials/{{ $parameter.credentialId }}',
},
},
},
{
name: 'Get Schema',
value: 'getSchema',
action: 'Get credential data schema for type',
routing: {
request: {
method: 'GET',
url: '=/credentials/schema/{{ $parameter.credentialTypeName }}',
},
},
},
],
},
];
const createOperation: INodeProperties[] = [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
placeholder: 'e.g. n8n account',
required: true,
displayOptions: {
show: {
resource: ['credential'],
operation: ['create'],
},
},
routing: {
request: {
body: {
name: '={{ $value }}',
},
},
},
description: 'Name of the new credential',
},
{
displayName: 'Credential Type',
name: 'credentialTypeName',
type: 'string',
placeholder: 'e.g. n8nApi',
default: '',
required: true,
displayOptions: {
show: {
resource: ['credential'],
operation: ['create'],
},
},
routing: {
request: {
body: {
type: '={{ $value }}',
},
},
},
description:
"The available types depend on nodes installed on the n8n instance. Some built-in types include e.g. 'githubApi', 'notionApi', and 'slackApi'.",
},
{
displayName: 'Data',
name: 'data',
type: 'json',
default: '',
placeholder:
'// e.g. for n8nApi \n{\n "apiKey": "my-n8n-api-key",\n "baseUrl": "https://<name>.app.n8n.cloud/api/v1",\n}',
required: true,
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
resource: ['credential'],
operation: ['create'],
},
},
routing: {
send: {
// Validate that the 'data' property is parseable as JSON and
// set it into the request as body.data.
preSend: [parseAndSetBodyJson('data', 'data')],
},
},
description:
"A valid JSON object with properties required for this Credential Type. To see the expected format, you can use 'Get Schema' operation.",
},
];
const deleteOperation: INodeProperties[] = [
{
displayName: 'Credential ID',
name: 'credentialId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['credential'],
operation: ['delete'],
},
},
},
];
const getSchemaOperation: INodeProperties[] = [
{
displayName: 'Credential Type',
name: 'credentialTypeName',
default: '',
placeholder: 'e.g. n8nApi',
required: true,
type: 'string',
displayOptions: {
show: {
resource: ['credential'],
operation: ['getSchema'],
},
},
description:
"The available types depend on nodes installed on the n8n instance. Some built-in types include e.g. 'githubApi', 'notionApi', and 'slackApi'.",
},
];
export const credentialFields: INodeProperties[] = [
...createOperation,
...deleteOperation,
...getSchemaOperation,
];
@@ -0,0 +1,252 @@
import type { INodeProperties } from 'n8n-workflow';
import { getCursorPaginator } from './GenericFunctions';
import { workflowIdLocator } from './WorkflowLocator';
export const executionOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
default: 'getAll',
displayOptions: {
show: {
resource: ['execution'],
},
},
options: [
{
name: 'Get',
value: 'get',
action: 'Get an execution',
routing: {
request: {
method: 'GET',
url: '=/executions/{{ $parameter.executionId }}',
},
},
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get many executions',
routing: {
request: {
method: 'GET',
url: '/executions',
},
send: {
paginate: true,
},
operations: {
pagination: getCursorPaginator(),
},
},
},
{
name: 'Delete',
value: 'delete',
action: 'Delete an execution',
routing: {
request: {
method: 'DELETE',
url: '=/executions/{{ $parameter.executionId }}',
},
},
},
],
},
];
const deleteOperation: INodeProperties[] = [
{
displayName: 'Execution ID',
name: 'executionId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['execution'],
operation: ['delete'],
},
},
default: '',
},
];
const getAllOperation: INodeProperties[] = [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: ['execution'],
operation: ['getAll'],
},
},
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 100,
typeOptions: {
minValue: 1,
maxValue: 250,
},
displayOptions: {
show: {
resource: ['execution'],
operation: ['getAll'],
returnAll: [false],
},
},
routing: {
request: {
qs: {
limit: '={{ $value }}',
},
},
},
description: 'Max number of results to return',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
resource: ['execution'],
operation: ['getAll'],
},
},
options: [
{
// Use the common workflowIdLocator, but provide a custom routing
...workflowIdLocator,
routing: {
send: {
type: 'query',
property: 'workflowId',
value: '={{ $value || undefined }}',
},
},
description: 'Workflow to filter the executions by',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: 'Error',
value: 'error',
},
{
name: 'Success',
value: 'success',
},
{
name: 'Waiting',
value: 'waiting',
},
],
default: 'success',
routing: {
send: {
type: 'query',
property: 'status',
value: '={{ $value }}',
},
},
description: 'Status to filter the executions by',
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
default: {},
placeholder: 'Add option',
displayOptions: {
show: {
resource: ['execution'],
operation: ['getAll'],
},
},
options: [
{
displayName: 'Include Execution Details',
name: 'activeWorkflows',
type: 'boolean',
default: false,
routing: {
send: {
type: 'query',
property: 'includeData',
value: '={{ $value }}',
},
},
description: 'Whether to include the detailed execution data',
},
],
},
];
const getOperation: INodeProperties[] = [
{
displayName: 'Execution ID',
name: 'executionId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['execution'],
operation: ['get'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
default: {},
placeholder: 'Add option',
displayOptions: {
show: {
resource: ['execution'],
operation: ['get'],
},
},
options: [
{
displayName: 'Include Execution Details',
name: 'activeWorkflows',
type: 'boolean',
default: false,
routing: {
send: {
type: 'query',
property: 'includeData',
value: '={{ $value }}',
},
},
description: 'Whether to include the detailed execution data',
},
],
},
];
export const executionFields: INodeProperties[] = [
...deleteOperation,
...getAllOperation,
...getOperation,
];
@@ -0,0 +1,236 @@
import type {
DeclarativeRestApiSettings,
IDataObject,
IExecuteFunctions,
IExecutePaginationFunctions,
IExecuteSingleFunctions,
IHookFunctions,
IHttpRequestMethods,
IHttpRequestOptions,
ILoadOptionsFunctions,
INodeExecutionData,
IRequestOptions,
JsonObject,
PreSendAction,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
/**
* A custom API request function to be used with the resourceLocator lookup queries.
*/
export async function apiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: object,
query?: IDataObject,
): Promise<any> {
query = query || {};
type N8nApiCredentials = {
apiKey: string;
baseUrl: string;
};
const credentials = await this.getCredentials<N8nApiCredentials>('n8nApi');
const baseUrl = credentials.baseUrl;
const options: IRequestOptions = {
method,
body,
qs: query,
uri: `${baseUrl.replace(new RegExp('/$'), '')}/${endpoint}`,
json: true,
};
try {
return await this.helpers.requestWithAuthentication.call(this, 'n8nApi', options);
} catch (error) {
if (error instanceof NodeApiError) {
throw error;
}
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export async function apiRequestAllItems(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: object,
query?: IDataObject,
): Promise<any> {
query = query || {};
const returnData: IDataObject[] = [];
let nextCursor: string | undefined = undefined;
let responseData;
do {
query.cursor = nextCursor;
query.limit = 100;
responseData = await apiRequest.call(this, method, endpoint, body, query);
returnData.push.apply(returnData, responseData.data as IDataObject[]);
nextCursor = responseData.nextCursor as string | undefined;
} while (nextCursor);
return returnData;
}
/**
* Get a cursor-based paginator to use with n8n 'getAll' type endpoints.
*
* It will look up a 'nextCursor' in the response and if the node has
* 'returnAll' set to true, will consecutively include it as the 'cursor' query
* parameter for the next request, effectively getting everything in slices.
*
* Prequisites:
* - routing.send.paginate must be set to true, for all requests to go through here
* - node is expected to have a boolean parameter 'returnAll'
* - no postReceive action setting the rootProperty, to get the items mapped
*
* @returns A ready-to-use cursor-based paginator function.
*/
export const getCursorPaginator = () => {
return async function cursorPagination(
this: IExecutePaginationFunctions,
requestOptions: DeclarativeRestApiSettings.ResultOptions,
): Promise<INodeExecutionData[]> {
if (!requestOptions.options.qs) {
requestOptions.options.qs = {};
}
let executions: INodeExecutionData[] = [];
let responseData: INodeExecutionData[];
let nextCursor: string | undefined = undefined;
const returnAll = this.getNodeParameter('returnAll', true) as boolean;
const extractItems = (page: INodeExecutionData) => {
const items = page.json.data as IDataObject[];
if (items) {
// Extract the items themselves
executions = executions.concat(items.map((item) => ({ json: item })));
}
};
do {
requestOptions.options.qs.cursor = nextCursor;
responseData = await this.makeRoutingRequest(requestOptions);
// Check for another page of items
const lastItem = responseData[responseData.length - 1].json;
nextCursor = lastItem.nextCursor as string | undefined;
responseData.forEach(extractItems);
// If we don't return all, just return the first page
} while (returnAll && nextCursor);
return executions;
};
};
/**
* A helper function to parse a node parameter as JSON and set it in the request body.
* Throws a NodeOperationError is the content is not valid JSON or it cannot be set.
*
* Currently, parameters with type 'json' are not validated automatically.
* Also mapping the value for 'body.data' declaratively has it treated as a string,
* but some operations (e.g. POST /credentials) don't work unless it is set as an object.
* To get the JSON-body operations to work consistently, we need to parse and set the body
* manually.
*
* @param parameterName The name of the node parameter to parse
* @param setAsBodyProperty An optional property name to set the parsed data into
* @returns The requestOptions with its body replaced with the contents of the parameter
*/
export const parseAndSetBodyJson = (
parameterName: string,
setAsBodyProperty?: string,
): PreSendAction => {
return async function (
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
try {
const rawData = this.getNodeParameter(parameterName, '{}') as string;
const parsedObject = JSON.parse(rawData);
// Set the parsed object to either as the request body directly, or as its sub-property
if (setAsBodyProperty === undefined) {
requestOptions.body = parsedObject;
} else {
requestOptions.body = Object.assign({}, requestOptions.body, {
[setAsBodyProperty]: parsedObject,
});
}
} catch (err) {
throw new NodeOperationError(
this.getNode(),
new Error(`The '${parameterName}' property must be valid JSON, but cannot be parsed`, {
cause: err,
}),
);
}
return requestOptions;
};
};
/**
* A helper function to prepare the workflow object data for creation. It only sets
* known workflow properties, for pre-emptively avoiding a HTTP 400 Bad Request
* response until we have a better client-side schema validation mechanism.
*
* NOTE! This expects the requestOptions.body to already be set as an object,
* so take care to first call parseAndSetBodyJson().
*/
export const prepareWorkflowCreateBody: PreSendAction = async function (
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const body = requestOptions.body as IDataObject;
const newBody: IDataObject = {};
newBody.name = body.name || 'My workflow';
newBody.nodes = body.nodes || [];
newBody.settings = body.settings || {};
newBody.connections = body.connections || {};
newBody.staticData = body.staticData || null;
requestOptions.body = newBody;
return requestOptions;
};
/**
* A helper function to prepare the workflow object data for update.
*
* NOTE! This expects the requestOptions.body to already be set as an object,
* so take care to first call parseAndSetBodyJson().
*/
export const prepareWorkflowUpdateBody: PreSendAction = async function (
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const body = requestOptions.body as IDataObject;
const newBody: IDataObject = {};
if (body.name) {
newBody.name = body.name;
}
if (body.nodes) {
newBody.nodes = body.nodes;
}
if (body.settings) {
newBody.settings = body.settings;
}
if (body.connections) {
newBody.connections = body.connections;
}
if (body.staticData) {
newBody.staticData = body.staticData;
}
requestOptions.body = newBody;
return requestOptions;
};
@@ -0,0 +1,22 @@
{
"node": "n8n-nodes-base.n8n",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development", "Core Nodes"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/api/authentication/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.n8n/"
}
]
},
"alias": ["Workflow", "Execution"],
"subcategories": {
"Core Nodes": ["Helpers", "Other Trigger Nodes"]
}
}
+89
View File
@@ -0,0 +1,89 @@
import { NodeConnectionTypes, type INodeType, type INodeTypeDescription } from 'n8n-workflow';
import { auditFields, auditOperations } from './AuditDescription';
import { credentialFields, credentialOperations } from './CredentialDescription';
import { executionFields, executionOperations } from './ExecutionDescription';
import { workflowFields, workflowOperations } from './WorkflowDescription';
import { searchWorkflows } from './WorkflowLocator';
/**
* The n8n node provides access to the n8n API.
*
* See: https://docs.n8n.io/api/api-reference/
*/
export class N8n implements INodeType {
description: INodeTypeDescription = {
displayName: 'n8n',
name: 'n8n',
icon: 'file:n8n.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Handle events and perform actions on your n8n instance',
defaults: {
name: 'n8n',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'n8nApi',
required: true,
},
],
requestDefaults: {
returnFullResponse: true,
baseURL: '={{ $credentials.baseUrl.replace(new RegExp("/$"), "") }}',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Audit',
value: 'audit',
},
{
name: 'Credential',
value: 'credential',
},
{
name: 'Execution',
value: 'execution',
},
{
name: 'Workflow',
value: 'workflow',
},
],
default: 'workflow',
},
...auditOperations,
...auditFields,
...credentialOperations,
...credentialFields,
...executionOperations,
...executionFields,
...workflowOperations,
...workflowFields,
],
};
methods = {
listSearch: {
// Provide workflows search capability for the workflow resourceLocator
searchWorkflows,
},
};
}
@@ -0,0 +1,460 @@
import type { INodeProperties } from 'n8n-workflow';
import {
getCursorPaginator,
parseAndSetBodyJson,
prepareWorkflowCreateBody,
prepareWorkflowUpdateBody,
} from './GenericFunctions';
import { workflowIdLocator } from './WorkflowLocator';
export const workflowOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
default: 'getAll',
displayOptions: {
show: {
resource: ['workflow'],
},
},
options: [
{
name: 'Publish',
value: 'activate',
action: 'Publish a workflow',
},
{
name: 'Create',
value: 'create',
action: 'Create a workflow',
routing: {
request: {
method: 'POST',
url: '/workflows',
},
},
},
{
name: 'Unpublish',
value: 'deactivate',
action: 'Unpublish a workflow',
},
{
name: 'Delete',
value: 'delete',
action: 'Delete a workflow',
},
{
name: 'Get',
value: 'get',
action: 'Get a workflow',
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get many workflows',
routing: {
request: {
method: 'GET',
url: '/workflows',
},
send: {
paginate: true,
},
operations: {
pagination: getCursorPaginator(),
},
},
},
{
name: 'Get Version',
value: 'getVersion',
action: 'Get a workflow version',
},
{
name: 'Update',
value: 'update',
action: 'Update a workflow',
},
],
},
];
const activateOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['activate'],
},
},
// The routing for resourceLocator-enabled properties currently needs to
// happen in the property block where the property itself is defined, or
// extractValue won't work when used with $parameter in routing.request.url.
routing: {
request: {
method: 'POST',
url: '=/workflows/{{ $value }}/activate',
},
},
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['workflow'],
operation: ['activate'],
},
},
options: [
{
displayName: 'Version ID',
name: 'versionId',
type: 'string',
default: '',
routing: {
send: {
type: 'body',
property: 'versionId',
},
},
description: 'The version ID of the workflow to publish',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
routing: {
send: {
type: 'body',
property: 'name',
},
},
description: 'Published version name (will be overwritten)',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
typeOptions: {
rows: 4,
},
default: '',
routing: {
send: {
type: 'body',
property: 'description',
},
},
description: 'Published version description (will be overwritten)',
},
],
},
];
const createOperation: INodeProperties[] = [
{
displayName: 'Workflow Object',
name: 'workflowObject',
type: 'json',
default: '{ "name": "My workflow", "nodes": [], "connections": {}, "settings": {} }',
placeholder:
'{\n "name": "My workflow",\n "nodes": [],\n "connections": {},\n "settings": {}\n}',
required: true,
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
resource: ['workflow'],
operation: ['create'],
},
},
routing: {
send: {
preSend: [parseAndSetBodyJson('workflowObject'), prepareWorkflowCreateBody],
},
},
description:
"A valid JSON object with required fields: 'name', 'nodes', 'connections' and 'settings'. More information can be found in the <a href=\"https://docs.n8n.io/api/api-reference/#tag/workflow/paths/~1workflows/post\">documentation</a>.",
},
];
const deactivateOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['deactivate'],
},
},
routing: {
request: {
method: 'POST',
url: '=/workflows/{{ $value }}/deactivate',
},
},
},
];
const deleteOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['delete'],
},
},
routing: {
request: {
method: 'DELETE',
url: '=/workflows/{{ $value }}',
},
},
},
];
const getAllOperation: INodeProperties[] = [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['getAll'],
},
},
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 100,
typeOptions: {
minValue: 1,
maxValue: 250,
},
displayOptions: {
show: {
resource: ['workflow'],
operation: ['getAll'],
returnAll: [false],
},
},
routing: {
request: {
qs: {
limit: '={{ $value }}',
},
},
},
description: 'Max number of results to return',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
default: {},
displayOptions: {
show: {
resource: ['workflow'],
operation: ['getAll'],
},
},
options: [
{
displayName: 'Return Only Published Workflows',
name: 'activeWorkflows',
type: 'boolean',
default: true,
routing: {
request: {
qs: {
active: '={{ $value }}',
},
},
},
},
{
displayName: 'Tags',
name: 'tags',
type: 'string',
default: '',
routing: {
// Only include the 'tags' query parameter if it's non-empty
send: {
type: 'query',
property: 'tags',
value: '={{ $value !== "" ? $value : undefined }}',
},
},
description: 'Include only workflows with these tags',
hint: 'Comma separated list of tags (empty value is ignored)',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
routing: {
request: {
qs: {
name: '={{ $value }}',
},
},
},
},
{
displayName: 'Project ID',
name: 'projectId',
type: 'string',
default: '',
routing: {
request: {
qs: {
projectId: '={{ $value }}',
},
},
},
},
{
displayName: 'Exclude Pinned Data',
name: 'excludePinnedData',
description: 'Whether to exclude pinned data from the response',
type: 'boolean',
default: false,
routing: {
request: {
qs: {
excludePinnedData: '={{ $value }}',
},
},
},
},
],
},
];
const getOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['get'],
},
},
routing: {
request: {
method: 'GET',
url: '=/workflows/{{ $value }}',
},
},
},
];
const getVersionOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['getVersion'],
},
},
routing: {
request: {
method: 'GET',
url: '=/workflows/{{ $value }}/{{ $parameter["versionId"] }}',
},
},
},
{
displayName: 'Version ID',
name: 'versionId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['getVersion'],
},
},
description: 'The version ID to retrieve',
},
];
const updateOperation: INodeProperties[] = [
{
...workflowIdLocator,
required: true,
displayOptions: {
show: {
resource: ['workflow'],
operation: ['update'],
},
},
routing: {
request: {
method: 'PUT',
url: '=/workflows/{{ $value }}',
},
},
},
{
displayName: 'Workflow Object',
name: 'workflowObject',
type: 'json',
default: '',
placeholder:
'{\n "name": "My workflow",\n "nodes": [],\n "connections": {},\n "settings": {}\n}',
required: true,
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
resource: ['workflow'],
operation: ['update'],
},
},
routing: {
send: {
preSend: [parseAndSetBodyJson('workflowObject'), prepareWorkflowUpdateBody],
},
},
description:
"A valid JSON object with required fields: 'name', 'nodes', 'connections' and 'settings'. More information can be found in the <a href=\"https://docs.n8n.io/api/api-reference/#tag/workflow/paths/~1workflows~1%7bid%7d/put\">documentation</a>.",
},
];
export const workflowFields: INodeProperties[] = [
...activateOperation,
...createOperation,
...deactivateOperation,
...deleteOperation,
...getAllOperation,
...getOperation,
...getVersionOperation,
...updateOperation,
];
@@ -0,0 +1,107 @@
import type { ILoadOptionsFunctions, INodeListSearchResult, INodeProperties } from 'n8n-workflow';
import { apiRequestAllItems } from './GenericFunctions';
type DataItemsResponse<T> = {
data: T[];
};
interface PartialWorkflow {
id: number;
name: string;
}
/**
* A helper to populate workflow lists. It does a pseudo-search by
* listing available workflows and matching with the specified query.
*/
export async function searchWorkflows(
this: ILoadOptionsFunctions,
query?: string,
): Promise<INodeListSearchResult> {
const searchResults = (await apiRequestAllItems.call(
this,
'GET',
'workflows',
{},
)) as DataItemsResponse<PartialWorkflow>;
// Map the workflows list against a simple name/id filter, and sort
// with the latest on top.
const workflows = (searchResults as unknown as PartialWorkflow[])
.map((w: PartialWorkflow) => ({
name: `${w.name} (#${w.id})`,
value: w.id,
}))
.filter(
(w) =>
!query ||
w.name.toLowerCase().includes(query.toLowerCase()) ||
w.value?.toString() === query,
)
.sort((a, b) => b.value - a.value);
return {
results: workflows,
};
}
/**
* A resourceLocator to enable looking up workflows by their ID.
* This object can be used as a base and then extended as needed.
*/
export const workflowIdLocator: INodeProperties = {
displayName: 'Workflow',
name: 'workflowId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description: 'Workflow to filter the executions by',
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a Workflow...',
initType: 'workflow',
typeOptions: {
searchListMethod: 'searchWorkflows',
searchFilterRequired: false,
searchable: true,
},
},
{
displayName: 'By URL',
name: 'url',
type: 'string',
placeholder: 'https://myinstance.app.n8n.cloud/workflow/1',
validation: [
{
type: 'regex',
properties: {
regex: '.*/workflow/([0-9a-zA-Z]{1,})',
errorMessage: 'Not a valid Workflow URL',
},
},
],
extractValue: {
type: 'regex',
regex: '.*/workflow/([0-9a-zA-Z]{1,})',
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: '[0-9a-zA-Z]{1,}',
errorMessage: 'Not a valid Workflow ID',
},
},
],
placeholder: '1',
},
],
};
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 230 120"><path fill="#EA4B71" fill-rule="evenodd" d="M204 48c-11.183 0-20.58-7.649-23.244-18h-27.508a12 12 0 0 0-11.836 10.027l-.987 5.919A23.94 23.94 0 0 1 132.626 60a23.94 23.94 0 0 1 7.799 14.054l.987 5.919A12 12 0 0 0 153.248 90h3.508C159.42 79.649 168.817 72 180 72c13.255 0 24 10.745 24 24s-10.745 24-24 24c-11.183 0-20.58-7.649-23.244-18h-3.508c-11.732 0-21.744-8.482-23.673-20.054l-.987-5.919A12 12 0 0 0 116.752 66h-9.508C104.58 76.351 95.183 84 84 84s-20.58-7.649-23.244-18H47.244C44.58 76.351 35.183 84 24 84 10.745 84 0 73.255 0 60s10.745-24 24-24c11.183 0 20.58 7.649 23.244 18h13.512C63.42 43.649 72.817 36 84 36s20.58 7.649 23.244 18h9.508a12 12 0 0 0 11.836-10.027l.987-5.919C131.504 26.482 141.516 18 153.248 18h27.508C183.42 7.649 192.817 0 204 0c13.255 0 24 10.745 24 24s-10.745 24-24 24m0-12c6.627 0 12-5.373 12-12s-5.373-12-12-12-12 5.373-12 12 5.373 12 12 12M24 72c6.627 0 12-5.373 12-12s-5.373-12-12-12-12 5.373-12 12 5.373 12 12 12m72-12c0 6.627-5.373 12-12 12s-12-5.373-12-12 5.373-12 12-12 12 5.373 12 12m96 36c0 6.627-5.373 12-12 12s-12-5.373-12-12 5.373-12 12-12 12 5.373 12 12" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,20 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
describe('Test N8n Node', () => {
const baseUrl = 'https://test.app.n8n.cloud/api/v1';
const credentials = {
n8nApi: {
apiKey: 'key123',
baseUrl,
},
};
beforeAll(async () => {
const { pinData } = await import('./workflow.n8n.workflows.json');
const apiResponse = pinData.n8n.map((item) => item.json);
nock(baseUrl).get('/workflows?tags=n8n-test').reply(200, { data: apiResponse });
});
new NodeTestHarness().setupTests({ credentials });
});
@@ -0,0 +1,694 @@
{
"name": "My workflow 2",
"nodes": [
{
"parameters": {},
"id": "798b312a-c922-4f11-8b96-496802bd327b",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
640,
360
]
},
{
"parameters": {
"filters": {
"tags": "n8n-test"
}
},
"id": "90a66b97-baf9-4388-aee6-ce46dfce2749",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"typeVersion": 1,
"position": [
900,
360
],
"credentials": {
"n8nApi": {
"id": "7",
"name": "n8n account"
}
}
}
],
"pinData": {
"n8n": [
{
"json": {
"createdAt": "2023-02-07T10:30:32.416Z",
"updatedAt": "2023-02-10T11:28:18.000Z",
"id": "107",
"name": "merge tests",
"active": false,
"nodes": [
{
"parameters": {},
"id": "9f880c6f-b5e0-4453-b1d7-550beb1febe8",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
700,
660
]
},
{
"parameters": {
"jsCode": "return [\n {id: 1, data: 'a', input: 1, text: 'foo'},\n {id: 2, data: 'b', input: 1, text: 'foo'},\n];"
},
"id": "263d9c7f-32d4-4112-b271-73d09b73809a",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
900,
540
]
},
{
"parameters": {
"jsCode": "return [\n {id: 1, data: 'c', input: 2, tag: 'second'},\n {id: 2, data: 'd', input: 2, tag: 'second'},\n {id: 3, data: 'e', input: 2, tag: 'second'},\n];"
},
"id": "3defcf5a-ec5e-4d23-a1f6-a99b7362a241",
"name": "Code1",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
900,
780
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"options": {}
},
"id": "7120cd6f-29f3-4f5b-aa25-babebd6335d3",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
140
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"joinMode": "keepNonMatches",
"options": {}
},
"id": "1d6973e5-0716-4d47-85ef-298d3f86bb9d",
"name": "Merge1",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
280
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"joinMode": "keepEverything",
"options": {}
},
"id": "dd75b8c0-21bf-460f-9920-644a467ed356",
"name": "Merge2",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
420
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"joinMode": "enrichInput1",
"options": {}
},
"id": "3545877d-6e4f-437e-b91f-4c792d714e73",
"name": "Merge3",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
560
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"joinMode": "enrichInput2",
"options": {}
},
"id": "4b6ac099-6e6f-4e91-bcfe-74d7524fad54",
"name": "Merge4",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
740
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"outputDataFrom": "input1",
"options": {}
},
"id": "fb4fef36-26ea-4fc0-b6e9-a4b2781c94fa",
"name": "Merge5",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
900
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"outputDataFrom": "input2",
"options": {}
},
"id": "0b4fbaaa-cf0b-4da5-b35f-da3f49409965",
"name": "Merge6",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
1040
]
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
},
"options": {
"clashHandling": {
"values": {
"resolveClash": "addSuffix"
}
}
}
},
"id": "25489a7a-74c1-48d5-a03d-1bbd2aba2abe",
"name": "Merge7",
"type": "n8n-nodes-base.merge",
"typeVersion": 2,
"position": [
1320,
1200
]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
},
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 0
},
{
"node": "Merge2",
"type": "main",
"index": 0
},
{
"node": "Merge3",
"type": "main",
"index": 0
},
{
"node": "Merge4",
"type": "main",
"index": 0
},
{
"node": "Merge5",
"type": "main",
"index": 0
},
{
"node": "Merge6",
"type": "main",
"index": 0
},
{
"node": "Merge7",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
},
{
"node": "Merge3",
"type": "main",
"index": 1
},
{
"node": "Merge2",
"type": "main",
"index": 1
},
{
"node": "Merge1",
"type": "main",
"index": 1
},
{
"node": "Merge4",
"type": "main",
"index": 1
},
{
"node": "Merge5",
"type": "main",
"index": 1
},
{
"node": "Merge6",
"type": "main",
"index": 1
},
{
"node": "Merge7",
"type": "main",
"index": 1
}
]
]
}
},
"settings": {},
"staticData": null,
"pinData": {
"Merge": [
{
"json": {
"id": 1,
"data": "c",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 2,
"data": "d",
"input": 2,
"text": "foo",
"tag": "second"
}
}
],
"Merge1": [
{
"json": {
"id": 3,
"data": "e",
"input": 2,
"tag": "second",
"_source": "input2"
}
}
],
"Merge2": [
{
"json": {
"id": 1,
"data": "c",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 2,
"data": "d",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 3,
"data": "e",
"input": 2,
"tag": "second"
}
}
],
"Merge3": [
{
"json": {
"id": 1,
"data": "c",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 2,
"data": "d",
"input": 2,
"text": "foo",
"tag": "second"
}
}
],
"Merge4": [
{
"json": {
"id": 1,
"data": "c",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 2,
"data": "d",
"input": 2,
"text": "foo",
"tag": "second"
}
},
{
"json": {
"id": 3,
"data": "e",
"input": 2,
"tag": "second"
}
}
],
"Merge5": [
{
"json": {
"id": 1,
"data": "a",
"input": 1,
"text": "foo"
}
},
{
"json": {
"id": 2,
"data": "b",
"input": 1,
"text": "foo"
}
}
],
"Merge6": [
{
"json": {
"id": 1,
"data": "c",
"input": 2,
"tag": "second"
}
},
{
"json": {
"id": 2,
"data": "d",
"input": 2,
"tag": "second"
}
}
],
"Merge7": [
{
"json": {
"id_1": 1,
"data_1": "a",
"input_1": 1,
"text_1": "foo",
"id_2": 1,
"data_2": "c",
"input_2": 2,
"tag_2": "second"
}
},
{
"json": {
"id_1": 2,
"data_1": "b",
"input_1": 1,
"text_1": "foo",
"id_2": 2,
"data_2": "d",
"input_2": 2,
"tag_2": "second"
}
}
]
},
"versionId": "e5d89802-2f19-42e3-8f3e-b8d5b3315227",
"triggerCount": 0,
"tags": [
{
"createdAt": "2023-02-10T11:27:50.689Z",
"updatedAt": "2023-02-10T11:27:50.689Z",
"id": "2",
"name": "n8n-test"
}
]
}
},
{
"json": {
"createdAt": "2023-02-09T13:38:39.242Z",
"updatedAt": "2023-02-10T11:27:51.000Z",
"id": "113",
"name": "rss feed test",
"active": false,
"nodes": [
{
"parameters": {},
"id": "e5fd1ef8-5528-4626-a523-7668969b2123",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
880,
400
]
},
{
"parameters": {
"url": "https://lorem-rss.herokuapp.com/feed?length=3"
},
"id": "9c40a026-fa38-4c91-a4a1-5332f762c3dd",
"name": "RSS Read",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1,
"position": [
1080,
400
]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "RSS Read",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {},
"staticData": null,
"pinData": {
"RSS Read": [
{
"json": {
"creator": "John Smith",
"title": "Lorem ipsum 2023-02-09T13:40:00Z",
"link": "http://example.com/test/1675950000",
"pubDate": "Thu, 09 Feb 2023 13:40:00 GMT",
"dc:creator": "John Smith",
"content": "Fugiat excepteur exercitation tempor ut aute sunt pariatur veniam pariatur dolor.",
"contentSnippet": "Fugiat excepteur exercitation tempor ut aute sunt pariatur veniam pariatur dolor.",
"guid": "http://example.com/test/1675950000",
"isoDate": "2023-02-09T13:40:00.000Z"
}
},
{
"json": {
"creator": "John Smith",
"title": "Lorem ipsum 2023-02-09T13:39:00Z",
"link": "http://example.com/test/1675949940",
"pubDate": "Thu, 09 Feb 2023 13:39:00 GMT",
"dc:creator": "John Smith",
"content": "Laboris quis nulla tempor eu ullamco est esse qui aute commodo aliqua occaecat.",
"contentSnippet": "Laboris quis nulla tempor eu ullamco est esse qui aute commodo aliqua occaecat.",
"guid": "http://example.com/test/1675949940",
"isoDate": "2023-02-09T13:39:00.000Z"
}
},
{
"json": {
"creator": "John Smith",
"title": "Lorem ipsum 2023-02-09T13:38:00Z",
"link": "http://example.com/test/1675949880",
"pubDate": "Thu, 09 Feb 2023 13:38:00 GMT",
"dc:creator": "John Smith",
"content": "Irure labore dolor dolore sint aliquip eu anim aute anim et nulla adipisicing nostrud.",
"contentSnippet": "Irure labore dolor dolore sint aliquip eu anim aute anim et nulla adipisicing nostrud.",
"guid": "http://example.com/test/1675949880",
"isoDate": "2023-02-09T13:38:00.000Z"
}
}
]
},
"versionId": "c0b88b07-6bb4-4b56-b692-6eafe5f77c8d",
"triggerCount": 0,
"tags": [
{
"createdAt": "2023-02-10T11:27:50.689Z",
"updatedAt": "2023-02-10T11:27:50.689Z",
"id": "2",
"name": "n8n-test"
}
]
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "4f4c9685-5901-41d6-938f-0a45ac378dec",
"id": "3",
"meta": {
"instanceId": "c9d4cba5f22cf38c90f74ed94dd16cea1f9b65fbb0fc58c6df51b44b4ca7d75d"
},
"tags": []
}