From d40b4e2aeba586bf31ef9391df0cead0f104a053 Mon Sep 17 00:00:00 2001
From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
Date: Thu, 4 Jan 2024 20:17:56 +0200
Subject: [PATCH] fix: fix use template telemetry events
---
.../editor-ui/src/stores/templates.store.ts | 2 +
.../__tests__/templateActions.test.ts | 158 ++
.../src/utils/templates/templateActions.ts | 134 +-
.../src/utils/templates/templateTransforms.ts | 39 +-
.../src/utils/testData/nodeTypeTestData.ts | 1872 +++++++++++++++++
.../src/utils/testData/templateTestData.ts | 145 ++
.../setupTemplate.store.ts | 58 +-
.../src/views/TemplatesCollectionView.vue | 23 +-
.../src/views/TemplatesWorkflowView.vue | 23 +-
9 files changed, 2361 insertions(+), 93 deletions(-)
create mode 100644 packages/editor-ui/src/utils/templates/__tests__/templateActions.test.ts
diff --git a/packages/editor-ui/src/stores/templates.store.ts b/packages/editor-ui/src/stores/templates.store.ts
index e4e827584e175..6428d6a5f4919 100644
--- a/packages/editor-ui/src/stores/templates.store.ts
+++ b/packages/editor-ui/src/stores/templates.store.ts
@@ -28,6 +28,8 @@ function getSearchKey(query: ITemplatesQuery): string {
return JSON.stringify([query.search || '', [...query.categories].sort()]);
}
+export type TemplatesStore = ReturnType By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.
Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.
Use production mode to run your workflow automatically. Activate the workflow, then make requests to the production URL. These executions will show up in the executions list, but not in the editor.',
+ active:
+ 'Webhooks have two modes: test and production.
Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.
Use production mode to run your workflow automatically. Since the workflow is activated, you can make requests to the production URL. These executions will show up in the executions list, but not in the editor.',
+ },
+ activationHint:
+ 'Once you’ve finished building your workflow, run it without having to click this button by using the production webhook URL.',
+ },
+ inputs: [],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'httpBasicAuth',
+ required: true,
+ displayOptions: { show: { authentication: ['basicAuth'] } },
+ },
+ {
+ name: 'httpHeaderAuth',
+ required: true,
+ displayOptions: { show: { authentication: ['headerAuth'] } },
+ },
+ ],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: '={{$parameter["httpMethod"] || "GET"}}',
+ isFullPath: true,
+ responseCode: '={{$parameter["responseCode"]}}',
+ responseMode: '={{$parameter["responseMode"]}}',
+ responseData:
+ '={{$parameter["responseData"] || ($parameter.options.noResponseBody ? "noData" : undefined) }}',
+ responseBinaryPropertyName: '={{$parameter["responseBinaryPropertyName"]}}',
+ responseContentType: '={{$parameter["options"]["responseContentType"]}}',
+ responsePropertyName: '={{$parameter["options"]["responsePropertyName"]}}',
+ responseHeaders: '={{$parameter["options"]["responseHeaders"]}}',
+ path: '={{$parameter["path"]}}',
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ { name: 'Basic Auth', value: 'basicAuth' },
+ { name: 'Header Auth', value: 'headerAuth' },
+ { name: 'None', value: 'none' },
+ ],
+ default: 'none',
+ description: 'The way to authenticate',
+ },
+ {
+ displayName: 'HTTP Method',
+ name: 'httpMethod',
+ type: 'options',
+ options: [
+ { name: 'DELETE', value: 'DELETE' },
+ { name: 'GET', value: 'GET' },
+ { name: 'HEAD', value: 'HEAD' },
+ { name: 'PATCH', value: 'PATCH' },
+ { name: 'POST', value: 'POST' },
+ { name: 'PUT', value: 'PUT' },
+ ],
+ default: 'GET',
+ description: 'The HTTP method to listen to',
+ },
+ {
+ displayName: 'Path',
+ name: 'path',
+ type: 'string',
+ default: '',
+ placeholder: 'webhook',
+ required: true,
+ description: 'The path to listen to',
+ },
+ {
+ displayName: 'Respond',
+ name: 'responseMode',
+ type: 'options',
+ options: [
+ {
+ name: 'Immediately',
+ value: 'onReceived',
+ description: 'As soon as this node executes',
+ },
+ {
+ name: 'When Last Node Finishes',
+ value: 'lastNode',
+ description: 'Returns data of the last-executed node',
+ },
+ {
+ name: "Using 'Respond to Webhook' Node",
+ value: 'responseNode',
+ description: 'Response defined in that node',
+ },
+ ],
+ default: 'onReceived',
+ description: 'When and how to respond to the webhook',
+ },
+ {
+ displayName:
+ 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details',
+ name: 'webhookNotice',
+ type: 'notice',
+ displayOptions: { show: { responseMode: ['responseNode'] } },
+ default: '',
+ },
+ {
+ displayName: 'Response Code',
+ name: 'responseCode',
+ type: 'number',
+ displayOptions: { hide: { responseMode: ['responseNode'] } },
+ typeOptions: { minValue: 100, maxValue: 599 },
+ default: 200,
+ description: 'The HTTP Response code to return',
+ },
+ {
+ displayName: 'Response Data',
+ name: 'responseData',
+ type: 'options',
+ displayOptions: { show: { responseMode: ['lastNode'] } },
+ options: [
+ {
+ name: 'All Entries',
+ value: 'allEntries',
+ description: 'Returns all the entries of the last node. Always returns an array.',
+ },
+ {
+ name: 'First Entry JSON',
+ value: 'firstEntryJson',
+ description:
+ 'Returns the JSON data of the first entry of the last node. Always returns a JSON object.',
+ },
+ {
+ name: 'First Entry Binary',
+ value: 'firstEntryBinary',
+ description:
+ 'Returns the binary data of the first entry of the last node. Always returns a binary file.',
+ },
+ { name: 'No Response Body', value: 'noData', description: 'Returns without a body' },
+ ],
+ default: 'firstEntryJson',
+ description:
+ 'What data should be returned. If it should return all items as an array or only the first item as object.',
+ },
+ {
+ displayName: 'Property Name',
+ name: 'responseBinaryPropertyName',
+ type: 'string',
+ required: true,
+ default: 'data',
+ displayOptions: { show: { responseData: ['firstEntryBinary'] } },
+ description: 'Name of the binary property to return',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Binary Data',
+ name: 'binaryData',
+ type: 'boolean',
+ displayOptions: { show: { '/httpMethod': ['PATCH', 'PUT', 'POST'], '@version': [1] } },
+ default: false,
+ description: 'Whether the webhook will receive binary data',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ displayOptions: { show: { binaryData: [true], '@version': [1] } },
+ description:
+ 'Name of the binary property to write the data of the received file to. If the data gets received via "Form-Data Multipart" it will be the prefix and a number starting with 0 will be attached to it.',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ displayOptions: { hide: { '@version': [1] } },
+ description:
+ 'Name of the binary property to write the data of the received file to, only relevant if binary data is received',
+ },
+ {
+ displayName: 'Ignore Bots',
+ name: 'ignoreBots',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to ignore requests from bots like link previewers and web crawlers',
+ },
+ {
+ displayName: 'No Response Body',
+ name: 'noResponseBody',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to send any body in the response',
+ displayOptions: {
+ hide: { rawBody: [true] },
+ show: { '/responseMode': ['onReceived'] },
+ },
+ },
+ {
+ displayName: 'Raw Body',
+ name: 'rawBody',
+ type: 'boolean',
+ displayOptions: {
+ show: { '@version': [1] },
+ hide: { binaryData: [true], noResponseBody: [true] },
+ },
+ default: false,
+ description: 'Raw body (binary)',
+ },
+ {
+ displayName: 'Raw Body',
+ name: 'rawBody',
+ type: 'boolean',
+ displayOptions: { hide: { noResponseBody: [true], '@version': [1] } },
+ default: false,
+ description: 'Whether to return the raw body',
+ },
+ {
+ displayName: 'Response Data',
+ name: 'responseData',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseMode': ['onReceived'] },
+ hide: { noResponseBody: [true] },
+ },
+ default: '',
+ placeholder: 'success',
+ description: 'Custom response data to send',
+ },
+ {
+ displayName: 'Response Content-Type',
+ name: 'responseContentType',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] },
+ },
+ default: '',
+ placeholder: 'application/xml',
+ description:
+ 'Set a custom content-type to return if another one as the "application/json" should be returned',
+ },
+ {
+ displayName: 'Response Headers',
+ name: 'responseHeaders',
+ placeholder: 'Add Response Header',
+ description: 'Add headers to the webhook response',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ default: {},
+ options: [
+ {
+ name: 'entries',
+ displayName: 'Entries',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the header',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value of the header',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Property Name',
+ name: 'responsePropertyName',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] },
+ },
+ default: 'data',
+ description: 'Name of the property to return the data of instead of the whole JSON',
+ },
+ {
+ displayName: 'Allowed Origins (CORS)',
+ name: 'allowedOrigins',
+ type: 'string',
+ default: '*',
+ description:
+ 'The origin(s) to allow cross-origin non-preflight requests from in a browser',
+ },
+ ],
+ },
+ ],
+ codex: {
+ categories: ['Development', 'Core Nodes'],
+ subcategories: { 'Core Nodes': ['Helpers'] },
+ resources: {
+ primaryDocumentation: [
+ { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/' },
+ ],
+ },
+ alias: ['HTTP', 'API', 'Build', 'WH'],
+ },
+ iconUrl: 'icons/n8n-nodes-base/dist/nodes/Webhook/webhook.svg',
+} satisfies INodeTypeDescription;
+
+export const nodeTypeWebhookV1_1 = {
+ displayName: 'Webhook',
+ name: 'n8n-nodes-base.webhook',
+ group: ['trigger'],
+ version: [1, 1.1],
+ description: 'Starts the workflow when a webhook is called',
+ eventTriggerDescription: 'Waiting for you to call the Test URL',
+ activationMessage: 'You can now make calls to your production webhook URL.',
+ defaults: { name: 'Webhook' },
+ supportsCORS: true,
+ triggerPanel: {
+ header: '',
+ executionsHelp: {
+ inactive:
+ 'Webhooks have two modes: test and production.
Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.
Use production mode to run your workflow automatically. Activate the workflow, then make requests to the production URL. These executions will show up in the executions list, but not in the editor.',
+ active:
+ 'Webhooks have two modes: test and production.
Use test mode while you build your workflow. Click the \'listen\' button, then make a request to the test URL. The executions will show up in the editor.
Use production mode to run your workflow automatically. Since the workflow is activated, you can make requests to the production URL. These executions will show up in the executions list, but not in the editor.',
+ },
+ activationHint:
+ 'Once you’ve finished building your workflow, run it without having to click this button by using the production webhook URL.',
+ },
+ inputs: [],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'httpBasicAuth',
+ required: true,
+ displayOptions: { show: { authentication: ['basicAuth'] } },
+ },
+ {
+ name: 'httpHeaderAuth',
+ required: true,
+ displayOptions: { show: { authentication: ['headerAuth'] } },
+ },
+ ],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: '={{$parameter["httpMethod"] || "GET"}}',
+ isFullPath: true,
+ responseCode: '={{$parameter["responseCode"]}}',
+ responseMode: '={{$parameter["responseMode"]}}',
+ responseData:
+ '={{$parameter["responseData"] || ($parameter.options.noResponseBody ? "noData" : undefined) }}',
+ responseBinaryPropertyName: '={{$parameter["responseBinaryPropertyName"]}}',
+ responseContentType: '={{$parameter["options"]["responseContentType"]}}',
+ responsePropertyName: '={{$parameter["options"]["responsePropertyName"]}}',
+ responseHeaders: '={{$parameter["options"]["responseHeaders"]}}',
+ path: '={{$parameter["path"]}}',
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ { name: 'Basic Auth', value: 'basicAuth' },
+ { name: 'Header Auth', value: 'headerAuth' },
+ { name: 'None', value: 'none' },
+ ],
+ default: 'none',
+ description: 'The way to authenticate',
+ },
+ {
+ displayName: 'HTTP Method',
+ name: 'httpMethod',
+ type: 'options',
+ options: [
+ { name: 'DELETE', value: 'DELETE' },
+ { name: 'GET', value: 'GET' },
+ { name: 'HEAD', value: 'HEAD' },
+ { name: 'PATCH', value: 'PATCH' },
+ { name: 'POST', value: 'POST' },
+ { name: 'PUT', value: 'PUT' },
+ ],
+ default: 'GET',
+ description: 'The HTTP method to listen to',
+ },
+ {
+ displayName: 'Path',
+ name: 'path',
+ type: 'string',
+ default: '',
+ placeholder: 'webhook',
+ required: true,
+ description: 'The path to listen to',
+ },
+ {
+ displayName: 'Respond',
+ name: 'responseMode',
+ type: 'options',
+ options: [
+ {
+ name: 'Immediately',
+ value: 'onReceived',
+ description: 'As soon as this node executes',
+ },
+ {
+ name: 'When Last Node Finishes',
+ value: 'lastNode',
+ description: 'Returns data of the last-executed node',
+ },
+ {
+ name: "Using 'Respond to Webhook' Node",
+ value: 'responseNode',
+ description: 'Response defined in that node',
+ },
+ ],
+ default: 'onReceived',
+ description: 'When and how to respond to the webhook',
+ },
+ {
+ displayName:
+ 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details',
+ name: 'webhookNotice',
+ type: 'notice',
+ displayOptions: { show: { responseMode: ['responseNode'] } },
+ default: '',
+ },
+ {
+ displayName: 'Response Code',
+ name: 'responseCode',
+ type: 'number',
+ displayOptions: { hide: { responseMode: ['responseNode'] } },
+ typeOptions: { minValue: 100, maxValue: 599 },
+ default: 200,
+ description: 'The HTTP Response code to return',
+ },
+ {
+ displayName: 'Response Data',
+ name: 'responseData',
+ type: 'options',
+ displayOptions: { show: { responseMode: ['lastNode'] } },
+ options: [
+ {
+ name: 'All Entries',
+ value: 'allEntries',
+ description: 'Returns all the entries of the last node. Always returns an array.',
+ },
+ {
+ name: 'First Entry JSON',
+ value: 'firstEntryJson',
+ description:
+ 'Returns the JSON data of the first entry of the last node. Always returns a JSON object.',
+ },
+ {
+ name: 'First Entry Binary',
+ value: 'firstEntryBinary',
+ description:
+ 'Returns the binary data of the first entry of the last node. Always returns a binary file.',
+ },
+ { name: 'No Response Body', value: 'noData', description: 'Returns without a body' },
+ ],
+ default: 'firstEntryJson',
+ description:
+ 'What data should be returned. If it should return all items as an array or only the first item as object.',
+ },
+ {
+ displayName: 'Property Name',
+ name: 'responseBinaryPropertyName',
+ type: 'string',
+ required: true,
+ default: 'data',
+ displayOptions: { show: { responseData: ['firstEntryBinary'] } },
+ description: 'Name of the binary property to return',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Binary Data',
+ name: 'binaryData',
+ type: 'boolean',
+ displayOptions: { show: { '/httpMethod': ['PATCH', 'PUT', 'POST'], '@version': [1] } },
+ default: false,
+ description: 'Whether the webhook will receive binary data',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ displayOptions: { show: { binaryData: [true], '@version': [1] } },
+ description:
+ 'Name of the binary property to write the data of the received file to. If the data gets received via "Form-Data Multipart" it will be the prefix and a number starting with 0 will be attached to it.',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ displayOptions: { hide: { '@version': [1] } },
+ description:
+ 'Name of the binary property to write the data of the received file to, only relevant if binary data is received',
+ },
+ {
+ displayName: 'Ignore Bots',
+ name: 'ignoreBots',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to ignore requests from bots like link previewers and web crawlers',
+ },
+ {
+ displayName: 'No Response Body',
+ name: 'noResponseBody',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to send any body in the response',
+ displayOptions: {
+ hide: { rawBody: [true] },
+ show: { '/responseMode': ['onReceived'] },
+ },
+ },
+ {
+ displayName: 'Raw Body',
+ name: 'rawBody',
+ type: 'boolean',
+ displayOptions: {
+ show: { '@version': [1] },
+ hide: { binaryData: [true], noResponseBody: [true] },
+ },
+ default: false,
+ description: 'Raw body (binary)',
+ },
+ {
+ displayName: 'Raw Body',
+ name: 'rawBody',
+ type: 'boolean',
+ displayOptions: { hide: { noResponseBody: [true], '@version': [1] } },
+ default: false,
+ description: 'Whether to return the raw body',
+ },
+ {
+ displayName: 'Response Data',
+ name: 'responseData',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseMode': ['onReceived'] },
+ hide: { noResponseBody: [true] },
+ },
+ default: '',
+ placeholder: 'success',
+ description: 'Custom response data to send',
+ },
+ {
+ displayName: 'Response Content-Type',
+ name: 'responseContentType',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] },
+ },
+ default: '',
+ placeholder: 'application/xml',
+ description:
+ 'Set a custom content-type to return if another one as the "application/json" should be returned',
+ },
+ {
+ displayName: 'Response Headers',
+ name: 'responseHeaders',
+ placeholder: 'Add Response Header',
+ description: 'Add headers to the webhook response',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ default: {},
+ options: [
+ {
+ name: 'entries',
+ displayName: 'Entries',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the header',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value of the header',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Property Name',
+ name: 'responsePropertyName',
+ type: 'string',
+ displayOptions: {
+ show: { '/responseData': ['firstEntryJson'], '/responseMode': ['lastNode'] },
+ },
+ default: 'data',
+ description: 'Name of the property to return the data of instead of the whole JSON',
+ },
+ {
+ displayName: 'Allowed Origins (CORS)',
+ name: 'allowedOrigins',
+ type: 'string',
+ default: '*',
+ description:
+ 'The origin(s) to allow cross-origin non-preflight requests from in a browser',
+ },
+ ],
+ },
+ ],
+ codex: {
+ categories: ['Development', 'Core Nodes'],
+ subcategories: { 'Core Nodes': ['Helpers'] },
+ resources: {
+ primaryDocumentation: [
+ { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/' },
+ ],
+ },
+ alias: ['HTTP', 'API', 'Build', 'WH'],
+ },
+ iconUrl: 'icons/n8n-nodes-base/dist/nodes/Webhook/webhook.svg',
+} satisfies INodeTypeDescription;
+
+export const nodeTypesSet = {
+ '1': {
+ displayName: 'Set',
+ name: 'n8n-nodes-base.set',
+ icon: 'fa:pen',
+ group: ['input'],
+ description: 'Sets values on items and optionally remove other values',
+ defaultVersion: 3.2,
+ version: [1, 2],
+ defaults: { name: 'Set', color: '#0000FF' },
+ inputs: ['main'],
+ outputs: ['main'],
+ properties: [
+ {
+ displayName: 'Keep Only Set',
+ name: 'keepOnlySet',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether only the values set on this node should be kept and all others removed',
+ },
+ {
+ displayName: 'Values to Set',
+ name: 'values',
+ placeholder: 'Add Value',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true, sortable: true },
+ description: 'The value to set',
+ default: {},
+ options: [
+ {
+ name: 'boolean',
+ displayName: 'Boolean',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ requiresDataPath: 'single',
+ default: 'propertyName',
+ description:
+ 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'boolean',
+ default: false,
+ description: 'The boolean value to write in the property',
+ },
+ ],
+ },
+ {
+ name: 'number',
+ displayName: 'Number',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: 'propertyName',
+ requiresDataPath: 'single',
+ description:
+ 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'number',
+ default: 0,
+ description: 'The number value to write in the property',
+ },
+ ],
+ },
+ {
+ name: 'string',
+ displayName: 'String',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: 'propertyName',
+ requiresDataPath: 'single',
+ description:
+ 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'The string value to write in the property',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Dot Notation',
+ name: 'dotNotation',
+ type: 'boolean',
+ default: true,
+ description:
+ '
By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.
If that is not intended this can be deactivated, it will then set { "a.b": value } instead..', + }, + ], + }, + ], + codex: { + categories: ['Core Nodes'], + subcategories: { 'Core Nodes': ['Data Transformation'] }, + resources: { + primaryDocumentation: [ + { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, + ], + }, + alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], + }, + }, + '3': { + displayName: 'Edit Fields (Set)', + name: 'n8n-nodes-base.set', + icon: 'fa:pen', + group: ['input'], + description: 'Modify, add, or remove item fields', + defaultVersion: 3.2, + version: [3, 3.1, 3.2], + subtitle: '={{$parameter["mode"]}}', + defaults: { name: 'Edit Fields', color: '#0000FF' }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Manual Mapping', + value: 'manual', + description: 'Edit item fields one by one', + action: 'Edit item fields one by one', + }, + { + name: 'JSON Output', + value: 'raw', + description: 'Customize item output with JSON', + action: 'Customize item output with JSON', + }, + ], + default: 'manual', + }, + { + displayName: 'Duplicate Item', + name: 'duplicateItem', + type: 'boolean', + default: false, + isNodeSetting: true, + }, + { + displayName: 'Duplicate Item Count', + name: 'duplicateCount', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: + 'How many times the item should be duplicated, mainly used for testing and debugging', + isNodeSetting: true, + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: + 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', + name: 'duplicateWarning', + type: 'notice', + default: '', + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: 'JSON Output', + name: 'jsonOutput', + type: 'json', + typeOptions: { rows: 5 }, + default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', + validateType: 'object', + ignoreValidationDuringExecution: true, + displayOptions: { show: { mode: ['raw'] } }, + }, + { + displayName: 'Fields to Set', + name: 'fields', + placeholder: 'Add Field', + type: 'fixedCollection', + description: 'Edit existing fields or add new ones to modify the output data', + typeOptions: { multipleValues: true, sortable: true }, + default: {}, + options: [ + { + name: 'values', + displayName: 'Values', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: 'e.g. fieldName', + description: + 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', + requiresDataPath: 'single', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + description: 'The field value type', + options: [ + { name: 'String', value: 'stringValue' }, + { name: 'Number', value: 'numberValue' }, + { name: 'Boolean', value: 'booleanValue' }, + { name: 'Array', value: 'arrayValue' }, + { name: 'Object', value: 'objectValue' }, + ], + default: 'stringValue', + }, + { + displayName: 'Value', + name: 'stringValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['stringValue'] } }, + validateType: 'string', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'numberValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['numberValue'] } }, + validateType: 'number', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'booleanValue', + type: 'options', + default: 'true', + options: [ + { name: 'True', value: 'true' }, + { name: 'False', value: 'false' }, + ], + displayOptions: { show: { type: ['booleanValue'] } }, + validateType: 'boolean', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'arrayValue', + type: 'string', + default: '', + placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', + displayOptions: { show: { type: ['arrayValue'] } }, + validateType: 'array', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'objectValue', + type: 'json', + default: '={}', + typeOptions: { rows: 2 }, + displayOptions: { show: { type: ['objectValue'] } }, + validateType: 'object', + ignoreValidationDuringExecution: true, + }, + ], + }, + ], + displayOptions: { show: { mode: ['manual'] } }, + }, + { + displayName: 'Include in Output', + name: 'include', + type: 'options', + description: 'How to select the fields you want to include in your output items', + default: 'all', + options: [ + { + name: 'All Input Fields', + value: 'all', + description: 'Also include all unchanged fields from the input', + }, + { + name: 'No Input Fields', + value: 'none', + description: 'Include only the fields specified above', + }, + { + name: 'Selected Input Fields', + value: 'selected', + description: 'Also include the fields listed in the parameter “Fields to Include”', + }, + { + name: 'All Input Fields Except', + value: 'except', + description: 'Exclude the fields listed in the parameter “Fields to Exclude”', + }, + ], + }, + { + displayName: 'Fields to Include', + name: 'includeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToInclude1,fieldToInclude2', + description: + 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['selected'] } }, + }, + { + displayName: 'Fields to Exclude', + name: 'excludeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToExclude1,fieldToExclude2', + description: + 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['except'] } }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Binary Data', + name: 'includeBinary', + type: 'boolean', + default: true, + description: 'Whether binary data should be included if present in the input item', + }, + { + displayName: 'Ignore Type Conversion Errors', + name: 'ignoreConversionErrors', + type: 'boolean', + default: false, + description: + 'Whether to ignore field type errors and apply a less strict type conversion', + displayOptions: { show: { '/mode': ['manual'] } }, + }, + { + displayName: 'Support Dot Notation', + name: 'dotNotation', + type: 'boolean', + default: true, + description: + 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', + }, + ], + }, + ], + codex: { + categories: ['Core Nodes'], + subcategories: { 'Core Nodes': ['Data Transformation'] }, + resources: { + primaryDocumentation: [ + { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, + ], + }, + alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], + }, + }, + '3.1': { + displayName: 'Edit Fields (Set)', + name: 'n8n-nodes-base.set', + icon: 'fa:pen', + group: ['input'], + description: 'Modify, add, or remove item fields', + defaultVersion: 3.2, + version: [3, 3.1, 3.2], + subtitle: '={{$parameter["mode"]}}', + defaults: { name: 'Edit Fields', color: '#0000FF' }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Manual Mapping', + value: 'manual', + description: 'Edit item fields one by one', + action: 'Edit item fields one by one', + }, + { + name: 'JSON Output', + value: 'raw', + description: 'Customize item output with JSON', + action: 'Customize item output with JSON', + }, + ], + default: 'manual', + }, + { + displayName: 'Duplicate Item', + name: 'duplicateItem', + type: 'boolean', + default: false, + isNodeSetting: true, + }, + { + displayName: 'Duplicate Item Count', + name: 'duplicateCount', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: + 'How many times the item should be duplicated, mainly used for testing and debugging', + isNodeSetting: true, + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: + 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', + name: 'duplicateWarning', + type: 'notice', + default: '', + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: 'JSON Output', + name: 'jsonOutput', + type: 'json', + typeOptions: { rows: 5 }, + default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', + validateType: 'object', + ignoreValidationDuringExecution: true, + displayOptions: { show: { mode: ['raw'] } }, + }, + { + displayName: 'Fields to Set', + name: 'fields', + placeholder: 'Add Field', + type: 'fixedCollection', + description: 'Edit existing fields or add new ones to modify the output data', + typeOptions: { multipleValues: true, sortable: true }, + default: {}, + options: [ + { + name: 'values', + displayName: 'Values', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: 'e.g. fieldName', + description: + 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', + requiresDataPath: 'single', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + description: 'The field value type', + options: [ + { name: 'String', value: 'stringValue' }, + { name: 'Number', value: 'numberValue' }, + { name: 'Boolean', value: 'booleanValue' }, + { name: 'Array', value: 'arrayValue' }, + { name: 'Object', value: 'objectValue' }, + ], + default: 'stringValue', + }, + { + displayName: 'Value', + name: 'stringValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['stringValue'] } }, + validateType: 'string', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'numberValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['numberValue'] } }, + validateType: 'number', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'booleanValue', + type: 'options', + default: 'true', + options: [ + { name: 'True', value: 'true' }, + { name: 'False', value: 'false' }, + ], + displayOptions: { show: { type: ['booleanValue'] } }, + validateType: 'boolean', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'arrayValue', + type: 'string', + default: '', + placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', + displayOptions: { show: { type: ['arrayValue'] } }, + validateType: 'array', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'objectValue', + type: 'json', + default: '={}', + typeOptions: { rows: 2 }, + displayOptions: { show: { type: ['objectValue'] } }, + validateType: 'object', + ignoreValidationDuringExecution: true, + }, + ], + }, + ], + displayOptions: { show: { mode: ['manual'] } }, + }, + { + displayName: 'Include in Output', + name: 'include', + type: 'options', + description: 'How to select the fields you want to include in your output items', + default: 'all', + options: [ + { + name: 'All Input Fields', + value: 'all', + description: 'Also include all unchanged fields from the input', + }, + { + name: 'No Input Fields', + value: 'none', + description: 'Include only the fields specified above', + }, + { + name: 'Selected Input Fields', + value: 'selected', + description: 'Also include the fields listed in the parameter “Fields to Include”', + }, + { + name: 'All Input Fields Except', + value: 'except', + description: 'Exclude the fields listed in the parameter “Fields to Exclude”', + }, + ], + }, + { + displayName: 'Fields to Include', + name: 'includeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToInclude1,fieldToInclude2', + description: + 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['selected'] } }, + }, + { + displayName: 'Fields to Exclude', + name: 'excludeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToExclude1,fieldToExclude2', + description: + 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['except'] } }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Binary Data', + name: 'includeBinary', + type: 'boolean', + default: true, + description: 'Whether binary data should be included if present in the input item', + }, + { + displayName: 'Ignore Type Conversion Errors', + name: 'ignoreConversionErrors', + type: 'boolean', + default: false, + description: + 'Whether to ignore field type errors and apply a less strict type conversion', + displayOptions: { show: { '/mode': ['manual'] } }, + }, + { + displayName: 'Support Dot Notation', + name: 'dotNotation', + type: 'boolean', + default: true, + description: + 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', + }, + ], + }, + ], + codex: { + categories: ['Core Nodes'], + subcategories: { 'Core Nodes': ['Data Transformation'] }, + resources: { + primaryDocumentation: [ + { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, + ], + }, + alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], + }, + }, + '3.2': { + displayName: 'Edit Fields (Set)', + name: 'n8n-nodes-base.set', + icon: 'fa:pen', + group: ['input'], + description: 'Modify, add, or remove item fields', + defaultVersion: 3.2, + version: [3, 3.1, 3.2], + subtitle: '={{$parameter["mode"]}}', + defaults: { name: 'Edit Fields', color: '#0000FF' }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Manual Mapping', + value: 'manual', + description: 'Edit item fields one by one', + action: 'Edit item fields one by one', + }, + { + name: 'JSON Output', + value: 'raw', + description: 'Customize item output with JSON', + action: 'Customize item output with JSON', + }, + ], + default: 'manual', + }, + { + displayName: 'Duplicate Item', + name: 'duplicateItem', + type: 'boolean', + default: false, + isNodeSetting: true, + }, + { + displayName: 'Duplicate Item Count', + name: 'duplicateCount', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: + 'How many times the item should be duplicated, mainly used for testing and debugging', + isNodeSetting: true, + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: + 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', + name: 'duplicateWarning', + type: 'notice', + default: '', + displayOptions: { show: { duplicateItem: [true] } }, + }, + { + displayName: 'JSON Output', + name: 'jsonOutput', + type: 'json', + typeOptions: { rows: 5 }, + default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}', + validateType: 'object', + ignoreValidationDuringExecution: true, + displayOptions: { show: { mode: ['raw'] } }, + }, + { + displayName: 'Fields to Set', + name: 'fields', + placeholder: 'Add Field', + type: 'fixedCollection', + description: 'Edit existing fields or add new ones to modify the output data', + typeOptions: { multipleValues: true, sortable: true }, + default: {}, + options: [ + { + name: 'values', + displayName: 'Values', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: 'e.g. fieldName', + description: + 'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.', + requiresDataPath: 'single', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + description: 'The field value type', + options: [ + { name: 'String', value: 'stringValue' }, + { name: 'Number', value: 'numberValue' }, + { name: 'Boolean', value: 'booleanValue' }, + { name: 'Array', value: 'arrayValue' }, + { name: 'Object', value: 'objectValue' }, + ], + default: 'stringValue', + }, + { + displayName: 'Value', + name: 'stringValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['stringValue'] } }, + validateType: 'string', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'numberValue', + type: 'string', + default: '', + displayOptions: { show: { type: ['numberValue'] } }, + validateType: 'number', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'booleanValue', + type: 'options', + default: 'true', + options: [ + { name: 'True', value: 'true' }, + { name: 'False', value: 'false' }, + ], + displayOptions: { show: { type: ['booleanValue'] } }, + validateType: 'boolean', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'arrayValue', + type: 'string', + default: '', + placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]', + displayOptions: { show: { type: ['arrayValue'] } }, + validateType: 'array', + ignoreValidationDuringExecution: true, + }, + { + displayName: 'Value', + name: 'objectValue', + type: 'json', + default: '={}', + typeOptions: { rows: 2 }, + displayOptions: { show: { type: ['objectValue'] } }, + validateType: 'object', + ignoreValidationDuringExecution: true, + }, + ], + }, + ], + displayOptions: { show: { mode: ['manual'] } }, + }, + { + displayName: 'Include in Output', + name: 'include', + type: 'options', + description: 'How to select the fields you want to include in your output items', + default: 'all', + options: [ + { + name: 'All Input Fields', + value: 'all', + description: 'Also include all unchanged fields from the input', + }, + { + name: 'No Input Fields', + value: 'none', + description: 'Include only the fields specified above', + }, + { + name: 'Selected Input Fields', + value: 'selected', + description: 'Also include the fields listed in the parameter “Fields to Include”', + }, + { + name: 'All Input Fields Except', + value: 'except', + description: 'Exclude the fields listed in the parameter “Fields to Exclude”', + }, + ], + }, + { + displayName: 'Fields to Include', + name: 'includeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToInclude1,fieldToInclude2', + description: + 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['selected'] } }, + }, + { + displayName: 'Fields to Exclude', + name: 'excludeFields', + type: 'string', + default: '', + placeholder: 'e.g. fieldToExclude1,fieldToExclude2', + description: + 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', + requiresDataPath: 'multiple', + displayOptions: { show: { include: ['except'] } }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Binary Data', + name: 'includeBinary', + type: 'boolean', + default: true, + description: 'Whether binary data should be included if present in the input item', + }, + { + displayName: 'Ignore Type Conversion Errors', + name: 'ignoreConversionErrors', + type: 'boolean', + default: false, + description: + 'Whether to ignore field type errors and apply a less strict type conversion', + displayOptions: { show: { '/mode': ['manual'] } }, + }, + { + displayName: 'Support Dot Notation', + name: 'dotNotation', + type: 'boolean', + default: true, + description: + 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', + }, + ], + }, + ], + codex: { + categories: ['Core Nodes'], + subcategories: { 'Core Nodes': ['Data Transformation'] }, + resources: { + primaryDocumentation: [ + { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/' }, + ], + }, + alias: ['Set', 'JSON', 'Filter', 'Transform', 'Map'], + }, + }, +} satisfies { [version: string]: INodeTypeDescription }; diff --git a/packages/editor-ui/src/utils/testData/templateTestData.ts b/packages/editor-ui/src/utils/testData/templateTestData.ts index 4243d1871b913..6a1e156063756 100644 --- a/packages/editor-ui/src/utils/testData/templateTestData.ts +++ b/packages/editor-ui/src/utils/testData/templateTestData.ts @@ -290,3 +290,148 @@ export const fullSaveEmailAttachmentsToNextCloudTemplate = { image: [], full: true, } satisfies ITemplatesWorkflowFull; + +/** Template that doesn't contain nodes requiring credentials */ +export const fullCreateApiEndpointTemplate = { + id: 1750, + name: 'Creating an API endpoint', + views: 13265, + recentViews: 9899, + totalViews: 13265, + createdAt: '2022-07-06T14:45:19.659Z', + description: + '**Task:**\nCreate a simple API endpoint using the Webhook and Respond to Webhook nodes\n\n**Why:**\nYou can prototype or replace a backend process with a single workflow\n\n**Main use cases:**\nReplace backend logic with a workflow', + workflow: { + meta: { instanceId: '8c8c5237b8e37b006a7adce87f4369350c58e41f3ca9de16196d3197f69eabcd' }, + nodes: [ + { + id: 'f80aceed-b676-42aa-bf25-f7a44408b1bc', + name: 'Webhook', + type: 'n8n-nodes-base.webhook', + position: [375, 115], + webhookId: '6f7b288e-1efe-4504-a6fd-660931327269', + parameters: { + path: '6f7b288e-1efe-4504-a6fd-660931327269', + options: {}, + responseMode: 'responseNode', + }, + typeVersion: 1, + }, + { + id: '3b9ec913-0bbe-4906-bf8e-da352b556655', + name: 'Note1', + type: 'n8n-nodes-base.stickyNote', + position: [355, -25], + parameters: { + width: 600, + height: 280, + content: + '## Create a simple API endpoint\n\nIn this workflow we show how to create a simple API endpoint with `Webhook` and `Respond to Webhook` nodes\n\n', + }, + typeVersion: 1, + }, + { + id: '9c36dae5-0700-450c-9739-e9f3eff31bfe', + name: 'Respond to Webhook', + type: 'n8n-nodes-base.respondToWebhook', + position: [815, 115], + parameters: { + options: {}, + respondWith: 'text', + responseBody: + '=The URL of the Google search query for the term "{{$node["Webhook"].json["query"]["first_name"]}} {{$node["Webhook"].json["query"]["last_name"]}}" is: {{$json["product"]}}', + }, + typeVersion: 1, + }, + { + id: '5a228fcb-78b9-4a28-95d2-d7c9fdf1d4ea', + name: 'Create URL string', + type: 'n8n-nodes-base.set', + position: [595, 115], + parameters: { + values: { + string: [ + { + name: 'product', + value: + '=https://www.google.com/search?q={{$json["query"]["first_name"]}}+{{$json["query"]["last_name"]}}', + }, + ], + }, + options: {}, + keepOnlySet: true, + }, + typeVersion: 1, + }, + { + id: 'e7971820-45a8-4dc8-ba4c-b3220d65307a', + name: 'Note3', + type: 'n8n-nodes-base.stickyNote', + position: [355, 275], + parameters: { + width: 600, + height: 220, + content: + '### How to use it\n1. Execute the workflow so that the webhook starts listening\n2. Make a test request by pasting, **in a new browser tab**, the test URL from the `Webhook` node and appending the following test at the end `?first_name=bob&last_name=dylan`\n\nYou will receive the following output in the new tab `The URL of the Google search query for the term "bob dylan" is: https://www.google.com/search?q=bob+dylan`\n\n', + }, + typeVersion: 1, + }, + ], + connections: { + Webhook: { main: [[{ node: 'Create URL string', type: 'main', index: 0 }]] }, + 'Create URL string': { main: [[{ node: 'Respond to Webhook', type: 'main', index: 0 }]] }, + }, + }, + lastUpdatedBy: 1, + workflowInfo: null, + user: { username: 'jon-n8n' }, + nodes: [ + { + id: 38, + icon: 'fa:pen', + name: 'n8n-nodes-base.set', + defaults: { name: 'Edit Fields', color: '#0000FF' }, + iconData: { icon: 'pen', type: 'icon' }, + categories: [{ id: 9, name: 'Core Nodes' }], + displayName: 'Edit Fields (Set)', + typeVersion: 3, + }, + { + id: 47, + icon: 'file:webhook.svg', + name: 'n8n-nodes-base.webhook', + defaults: { name: 'Webhook' }, + iconData: { + type: 'file', + fileBuffer: + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0zNSwzN2MtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMzcuMiwzNywzNSwzN3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUsNDNjLTMsMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEsMS40LDIuOSwyLjMsNC43LDIuM2MzLjMsMCw2LTIuNyw2LTZzLTIuNy02LTYtNiBjLTEsMC0yLDAuMy0yLjksMC43bC0xLjcsMUwyMy4zLDE2bDMuNS0xLjlsNS4zLDkuNGMxLTAuMywyLTAuNSwzLTAuNWM1LjUsMCwxMCw0LjUsMTAsMTBTNDAuNSw0MywzNSw0M3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMTQsNDNDOC41LDQzLDQsMzguNSw0LDMzYzAtNC42LDMuMS04LjUsNy41LTkuN2wxLDMuOUM5LjksMjcuOSw4LDMwLjMsOCwzM2MwLDMuMywyLjcsNiw2LDYgczYtMi43LDYtNnYtMmgxNXY0SDIzLjhDMjIuOSwzOS42LDE4LjgsNDMsMTQsNDN6Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0iTTE0LDM3Yy0yLjIsMC00LTEuOC00LTRzMS44LTQsNC00czQsMS44LDQsNFMxNi4yLDM3LDE0LDM3eiIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSwxOWMtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMjcuMiwxOSwyNSwxOXoiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTUuNywzNEwxMi4zLDMybDUuOS05LjdjLTItMS45LTMuMi00LjUtMy4yLTcuM2MwLTUuNSw0LjUtMTAsMTAtMTBjNS41LDAsMTAsNC41LDEwLDEwIGMwLDAuOS0wLjEsMS43LTAuMywyLjVsLTMuOS0xYzAuMS0wLjUsMC4yLTEsMC4yLTEuNWMwLTMuMy0yLjctNi02LTZzLTYsMi43LTYsNmMwLDIuMSwxLjEsNCwyLjksNS4xbDEuNywxTDE1LjcsMzR6Ii8+PC9zdmc+Cg==', + }, + categories: [ + { id: 5, name: 'Development' }, + { id: 9, name: 'Core Nodes' }, + ], + displayName: 'Webhook', + typeVersion: 1, + }, + { + id: 535, + icon: 'file:webhook.svg', + name: 'n8n-nodes-base.respondToWebhook', + defaults: { name: 'Respond to Webhook' }, + iconData: { + type: 'file', + fileBuffer: + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0zNSwzN2MtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMzcuMiwzNywzNSwzN3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUsNDNjLTMsMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEsMS40LDIuOSwyLjMsNC43LDIuM2MzLjMsMCw2LTIuNyw2LTZzLTIuNy02LTYtNiBjLTEsMC0yLDAuMy0yLjksMC43bC0xLjcsMUwyMy4zLDE2bDMuNS0xLjlsNS4zLDkuNGMxLTAuMywyLTAuNSwzLTAuNWM1LjUsMCwxMCw0LjUsMTAsMTBTNDAuNSw0MywzNSw0M3oiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMTQsNDNDOC41LDQzLDQsMzguNSw0LDMzYzAtNC42LDMuMS04LjUsNy41LTkuN2wxLDMuOUM5LjksMjcuOSw4LDMwLjMsOCwzM2MwLDMuMywyLjcsNiw2LDYgczYtMi43LDYtNnYtMmgxNXY0SDIzLjhDMjIuOSwzOS42LDE4LjgsNDMsMTQsNDN6Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0iTTE0LDM3Yy0yLjIsMC00LTEuOC00LTRzMS44LTQsNC00czQsMS44LDQsNFMxNi4yLDM3LDE0LDM3eiIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSwxOWMtMi4yLDAtNC0xLjgtNC00czEuOC00LDQtNHM0LDEuOCw0LDRTMjcuMiwxOSwyNSwxOXoiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTUuNywzNEwxMi4zLDMybDUuOS05LjdjLTItMS45LTMuMi00LjUtMy4yLTcuM2MwLTUuNSw0LjUtMTAsMTAtMTBjNS41LDAsMTAsNC41LDEwLDEwIGMwLDAuOS0wLjEsMS43LTAuMywyLjVsLTMuOS0xYzAuMS0wLjUsMC4yLTEsMC4yLTEuNWMwLTMuMy0yLjctNi02LTZzLTYsMi43LTYsNmMwLDIuMSwxLjEsNCwyLjksNS4xbDEuNywxTDE1LjcsMzR6Ii8+PC9zdmc+Cg==', + }, + categories: [ + { id: 7, name: 'Utility' }, + { id: 9, name: 'Core Nodes' }, + ], + displayName: 'Respond to Webhook', + typeVersion: 1, + }, + ], + categories: [{ id: 20, name: 'Building Blocks' }], + image: [], + full: true, +} satisfies ITemplatesWorkflowFull; diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/setupTemplate.store.ts b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/setupTemplate.store.ts index 634f0ff3d6a3e..7c72c1c4485b9 100644 --- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/setupTemplate.store.ts +++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/setupTemplate.store.ts @@ -8,28 +8,21 @@ import { useRootStore } from '@/stores/n8nRoot.store'; import { useTemplatesStore } from '@/stores/templates.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { getAppNameFromNodeName } from '@/utils/nodeTypesUtils'; -import type { - INodeCredentialDescription, - INodeCredentialsDetails, - INodeTypeDescription, -} from 'n8n-workflow'; -import type { - ICredentialsResponse, - INodeUi, - ITemplatesWorkflowFull, - IWorkflowTemplateNode, -} from '@/Interface'; +import type { INodeCredentialsDetails, INodeTypeDescription } from 'n8n-workflow'; +import type { ICredentialsResponse, INodeUi, IWorkflowTemplateNode } from '@/Interface'; import { VIEWS } from '@/constants'; import { createWorkflowFromTemplate } from '@/utils/templates/templateActions'; -import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms'; +import type { + TemplateCredentialKey, + TemplateNodeWithRequiredCredential, +} from '@/utils/templates/templateTransforms'; import { + getNodesRequiringCredentials, keyFromCredentialTypeAndName, normalizeTemplateNodeCredentials, } from '@/utils/templates/templateTransforms'; import { useExternalHooks } from '@/composables/useExternalHooks'; import { useTelemetry } from '@/composables/useTelemetry'; -import { getNodeTypeDisplayableCredentials } from '@/utils/nodes/nodeTransforms'; -import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms'; export type NodeAndType = { node: INodeUi; @@ -64,35 +57,8 @@ export type AppCredentialCount = { count: number; }; -export type TemplateNodeWithRequiredCredential = { - node: IWorkflowTemplateNode; - requiredCredentials: INodeCredentialDescription[]; -}; - //#region Getter functions -/** - * Returns the nodes in the template that require credentials - * and the required credentials for each node. - */ -export const getNodesRequiringCredentials = ( - nodeTypeProvider: NodeTypeProvider, - template: ITemplatesWorkflowFull, -): TemplateNodeWithRequiredCredential[] => { - if (!template) { - return []; - } - - const nodesWithCredentials: TemplateNodeWithRequiredCredential[] = template.workflow.nodes - .map((node) => ({ - node, - requiredCredentials: getNodeTypeDisplayableCredentials(nodeTypeProvider, node), - })) - .filter(({ requiredCredentials }) => requiredCredentials.length > 0); - - return nodesWithCredentials; -}; - export const groupNodeCredentialsByKey = ( nodeWithRequiredCredentials: TemplateNodeWithRequiredCredential[], ) => { @@ -378,6 +344,16 @@ export const useSetupTemplateStore = defineStore('setupTemplate', () => { completed: true, }); + const telemetryPayload = { + source: 'workflow', + template_id: template.value.id, + wf_template_repo_session_id: templatesStore.currentSessionId, + }; + + telemetry.track('User inserted workflow template', telemetryPayload, { + withPostHog: true, + }); + // Replace the URL so back button doesn't come back to this setup view await router.replace({ name: VIEWS.WORKFLOW, diff --git a/packages/editor-ui/src/views/TemplatesCollectionView.vue b/packages/editor-ui/src/views/TemplatesCollectionView.vue index f9e69e07cca25..ad3fb6e56bf0d 100644 --- a/packages/editor-ui/src/views/TemplatesCollectionView.vue +++ b/packages/editor-ui/src/views/TemplatesCollectionView.vue @@ -66,11 +66,12 @@ import type { } from '@/Interface'; import { setPageTitle } from '@/utils/htmlUtils'; -import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, VIEWS } from '@/constants'; +import { VIEWS } from '@/constants'; import { useTemplatesStore } from '@/stores/templates.store'; import { usePostHog } from '@/stores/posthog.store'; -import { openTemplateCredentialSetup } from '@/utils/templates/templateActions'; +import { useTemplateWorkflow } from '@/utils/templates/templateActions'; import { useExternalHooks } from '@/composables/useExternalHooks'; +import { useNodeTypesStore } from '@/stores/nodeTypes.store'; export default defineComponent({ name: 'TemplatesCollectionView', @@ -152,23 +153,15 @@ export default defineComponent({ this.navigateTo(event, VIEWS.TEMPLATE, id); }, async onUseWorkflow({ event, id }: { event: MouseEvent; id: string }) { - if (this.posthogStore.isFeatureEnabled(TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT)) { - const telemetryPayload = { - template_id: id, - wf_template_repo_session_id: this.templatesStore.currentSessionId, - source: 'collection', - }; - await this.externalHooks.run('templatesCollectionView.onUseWorkflow', telemetryPayload); - this.$telemetry.track('User inserted workflow template', telemetryPayload, { - withPostHog: true, - }); - } - - await openTemplateCredentialSetup({ + await useTemplateWorkflow({ posthogStore: this.posthogStore, router: this.$router, templateId: id, inNewBrowserTab: event.metaKey || event.ctrlKey, + templatesStore: useTemplatesStore(), + externalHooks: this.externalHooks, + nodeTypesStore: useNodeTypesStore(), + telemetry: this.$telemetry, }); }, navigateTo(e: MouseEvent, page: string, id: string) { diff --git a/packages/editor-ui/src/views/TemplatesWorkflowView.vue b/packages/editor-ui/src/views/TemplatesWorkflowView.vue index 9e8125b1223bf..f867384275e69 100644 --- a/packages/editor-ui/src/views/TemplatesWorkflowView.vue +++ b/packages/editor-ui/src/views/TemplatesWorkflowView.vue @@ -68,9 +68,9 @@ import { workflowHelpers } from '@/mixins/workflowHelpers'; import { setPageTitle } from '@/utils/htmlUtils'; import { useTemplatesStore } from '@/stores/templates.store'; import { usePostHog } from '@/stores/posthog.store'; -import { openTemplateCredentialSetup } from '@/utils/templates/templateActions'; +import { useTemplateWorkflow } from '@/utils/templates/templateActions'; import { useExternalHooks } from '@/composables/useExternalHooks'; -import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT } from '@/constants'; +import { useNodeTypesStore } from '@/stores/nodeTypes.store'; export default defineComponent({ name: 'TemplatesWorkflowView', @@ -132,24 +132,15 @@ export default defineComponent({ }, methods: { async openTemplateSetup(id: string, e: PointerEvent) { - if (!this.posthogStore.isFeatureEnabled(TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT)) { - const telemetryPayload = { - source: 'workflow', - template_id: id, - wf_template_repo_session_id: this.templatesStore.currentSessionId, - }; - - this.$telemetry.track('User inserted workflow template', telemetryPayload, { - withPostHog: true, - }); - await this.externalHooks.run('templatesWorkflowView.openWorkflow', telemetryPayload); - } - - await openTemplateCredentialSetup({ + await useTemplateWorkflow({ posthogStore: this.posthogStore, router: this.$router, templateId: id, inNewBrowserTab: e.metaKey || e.ctrlKey, + externalHooks: this.externalHooks, + nodeTypesStore: useNodeTypesStore(), + telemetry: this.$telemetry, + templatesStore: useTemplatesStore(), }); }, onHidePreview() {