feat(editor): Node Creator AI nodes improvements (#9484)

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
oleg 2024-05-30 16:53:33 +02:00 committed by GitHub
parent e68a3fd6ce
commit be4f54de15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 661 additions and 204 deletions

View file

@ -35,7 +35,7 @@ export const INSTANCE_MEMBERS = [
]; ];
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger'; export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Test workflow"'; export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking Test workflow';
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger'; export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger'; export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
export const CODE_NODE_NAME = 'Code'; export const CODE_NODE_NAME = 'Code';

View file

@ -510,7 +510,7 @@ describe('Execution', () => {
cy.wait('@workflowRun').then((interception) => { cy.wait('@workflowRun').then((interception) => {
expect(interception.request.body).to.have.property('runData').that.is.an('object'); expect(interception.request.body).to.have.property('runData').that.is.an('object');
const expectedKeys = ['When clicking "Test workflow"', 'fetch 5 random users']; const expectedKeys = ['When clicking Test workflow', 'fetch 5 random users'];
expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length); expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length);
expect(interception.request.body.runData).to.include.all.keys(expectedKeys); expect(interception.request.body.runData).to.include.all.keys(expectedKeys);

View file

@ -35,7 +35,7 @@ describe('Node Creator', () => {
nodeCreatorFeature.actions.openNodeCreator(); nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.searchBar().find('input').type('manual'); nodeCreatorFeature.getters.searchBar().find('input').type('manual');
nodeCreatorFeature.getters.creatorItem().should('have.length', 2); nodeCreatorFeature.getters.creatorItem().should('have.length', 1);
nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123');
nodeCreatorFeature.getters.creatorItem().should('have.length', 0); nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
nodeCreatorFeature.getters nodeCreatorFeature.getters
@ -159,7 +159,7 @@ describe('Node Creator', () => {
it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => { it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => {
nodeCreatorFeature.actions.openNodeCreator(); nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.getCreatorItem('Manually').click(); nodeCreatorFeature.getters.getCreatorItem('Trigger manually').click();
nodeCreatorFeature.actions.openNodeCreator(); nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
@ -308,7 +308,7 @@ describe('Node Creator', () => {
nodeCreatorFeature.getters.getCategoryItem('Actions').click(); nodeCreatorFeature.getters.getCategoryItem('Actions').click();
nodeCreatorFeature.getters.getCreatorItem('Create a credential').click(); nodeCreatorFeature.getters.getCreatorItem('Create a credential').click();
NDVModal.actions.close(); NDVModal.actions.close();
WorkflowPage.actions.deleteNode('When clicking "Test workflow"'); WorkflowPage.actions.deleteNode('When clicking Test workflow');
WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click(); WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click();
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
nodeCreatorFeature.getters.getCreatorItem('n8n').click(); nodeCreatorFeature.getters.getCreatorItem('n8n').click();

View file

@ -38,7 +38,7 @@ describe('Editors', () => {
}); });
ndv.actions.close(); ndv.actions.close();
workflowPage.actions.openNode('When clicking "Test workflow"'); workflowPage.actions.openNode('When clicking Test workflow');
ndv.actions.setPinnedData([{ table: 'test_table' }]); ndv.actions.setPinnedData([{ table: 'test_table' }]);
ndv.actions.close(); ndv.actions.close();

View file

@ -652,7 +652,7 @@ describe('NDV', () => {
ndv.getters.backToCanvas().click(); ndv.getters.backToCanvas().click();
workflowPage.actions.executeWorkflow(); workflowPage.actions.executeWorkflow();
// Manual tigger node should show success indicator // Manual tigger node should show success indicator
workflowPage.actions.openNode('When clicking "Test workflow"'); workflowPage.actions.openNode('When clicking Test workflow');
ndv.getters.nodeRunSuccessIndicator().should('exist'); ndv.getters.nodeRunSuccessIndicator().should('exist');
// Code node should show error // Code node should show error
ndv.getters.backToCanvas().click(); ndv.getters.backToCanvas().click();

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "d0eda550-2526-42a1-aa19-dee411c8acf9", "id": "d0eda550-2526-42a1-aa19-dee411c8acf9",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -91,7 +91,7 @@
], ],
"pinData": {}, "pinData": {},
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b", "id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -570,7 +570,7 @@
], ],
"pinData": {}, "pinData": {},
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "5ae8991f-08a2-4b27-b61c-85e3b8a83693", "id": "5ae8991f-08a2-4b27-b61c-85e3b8a83693",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -78,14 +78,14 @@
} }
} }
], ],
"When clicking \"Test workflow\"": [ "When clicking Test workflow": [
{ {
"json": {} "json": {}
} }
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "b3f0815d-b733-413f-ab3f-74e48277bd3a", "id": "b3f0815d-b733-413f-ab3f-74e48277bd3a",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -160,7 +160,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "46770685-44d1-4aad-9107-1d790cf26b50", "id": "46770685-44d1-4aad-9107-1d790cf26b50",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -74,7 +74,7 @@
} }
], ],
"pinData": { "pinData": {
"When clicking \"Test workflow\"": [ "When clicking Test workflow": [
{ {
"json": { "json": {
"id": "654cfa05fa51480dcb543b1a", "id": "654cfa05fa51480dcb543b1a",
@ -599,7 +599,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -42,7 +42,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce", "id": "551313bb-1e01-4133-9956-e6f09968f2ce",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -92,7 +92,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {
@ -191,7 +191,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce", "id": "551313bb-1e01-4133-9956-e6f09968f2ce",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -241,7 +241,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {
@ -374,7 +374,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce", "id": "551313bb-1e01-4133-9956-e6f09968f2ce",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -424,7 +424,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {
@ -524,7 +524,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce", "id": "551313bb-1e01-4133-9956-e6f09968f2ce",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -574,7 +574,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "f26332f3-c61a-4843-94bd-64a73ad161ff", "id": "f26332f3-c61a-4843-94bd-64a73ad161ff",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -105,7 +105,7 @@
], ],
"pinData": {}, "pinData": {},
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -19,7 +19,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "449ab540-d9d7-480d-b131-05e9989a69cd", "id": "449ab540-d9d7-480d-b131-05e9989a69cd",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -42,7 +42,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -40,7 +40,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e", "id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -199,7 +199,7 @@
] ]
] ]
}, },
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f", "id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -99,7 +99,7 @@
], ],
"pinData": {}, "pinData": {},
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -30,7 +30,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "4f4c6527-d565-448a-96bd-8f5414caf8cc", "id": "4f4c6527-d565-448a-96bd-8f5414caf8cc",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -136,7 +136,7 @@
] ]
] ]
}, },
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -3,7 +3,7 @@
"nodes": [ "nodes": [
{ {
"id": "2acca986-10a6-451e-b20a-86e95b50e627", "id": "2acca986-10a6-451e-b20a-86e95b50e627",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [460, 460] "position": [460, 460]

View file

@ -7,7 +7,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "09e4325e-ede1-40cf-a1ba-58612bbc7f1b", "id": "09e4325e-ede1-40cf-a1ba-58612bbc7f1b",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -77,7 +77,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -47,7 +47,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "58512a93-dabf-4584-817f-27c608c1bdd5", "id": "58512a93-dabf-4584-817f-27c608c1bdd5",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -69,7 +69,7 @@
] ]
] ]
}, },
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -47,7 +47,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9", "id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -552,7 +552,7 @@
] ]
] ]
}, },
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "0a60e507-7f34-41c0-a0f9-697d852033b6", "id": "0a60e507-7f34-41c0-a0f9-697d852033b6",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -93,7 +93,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -6,7 +6,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c", "id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -37,7 +37,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -6,7 +6,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "bcb6abdf-d34b-4ea7-a8ed-58155b708c43", "id": "bcb6abdf-d34b-4ea7-a8ed-58155b708c43",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -90,7 +90,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "25ff0c17-7064-4e14-aec6-45c71d63201b", "id": "25ff0c17-7064-4e14-aec6-45c71d63201b",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [

View file

@ -27,7 +27,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa", "id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -37,7 +37,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -6,7 +6,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "40720511-19b6-4421-bdb0-3fb6efef4bc5", "id": "40720511-19b6-4421-bdb0-3fb6efef4bc5",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -64,7 +64,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -247,7 +247,7 @@ export class Agent implements INodeType {
alias: ['LangChain'], alias: ['LangChain'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Agents'], AI: ['Agents', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -31,7 +31,7 @@ export class OpenAiAssistant implements INodeType {
alias: ['LangChain'], alias: ['LangChain'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Agents'], AI: ['Agents', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -257,7 +257,7 @@ export class ChainLlm implements INodeType {
alias: ['LangChain'], alias: ['LangChain'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Chains'], AI: ['Chains', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -30,7 +30,7 @@ export class ChainRetrievalQa implements INodeType {
alias: ['LangChain'], alias: ['LangChain'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Chains'], AI: ['Chains', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -16,7 +16,7 @@ export class ChainSummarization extends VersionedNodeType {
alias: ['LangChain'], alias: ['LangChain'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Chains'], AI: ['Chains', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -77,7 +77,7 @@ export class MemoryManager implements INodeType {
codex: { codex: {
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Miscellaneous'], AI: ['Miscellaneous', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -146,6 +146,79 @@ export class OutputParserStructured implements INodeType {
} }
}`, }`,
}, },
{
displayName: 'Schema Type',
name: 'schemaType',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Generate From JSON Example',
value: 'fromJson',
description: 'Generate a schema from an example JSON object',
},
{
name: 'Define Below',
value: 'manual',
description: 'Define the JSON schema manually',
},
],
default: 'fromJson',
description: 'How to specify the schema for the function',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.2 } }],
},
},
},
{
displayName: 'JSON Example',
name: 'jsonSchemaExample',
type: 'json',
default: `{
"state": "California",
"cities": ["Los Angeles", "San Francisco", "San Diego"]
}`,
noDataExpression: true,
typeOptions: {
rows: 10,
},
displayOptions: {
show: {
schemaType: ['fromJson'],
},
},
description: 'Example JSON object to use to generate the schema',
},
{
displayName: 'Input Schema',
name: 'inputSchema',
type: 'json',
default: `{
"type": "object",
"properties": {
"state": {
"type": "string"
},
"cities": {
"type": "array",
"items": {
"type": "string"
}
}
}
}`,
noDataExpression: true,
typeOptions: {
rows: 10,
},
displayOptions: {
show: {
schemaType: ['manual'],
},
},
description: 'Schema to use for the function',
},
{ {
displayName: 'JSON Schema', displayName: 'JSON Schema',
name: 'jsonSchema', name: 'jsonSchema',

View file

@ -34,9 +34,6 @@ export class ChatTrigger implements INodeType {
}, },
], ],
}, },
subcategories: {
'Core Nodes': ['Other Trigger Nodes'],
},
}, },
supportsCORS: true, supportsCORS: true,
maxNodes: 1, maxNodes: 1,

View file

@ -79,7 +79,7 @@ export const versionDescription: INodeTypeDescription = {
alias: ['LangChain', 'ChatGPT', 'DallE'], alias: ['LangChain', 'ChatGPT', 'DallE'],
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
AI: ['Agents', 'Miscellaneous'], AI: ['Agents', 'Miscellaneous', 'Root Nodes'],
}, },
resources: { resources: {
primaryDocumentation: [ primaryDocumentation: [

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from '../../composables/useI18n'; import { useI18n } from '../../composables/useI18n';
import type { NodeCreatorTag } from '../../types/node-creator-node';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import N8nTooltip from '../N8nTooltip'; import N8nTooltip from '../N8nTooltip';
import { ElTag } from 'element-plus'; import { ElTag } from 'element-plus';
@ -9,7 +10,7 @@ export interface Props {
isAi?: boolean; isAi?: boolean;
isTrigger?: boolean; isTrigger?: boolean;
description?: string; description?: string;
tag?: string; tag?: NodeCreatorTag;
title: string; title: string;
showActionArrow?: boolean; showActionArrow?: boolean;
} }
@ -37,8 +38,8 @@ const { t } = useI18n();
<div> <div>
<div :class="$style.details"> <div :class="$style.details">
<span :class="$style.name" data-test-id="node-creator-item-name" v-text="title" /> <span :class="$style.name" data-test-id="node-creator-item-name" v-text="title" />
<ElTag v-if="tag" :class="$style.tag" size="small" round type="success"> <ElTag v-if="tag" :class="$style.tag" size="small" round :type="tag.type ?? 'success'">
{{ tag }} {{ tag.text }}
</ElTag> </ElTag>
<FontAwesomeIcon <FontAwesomeIcon
v-if="isTrigger" v-if="isTrigger"
@ -87,8 +88,16 @@ const { t } = useI18n();
.creatorNode:hover .panelIcon { .creatorNode:hover .panelIcon {
color: var(--action-arrow-color-hover, var(--color-text-light)); color: var(--action-arrow-color-hover, var(--color-text-light));
} }
.tag { :root .tag {
margin-left: var(--spacing-2xs); margin-left: var(--spacing-2xs);
line-height: var(--font-size-3xs);
font-size: var(--font-size-3xs);
padding: 0.1875rem var(--spacing-3xs) var(--spacing-4xs) var(--spacing-3xs);
height: auto;
span {
font-size: var(--font-size-2xs) !important;
}
} }
.panelIcon { .panelIcon {
flex-grow: 1; flex-grow: 1;

View file

@ -8,3 +8,4 @@ export * from './menu';
export * from './select'; export * from './select';
export * from './user'; export * from './user';
export * from './keyboardshortcut'; export * from './keyboardshortcut';
export * from './node-creator-node';

View file

@ -0,0 +1,6 @@
import type { ElTag } from 'element-plus';
export type NodeCreatorTag = {
text: string;
type?: (typeof ElTag)['type'];
};

View file

@ -9,7 +9,7 @@ import type {
VIEWS, VIEWS,
ROLE, ROLE,
} from '@/constants'; } from '@/constants';
import type { IMenuItem } from 'n8n-design-system'; import type { IMenuItem, NodeCreatorTag } from 'n8n-design-system';
import { import {
type GenericValue, type GenericValue,
type IConnections, type IConnections,
@ -932,7 +932,9 @@ export type SimplifiedNodeType = Pick<
| 'codex' | 'codex'
| 'defaults' | 'defaults'
| 'outputs' | 'outputs'
>; > & {
tag?: string;
};
export interface SubcategoryItemProps { export interface SubcategoryItemProps {
description?: string; description?: string;
iconType?: string; iconType?: string;
@ -951,11 +953,21 @@ export interface ViewItemProps {
title: string; title: string;
description: string; description: string;
icon: string; icon: string;
tag?: string; tag?: NodeCreatorTag;
borderless?: boolean;
} }
export interface LabelItemProps { export interface LabelItemProps {
key: string; key: string;
} }
export interface LinkItemProps {
url: string;
key: string;
newTab?: boolean;
title: string;
description: string;
icon: string;
tag?: NodeCreatorTag;
}
export interface ActionTypeDescription extends SimplifiedNodeType { export interface ActionTypeDescription extends SimplifiedNodeType {
displayOptions?: IDisplayOptions; displayOptions?: IDisplayOptions;
values?: IDataObject; values?: IDataObject;
@ -1010,6 +1022,11 @@ export interface LabelCreateElement extends CreateElementBase {
properties: LabelItemProps; properties: LabelItemProps;
} }
export interface LinkCreateElement extends CreateElementBase {
type: 'link';
properties: LinkItemProps;
}
export interface ActionCreateElement extends CreateElementBase { export interface ActionCreateElement extends CreateElementBase {
type: 'action'; type: 'action';
subcategory: string; subcategory: string;
@ -1023,7 +1040,8 @@ export type INodeCreateElement =
| SectionCreateElement | SectionCreateElement
| ViewCreateElement | ViewCreateElement
| LabelCreateElement | LabelCreateElement
| ActionCreateElement; | ActionCreateElement
| LinkCreateElement;
export interface SubcategorizedNodeTypes { export interface SubcategorizedNodeTypes {
[subcategory: string]: INodeCreateElement[]; [subcategory: string]: INodeCreateElement[];

View file

@ -0,0 +1,32 @@
<template>
<n8n-node-creator-node
:class="$style.creatorLink"
:title="link.title"
:is-trigger="false"
:description="link.description"
:tag="link.tag"
:show-action-arrow="true"
>
<template #icon>
<n8n-node-icon type="icon" :name="link.icon" :circle="false" :show-tooltip="false" />
</template>
</n8n-node-creator-node>
</template>
<script setup lang="ts">
import type { LinkItemProps } from '@/Interface';
export interface Props {
link: LinkItemProps;
}
defineProps<Props>();
</script>
<style lang="scss" module>
.creatorLink {
--action-arrow-color: var(--color-text-light);
margin-left: var(--spacing-s);
margin-right: var(--spacing-xs);
}
</style>

View file

@ -8,6 +8,7 @@
:show-action-arrow="showActionArrow" :show-action-arrow="showActionArrow"
:is-trigger="isTrigger" :is-trigger="isTrigger"
:data-test-id="dataTestId" :data-test-id="dataTestId"
:tag="nodeType.tag"
@dragstart="onDragStart" @dragstart="onDragStart"
@dragend="onDragEnd" @dragend="onDragEnd"
> >

View file

@ -139,6 +139,13 @@ function onSelected(item: INodeCreateElement) {
searchItems: mergedNodes, searchItems: mergedNodes,
}); });
} }
if (item.type === 'link') {
window.open(item.properties.url, '_blank');
telemetry.trackNodesPanel('nodeCreateList.onLinkSelected', {
link: item.properties.url,
});
}
} }
function subcategoriesMapper(item: INodeCreateElement) { function subcategoriesMapper(item: INodeCreateElement) {
@ -195,13 +202,13 @@ function onKeySelect(activeItemId: string) {
registerKeyHook('MainViewArrowRight', { registerKeyHook('MainViewArrowRight', {
keyboardKeys: ['ArrowRight', 'Enter'], keyboardKeys: ['ArrowRight', 'Enter'],
condition: (type) => ['subcategory', 'node', 'view'].includes(type), condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
handler: onKeySelect, handler: onKeySelect,
}); });
registerKeyHook('MainViewArrowLeft', { registerKeyHook('MainViewArrowLeft', {
keyboardKeys: ['ArrowLeft'], keyboardKeys: ['ArrowLeft'],
condition: (type) => ['subcategory', 'node', 'view'].includes(type), condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
handler: arrowLeft, handler: arrowLeft,
}); });
</script> </script>

View file

@ -8,6 +8,7 @@ import SubcategoryItem from '../ItemTypes/SubcategoryItem.vue';
import LabelItem from '../ItemTypes/LabelItem.vue'; import LabelItem from '../ItemTypes/LabelItem.vue';
import ActionItem from '../ItemTypes/ActionItem.vue'; import ActionItem from '../ItemTypes/ActionItem.vue';
import ViewItem from '../ItemTypes/ViewItem.vue'; import ViewItem from '../ItemTypes/ViewItem.vue';
import LinkItem from '../ItemTypes/LinkItem.vue';
import CategorizedItemsRenderer from './CategorizedItemsRenderer.vue'; import CategorizedItemsRenderer from './CategorizedItemsRenderer.vue';
export interface Props { export interface Props {
@ -147,6 +148,8 @@ watch(
[$style.active]: activeItemId === item.uuid, [$style.active]: activeItemId === item.uuid,
[$style.iteratorItem]: true, [$style.iteratorItem]: true,
[$style[item.type]]: true, [$style[item.type]]: true,
// Borderless is only applied to views
[$style.borderless]: item.type === 'view' && item.properties.borderless === true,
}" }"
data-test-id="item-iterator-item" data-test-id="item-iterator-item"
:data-keyboard-nav-type="item.type !== 'label' ? item.type : undefined" :data-keyboard-nav-type="item.type !== 'label' ? item.type : undefined"
@ -175,6 +178,12 @@ watch(
:view="item.properties" :view="item.properties"
:class="$style.viewItem" :class="$style.viewItem"
/> />
<LinkItem
v-else-if="item.type === 'link'"
:link="item.properties"
:class="$style.linkItem"
/>
</div> </div>
</div> </div>
<n8n-loading v-else :loading="true" :rows="1" variant="p" :class="$style.itemSkeleton" /> <n8n-loading v-else :loading="true" :rows="1" variant="p" :class="$style.itemSkeleton" />
@ -223,12 +232,14 @@ watch(
display: none; display: none;
} }
} }
.view { .view {
position: relative; position: relative;
&:last-child { &:last-child {
margin-top: var(--spacing-s); margin-top: var(--spacing-s);
padding-top: var(--spacing-xs); padding-top: var(--spacing-xs);
&:after { &:after {
content: ''; content: '';
position: absolute; position: absolute;
@ -241,4 +252,34 @@ watch(
} }
} }
} }
.link {
position: relative;
&:last-child {
margin-bottom: var(--spacing-s);
padding-bottom: var(--spacing-xs);
&:after {
content: '';
position: absolute;
left: var(--spacing-s);
right: var(--spacing-s);
top: 0;
margin: auto;
bottom: 0;
border-bottom: 1px solid var(--color-foreground-base);
}
}
}
.borderless {
&:last-child {
margin-top: 0;
padding-top: 0;
&:after {
content: none;
}
}
}
</style> </style>

View file

@ -76,7 +76,7 @@ describe('NodesListPanel', () => {
await fireEvent.click(container.querySelector('.backButton')!); await fireEvent.click(container.querySelector('.backButton')!);
await nextTick(); await nextTick();
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(7); expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(8);
}); });
it('should render regular nodes', async () => { it('should render regular nodes', async () => {
@ -136,7 +136,7 @@ describe('NodesListPanel', () => {
await nextTick(); await nextTick();
expect(screen.getByText('What happens next?')).toBeInTheDocument(); expect(screen.getByText('What happens next?')).toBeInTheDocument();
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(6); expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(5);
screen.getByText('Action in an app').click(); screen.getByText('Action in an app').click();
await nextTick(); await nextTick();

View file

@ -1,19 +1,29 @@
import type { INodeCreateElement, NodeFilterType, SimplifiedNodeType } from '@/Interface'; import type {
INodeCreateElement,
NodeCreateElement,
NodeFilterType,
SimplifiedNodeType,
} from '@/Interface';
import { import {
AI_CATEGORY_ROOT_NODES,
AI_CODE_NODE_TYPE, AI_CODE_NODE_TYPE,
AI_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW,
AI_SUBCATEGORY,
DEFAULT_SUBCATEGORY, DEFAULT_SUBCATEGORY,
TRIGGER_NODE_CREATOR_VIEW, TRIGGER_NODE_CREATOR_VIEW,
} from '@/constants'; } from '@/constants';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { computed, nextTick, ref } from 'vue'; import { computed, nextTick, ref } from 'vue';
import difference from 'lodash-es/difference';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import { import {
flattenCreateElements, flattenCreateElements,
groupItemsInSections, groupItemsInSections,
isAINode,
searchNodes, searchNodes,
sortNodeCreateElements, sortNodeCreateElements,
subcategorizeItems, subcategorizeItems,
@ -27,6 +37,7 @@ import { useKeyboardNavigation } from './useKeyboardNavigation';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { INodeInputFilter, NodeConnectionType } from 'n8n-workflow'; import type { INodeInputFilter, NodeConnectionType } from 'n8n-workflow';
import { useCanvasStore } from '@/stores/canvas.store';
interface ViewStack { interface ViewStack {
uuid?: string; uuid?: string;
@ -60,11 +71,12 @@ interface ViewStack {
export const useViewStacks = defineStore('nodeCreatorViewStacks', () => { export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
const nodeCreatorStore = useNodeCreatorStore(); const nodeCreatorStore = useNodeCreatorStore();
const { getActiveItemIndex } = useKeyboardNavigation(); const { getActiveItemIndex } = useKeyboardNavigation();
const i18n = useI18n();
const viewStacks = ref<ViewStack[]>([]); const viewStacks = ref<ViewStack[]>([]);
const activeStackItems = computed<INodeCreateElement[]>(() => { const activeStackItems = computed<INodeCreateElement[]>(() => {
const stack = viewStacks.value[viewStacks.value.length - 1]; const stack = getLastActiveStack();
if (!stack?.baselineItems) { if (!stack?.baselineItems) {
return stack.items ? extendItemsWithUUID(stack.items) : []; return stack.items ? extendItemsWithUUID(stack.items) : [];
@ -76,13 +88,24 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
? searchBaseItems.value ? searchBaseItems.value
: flattenCreateElements(stack.baselineItems ?? []); : flattenCreateElements(stack.baselineItems ?? []);
return extendItemsWithUUID(searchNodes(stack.search || '', searchBase)); const canvasHasAINodes = useCanvasStore().aiNodes.length > 0;
const filteredNodes =
isAiRootView(stack) || canvasHasAINodes ? searchBase : filterOutAiNodes(searchBase);
const searchResults = extendItemsWithUUID(searchNodes(stack.search || '', filteredNodes));
const groupedNodes = groupIfAiNodes(searchResults, false) ?? searchResults;
// Set the active index to the second item if there's a section
// as the first item is collapsable
stack.activeIndex = groupedNodes.some((node) => node.type === 'section') ? 1 : 0;
return groupedNodes;
} }
return extendItemsWithUUID(stack.baselineItems); return extendItemsWithUUID(groupIfAiNodes(stack.baselineItems, true));
}); });
const activeViewStack = computed<ViewStack>(() => { const activeViewStack = computed<ViewStack>(() => {
const stack = viewStacks.value[viewStacks.value.length - 1]; const stack = getLastActiveStack();
if (!stack) return {}; if (!stack) return {};
const flatBaselineItems = flattenCreateElements(stack.baselineItems ?? []); const flatBaselineItems = flattenCreateElements(stack.baselineItems ?? []);
@ -99,34 +122,148 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
); );
const searchBaseItems = computed<INodeCreateElement[]>(() => { const searchBaseItems = computed<INodeCreateElement[]>(() => {
const stack = viewStacks.value[viewStacks.value.length - 1]; const stack = getLastActiveStack();
if (!stack?.searchItems) return []; if (!stack?.searchItems) return [];
return stack.searchItems.map((item) => transformNodeType(item, stack.subcategory)); return stack.searchItems.map((item) => transformNodeType(item, stack.subcategory));
}); });
function getLastActiveStack() {
return viewStacks.value[viewStacks.value.length - 1];
}
// Generate a delta between the global search results(all nodes) and the stack search results // Generate a delta between the global search results(all nodes) and the stack search results
const globalSearchItemsDiff = computed<INodeCreateElement[]>(() => { const globalSearchItemsDiff = computed<INodeCreateElement[]>(() => {
const stack = viewStacks.value[viewStacks.value.length - 1]; const stack = getLastActiveStack();
if (!stack?.search) return []; if (!stack?.search) return [];
const allNodes = nodeCreatorStore.mergedNodes.map((item) => transformNodeType(item)); const allNodes = nodeCreatorStore.mergedNodes.map((item) => transformNodeType(item));
const globalSearchResult = extendItemsWithUUID(searchNodes(stack.search || '', allNodes)); // Apply filtering for AI nodes if the current view is not the AI root view
const filteredNodes = isAiRootView(stack) ? allNodes : filterOutAiNodes(allNodes);
return globalSearchResult.filter((item) => { let globalSearchResult: INodeCreateElement[] = extendItemsWithUUID(
return !activeStackItems.value.find((activeItem) => activeItem.key === item.key); searchNodes(stack.search || '', filteredNodes),
);
if (isAiRootView(stack)) {
globalSearchResult = groupIfAiNodes(globalSearchResult);
}
const filteredItems = globalSearchResult.filter((item) => {
return !activeStackItems.value.find((activeItem) => {
if (activeItem.type === 'section') {
const matchingSectionItem = activeItem.children.some(
(sectionItem) => sectionItem.key === item.key,
);
return matchingSectionItem;
}
return activeItem.key === item.key;
}); });
}); });
// Filter out empty sections if all of their children are filtered out
const filteredSections = filteredItems.filter((item) => {
if (item.type === 'section') {
const hasVisibleChildren = item.children.some((child) =>
activeStackItems.value.some((filteredItem) => filteredItem.key === child.key),
);
return hasVisibleChildren;
}
return true;
});
return filteredSections;
});
const itemsBySubcategory = computed(() => subcategorizeItems(nodeCreatorStore.mergedNodes)); const itemsBySubcategory = computed(() => subcategorizeItems(nodeCreatorStore.mergedNodes));
function isAiRootView(stack: ViewStack) {
return stack.rootView === AI_NODE_CREATOR_VIEW;
}
function groupIfAiNodes(items: INodeCreateElement[], sortAlphabetically = true) {
const aiNodes = items.filter((node): node is NodeCreateElement => isAINode(node));
if (aiNodes.length > 0) {
const sectionsMap = new Map<string, NodeViewItemSection>();
aiNodes.forEach((node) => {
const section = node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.[0];
if (section) {
const currentItems = sectionsMap.get(section)?.items ?? [];
const isSubnodesSection =
!node.properties.codex?.subcategories?.[AI_SUBCATEGORY].includes(
AI_CATEGORY_ROOT_NODES,
);
sectionsMap.set(section, {
key: section,
title: isSubnodesSection
? `${section} (${i18n.baseText('nodeCreator.subnodes')})`
: section,
items: [...currentItems, node.key],
});
}
});
const nonAiNodes = difference(items, aiNodes);
const nonAiTriggerNodes = nonAiNodes.filter(
(item) => item.type === 'node' && useNodeTypesStore().isTriggerNode(item.properties.name),
);
const nonAiRegularNodes = difference(nonAiNodes, nonAiTriggerNodes);
if (nonAiNodes.length > 0) {
let sectionKey = '';
if (nonAiRegularNodes.length && nonAiTriggerNodes.length) {
sectionKey = i18n.baseText('nodeCreator.actionsCategory.regularAndTriggers');
} else {
sectionKey = nonAiRegularNodes.length
? i18n.baseText('nodeCreator.actionsCategory.regularNodes')
: i18n.baseText('nodeCreator.actionsCategory.triggerNodes');
}
const nodesKeys = nonAiNodes.map((node) => node.key);
sectionsMap.set(sectionKey, {
key: sectionKey,
title: sectionKey,
items: [...nodesKeys],
});
}
// Convert sectionsMap to array of sections
const sections = Array.from(sectionsMap.values());
return groupItemsInSections(items, sections, sortAlphabetically);
}
return items;
}
function filterOutAiNodes(items: INodeCreateElement[]) {
const filteredSearchBase = items.filter((item) => {
if (item.type === 'node') {
const isAICategory = item.properties.codex?.categories?.includes(AI_SUBCATEGORY) === true;
if (!isAICategory) return true;
const isRootNodeSubcategory =
item.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_ROOT_NODES);
return isRootNodeSubcategory;
}
return true;
});
return filteredSearchBase;
}
async function gotoCompatibleConnectionView( async function gotoCompatibleConnectionView(
connectionType: NodeConnectionType, connectionType: NodeConnectionType,
isOutput?: boolean, isOutput?: boolean,
filter?: INodeInputFilter, filter?: INodeInputFilter,
) { ) {
const i18n = useI18n();
let nodesByConnectionType: { [key: string]: string[] }; let nodesByConnectionType: { [key: string]: string[] };
let relatedAIView: { properties: NodeViewItem['properties'] } | undefined; let relatedAIView: { properties: NodeViewItem['properties'] } | undefined;
@ -185,7 +322,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
} }
function setStackBaselineItems() { function setStackBaselineItems() {
const stack = viewStacks.value[viewStacks.value.length - 1]; const stack = getLastActiveStack();
if (!stack || !activeViewStack.value.uuid) return; if (!stack || !activeViewStack.value.uuid) return;
let stackItems = stack?.items ?? []; let stackItems = stack?.items ?? [];
@ -258,7 +395,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
} }
function updateCurrentViewStack(stack: Partial<ViewStack>) { function updateCurrentViewStack(stack: Partial<ViewStack>) {
const currentStack = viewStacks.value[viewStacks.value.length - 1]; const currentStack = getLastActiveStack();
const matchedIndex = viewStacks.value.findIndex((s) => s.uuid === currentStack.uuid); const matchedIndex = viewStacks.value.findIndex((s) => s.uuid === currentStack.uuid);
if (!currentStack) return; if (!currentStack) return;

View file

@ -6,12 +6,18 @@ import type {
INodeCreateElement, INodeCreateElement,
SectionCreateElement, SectionCreateElement,
} from '@/Interface'; } from '@/Interface';
import { AI_SUBCATEGORY, CORE_NODES_CATEGORY, DEFAULT_SUBCATEGORY } from '@/constants'; import {
AI_CATEGORY_AGENTS,
AI_SUBCATEGORY,
CORE_NODES_CATEGORY,
DEFAULT_SUBCATEGORY,
} from '@/constants';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { sublimeSearch } from '@/utils/sortUtils'; import { sublimeSearch } from '@/utils/sortUtils';
import { i18n } from '@/plugins/i18n';
import type { NodeViewItemSection } from './viewsData'; import type { NodeViewItemSection } from './viewsData';
import { i18n } from '@/plugins/i18n';
import { sortBy } from 'lodash-es';
export function transformNodeType( export function transformNodeType(
node: SimplifiedNodeType, node: SimplifiedNodeType,
@ -70,6 +76,7 @@ export function sortNodeCreateElements(nodes: INodeCreateElement[]) {
export function searchNodes(searchFilter: string, items: INodeCreateElement[]) { export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
// In order to support the old search we need to remove the 'trigger' part // In order to support the old search we need to remove the 'trigger' part
const trimmedFilter = searchFilter.toLowerCase().replace('trigger', '').trimEnd(); const trimmedFilter = searchFilter.toLowerCase().replace('trigger', '').trimEnd();
const result = ( const result = (
sublimeSearch<INodeCreateElement>(trimmedFilter, items, [ sublimeSearch<INodeCreateElement>(trimmedFilter, items, [
{ key: 'properties.displayName', weight: 1.3 }, { key: 'properties.displayName', weight: 1.3 },
@ -83,38 +90,72 @@ export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateElement[] { export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateElement[] {
return items.map((item) => (item.type === 'section' ? item.children : item)).flat(); return items.map((item) => (item.type === 'section' ? item.children : item)).flat();
} }
export function isAINode(node: INodeCreateElement) {
const isNode = node.type === 'node';
if (!isNode) return false;
if (node.properties.codex?.categories?.includes(AI_SUBCATEGORY)) {
const isAgentSubcategory =
node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_AGENTS);
return !isAgentSubcategory;
}
return false;
}
export function groupItemsInSections( export function groupItemsInSections(
items: INodeCreateElement[], items: INodeCreateElement[],
sections: string[] | NodeViewItemSection[], sections: string[] | NodeViewItemSection[],
sortAlphabetically = true,
): INodeCreateElement[] { ): INodeCreateElement[] {
const filteredSections = sections.filter( const filteredSections = sections.filter(
(section): section is NodeViewItemSection => typeof section === 'object', (section): section is NodeViewItemSection => typeof section === 'object',
); );
const itemsBySection = items.reduce((acc: Record<string, INodeCreateElement[]>, item) => { const itemsBySection = (items2: INodeCreateElement[]) =>
items2.reduce((acc: Record<string, INodeCreateElement[]>, item) => {
const section = filteredSections.find((s) => s.items.includes(item.key)); const section = filteredSections.find((s) => s.items.includes(item.key));
const key = section?.key ?? 'other'; const key = section?.key ?? 'other';
if (key) {
acc[key] = [...(acc[key] ?? []), item]; acc[key] = [...(acc[key] ?? []), item];
}
return acc; return acc;
}, {}); }, {});
const result: SectionCreateElement[] = filteredSections const mapNewSections = (
.map( newSections: NodeViewItemSection[],
children: Record<string, INodeCreateElement[]>,
) =>
newSections.map(
(section): SectionCreateElement => ({ (section): SectionCreateElement => ({
type: 'section', type: 'section',
key: section.key, key: section.key,
title: section.title, title: section.title,
children: sortNodeCreateElements(itemsBySection[section.key] ?? []), children: sortAlphabetically
? sortNodeCreateElements(children[section.key] ?? [])
: children[section.key] ?? [],
}), }),
) );
const nonAINodes = items.filter((item) => !isAINode(item));
const AINodes = items.filter((item) => isAINode(item));
const nonAINodesBySection = itemsBySection(nonAINodes);
const nonAINodesSections = mapNewSections(filteredSections, nonAINodesBySection);
const AINodesBySection = itemsBySection(AINodes);
const AINodesSections = mapNewSections(sortBy(filteredSections, ['title']), AINodesBySection);
const result = [...nonAINodesSections, ...AINodesSections]
.concat({ .concat({
type: 'section', type: 'section',
key: 'other', key: 'other',
title: i18n.baseText('nodeCreator.sectionNames.other'), title: i18n.baseText('nodeCreator.sectionNames.other'),
children: sortNodeCreateElements(itemsBySection.other ?? []), children: sortNodeCreateElements(nonAINodesBySection.other ?? []),
}) })
.filter((section) => section.children.length > 0); .filter((section) => section.type !== 'section' || section.children.length > 0);
if (result.length <= 1) { if (result.length <= 1) {
return items; return items;

View file

@ -5,10 +5,10 @@ import {
EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
FORM_TRIGGER_NODE_TYPE, FORM_TRIGGER_NODE_TYPE,
MANUAL_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE,
MANUAL_CHAT_TRIGGER_NODE_TYPE,
SCHEDULE_TRIGGER_NODE_TYPE, SCHEDULE_TRIGGER_NODE_TYPE,
REGULAR_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW,
TRANSFORM_DATA_SUBCATEGORY, TRANSFORM_DATA_SUBCATEGORY,
FILES_SUBCATEGORY,
FLOWS_CONTROL_SUBCATEGORY, FLOWS_CONTROL_SUBCATEGORY,
TRIGGER_NODE_CREATOR_VIEW, TRIGGER_NODE_CREATOR_VIEW,
EMAIL_IMAP_NODE_TYPE, EMAIL_IMAP_NODE_TYPE,
@ -52,6 +52,8 @@ import {
EMAIL_SEND_NODE_TYPE, EMAIL_SEND_NODE_TYPE,
EDIT_IMAGE_NODE_TYPE, EDIT_IMAGE_NODE_TYPE,
COMPRESSION_NODE_TYPE, COMPRESSION_NODE_TYPE,
AI_CODE_TOOL_LANGCHAIN_NODE_TYPE,
AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
} from '@/constants'; } from '@/constants';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@ -76,13 +78,17 @@ export interface NodeViewItem {
iconProps?: { iconProps?: {
color?: string; color?: string;
}; };
url?: string;
connectionType?: NodeConnectionType; connectionType?: NodeConnectionType;
panelClass?: string; panelClass?: string;
group?: string[]; group?: string[];
sections?: NodeViewItemSection[]; sections?: NodeViewItemSection[];
description?: string; description?: string;
displayName?: string; displayName?: string;
tag?: string; tag?: {
type: string;
text: string;
};
forceIncludeNodes?: string[]; forceIncludeNodes?: string[];
iconData?: { iconData?: {
type: string; type: string;
@ -141,12 +147,24 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
value: AI_NODE_CREATOR_VIEW, value: AI_NODE_CREATOR_VIEW,
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'), title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
subtitle: i18n.baseText('nodeCreator.aiPanel.selectAiNode'), subtitle: i18n.baseText('nodeCreator.aiPanel.selectAiNode'),
info: i18n.baseText('nodeCreator.aiPanel.infoBox', {
interpolate: { link: templatesStore.getWebsiteCategoryURL('ai') },
}),
items: [ items: [
...chainNodes, {
key: 'ai_templates_root',
type: 'link',
properties: {
title: i18n.baseText('nodeCreator.aiPanel.linkItem.title'),
icon: 'box-open',
description: i18n.baseText('nodeCreator.aiPanel.linkItem.description'),
name: 'ai_templates_root',
url: templatesStore.getWebsiteCategoryURL(undefined, 'AdvancedAI'),
tag: {
type: 'info',
text: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerTag'),
},
},
},
...agentNodes, ...agentNodes,
...chainNodes,
{ {
key: AI_OTHERS_NODE_CREATOR_VIEW, key: AI_OTHERS_NODE_CREATOR_VIEW,
type: 'view', type: 'view',
@ -159,6 +177,7 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
], ],
}; };
} }
export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView { export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
const i18n = useI18n(); const i18n = useI18n();
@ -232,12 +251,20 @@ export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
}, },
}, },
{ {
key: AI_CATEGORY_TOOLS,
type: 'subcategory', type: 'subcategory',
key: AI_CATEGORY_TOOLS,
category: CORE_NODES_CATEGORY,
properties: { properties: {
title: AI_CATEGORY_TOOLS, title: AI_CATEGORY_TOOLS,
icon: 'tools', icon: 'tools',
...getAISubcategoryProperties(NodeConnectionType.AiTool), ...getAISubcategoryProperties(NodeConnectionType.AiTool),
sections: [
{
key: 'popular',
title: i18n.baseText('nodeCreator.sectionNames.popular'),
items: [AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, AI_CODE_TOOL_LANGCHAIN_NODE_TYPE],
},
],
}, },
}, },
{ {
@ -278,6 +305,18 @@ export function TriggerView() {
title: i18n.baseText('nodeCreator.triggerHelperPanel.selectATrigger'), title: i18n.baseText('nodeCreator.triggerHelperPanel.selectATrigger'),
subtitle: i18n.baseText('nodeCreator.triggerHelperPanel.selectATriggerDescription'), subtitle: i18n.baseText('nodeCreator.triggerHelperPanel.selectATriggerDescription'),
items: [ items: [
{
key: MANUAL_TRIGGER_NODE_TYPE,
type: 'node',
category: [CORE_NODES_CATEGORY],
properties: {
group: [],
name: MANUAL_TRIGGER_NODE_TYPE,
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
icon: 'fa:mouse-pointer',
},
},
{ {
key: DEFAULT_SUBCATEGORY, key: DEFAULT_SUBCATEGORY,
type: 'subcategory', type: 'subcategory',
@ -331,18 +370,6 @@ export function TriggerView() {
}, },
}, },
}, },
{
key: MANUAL_TRIGGER_NODE_TYPE,
type: 'node',
category: [CORE_NODES_CATEGORY],
properties: {
group: [],
name: MANUAL_TRIGGER_NODE_TYPE,
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
icon: 'fa:mouse-pointer',
},
},
{ {
key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
type: 'node', type: 'node',
@ -355,6 +382,18 @@ export function TriggerView() {
icon: 'fa:sign-out-alt', icon: 'fa:sign-out-alt',
}, },
}, },
{
key: MANUAL_CHAT_TRIGGER_NODE_TYPE,
type: 'node',
category: [CORE_NODES_CATEGORY],
properties: {
group: [],
name: MANUAL_CHAT_TRIGGER_NODE_TYPE,
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDisplayName'),
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDescription'),
icon: 'fa:comments',
},
},
{ {
type: 'subcategory', type: 'subcategory',
key: OTHER_TRIGGER_NODES_SUBCATEGORY, key: OTHER_TRIGGER_NODES_SUBCATEGORY,
@ -447,22 +486,6 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
], ],
}, },
}, },
{
type: 'subcategory',
key: FILES_SUBCATEGORY,
category: CORE_NODES_CATEGORY,
properties: {
title: FILES_SUBCATEGORY,
icon: 'file-alt',
sections: [
{
key: 'popular',
title: i18n.baseText('nodeCreator.sectionNames.popular'),
items: [CONVERT_TO_FILE_NODE_TYPE, EXTRACT_FROM_FILE_NODE_TYPE],
},
],
},
},
{ {
type: 'subcategory', type: 'subcategory',
key: HELPERS_SUBCATEGORY, key: HELPERS_SUBCATEGORY,
@ -491,9 +514,13 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
title: i18n.baseText('nodeCreator.aiPanel.langchainAiNodes'), title: i18n.baseText('nodeCreator.aiPanel.langchainAiNodes'),
icon: 'robot', icon: 'robot',
description: i18n.baseText('nodeCreator.aiPanel.nodesForAi'), description: i18n.baseText('nodeCreator.aiPanel.nodesForAi'),
tag: i18n.baseText('nodeCreator.aiPanel.newTag'), tag: {
type: 'success',
text: i18n.baseText('nodeCreator.aiPanel.newTag'),
}, },
}); borderless: true,
},
} as NodeViewItem);
view.items.push({ view.items.push({
key: TRIGGER_NODE_CREATOR_VIEW, key: TRIGGER_NODE_CREATOR_VIEW,

View file

@ -264,8 +264,10 @@ export const AI_CATEGORY_RETRIEVERS = 'Retrievers';
export const AI_CATEGORY_EMBEDDING = 'Embeddings'; export const AI_CATEGORY_EMBEDDING = 'Embeddings';
export const AI_CATEGORY_DOCUMENT_LOADERS = 'Document Loaders'; export const AI_CATEGORY_DOCUMENT_LOADERS = 'Document Loaders';
export const AI_CATEGORY_TEXT_SPLITTERS = 'Text Splitters'; export const AI_CATEGORY_TEXT_SPLITTERS = 'Text Splitters';
export const AI_CATEGORY_ROOT_NODES = 'Root Nodes';
export const AI_UNCATEGORIZED_CATEGORY = 'Miscellaneous'; export const AI_UNCATEGORIZED_CATEGORY = 'Miscellaneous';
export const AI_CODE_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolCode';
export const AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolWorkflow';
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3'; export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
// Node Connection Types // Node Connection Types
@ -674,10 +676,17 @@ export const AI_ASSISTANT_EXPERIMENT = {
variant: 'variant', variant: 'variant',
}; };
export const CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT = {
name: '20_canvas_auto_add_manual_trigger',
control: 'control',
variant: 'variant',
};
export const EXPERIMENTS_TO_TRACK = [ export const EXPERIMENTS_TO_TRACK = [
ASK_AI_EXPERIMENT.name, ASK_AI_EXPERIMENT.name,
TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT,
AI_ASSISTANT_EXPERIMENT.name, AI_ASSISTANT_EXPERIMENT.name,
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
]; ];
export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998; export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998;

View file

@ -928,9 +928,9 @@
"ndv.output.of": " of ", "ndv.output.of": " of ",
"ndv.output.pageSize": "Page Size", "ndv.output.pageSize": "Page Size",
"ndv.output.run": "Run", "ndv.output.run": "Run",
"ndv.output.runNodeHint": "Test this node to output data", "ndv.output.runNodeHint": "Execute this node to view data",
"ndv.output.runNodeHintSubNode": "Output will appear here once the parent node is run", "ndv.output.runNodeHintSubNode": "Output will appear here once the parent node is run",
"ndv.output.insertTestData": "insert test data", "ndv.output.insertTestData": "set mock data",
"ndv.output.staleDataWarning.regular": "Node parameters have changed.<br>Test node again to refresh output.", "ndv.output.staleDataWarning.regular": "Node parameters have changed.<br>Test node again to refresh output.",
"ndv.output.staleDataWarning.pinData": "Node parameter changes will not affect pinned output data.", "ndv.output.staleDataWarning.pinData": "Node parameter changes will not affect pinned output data.",
"ndv.output.tooMuchData.message": "The node contains {size} MB of data. Displaying it may cause problems. <br /> If you do decide to display it, avoid the JSON view.", "ndv.output.tooMuchData.message": "The node contains {size} MB of data. Displaying it may cause problems. <br /> If you do decide to display it, avoid the JSON view.",
@ -986,6 +986,9 @@
"nodeCreator.actionsCategory.onNewEvent": "On new {event} event", "nodeCreator.actionsCategory.onNewEvent": "On new {event} event",
"nodeCreator.actionsCategory.onEvent": "On {event}", "nodeCreator.actionsCategory.onEvent": "On {event}",
"nodeCreator.actionsCategory.triggers": "Triggers", "nodeCreator.actionsCategory.triggers": "Triggers",
"nodeCreator.actionsCategory.triggerNodes": "Trigger Nodes",
"nodeCreator.actionsCategory.regularNodes": "Regular Nodes",
"nodeCreator.actionsCategory.regularAndTriggers": "Regular & Trigger Nodes",
"nodeCreator.actionsCategory.searchActions": "Search {node} Actions...", "nodeCreator.actionsCategory.searchActions": "Search {node} Actions...",
"nodeCreator.actionsCategory.noMatchingActions": "No matching Actions. <i>Reset search</i>", "nodeCreator.actionsCategory.noMatchingActions": "No matching Actions. <i>Reset search</i>",
"nodeCreator.actionsCategory.noMatchingTriggers": "No matching Triggers. <i>Reset search</i>", "nodeCreator.actionsCategory.noMatchingTriggers": "No matching Triggers. <i>Reset search</i>",
@ -996,6 +999,7 @@
"nodeCreator.actionsTooltip.actionsPerformStep": "Actions perform a step once your workflow has already started. <a target=\"_blank\" href=\"https://docs.n8n.io/integrations/builtin/\"> Learn more</a>", "nodeCreator.actionsTooltip.actionsPerformStep": "Actions perform a step once your workflow has already started. <a target=\"_blank\" href=\"https://docs.n8n.io/integrations/builtin/\"> Learn more</a>",
"nodeCreator.actionsCallout.noTriggerItems": "No <strong>{nodeName}</strong> Triggers available. Users often combine the following Triggers with <strong>{nodeName}</strong> Actions.", "nodeCreator.actionsCallout.noTriggerItems": "No <strong>{nodeName}</strong> Triggers available. Users often combine the following Triggers with <strong>{nodeName}</strong> Actions.",
"nodeCreator.categoryNames.otherCategories": "Results in other categories", "nodeCreator.categoryNames.otherCategories": "Results in other categories",
"nodeCreator.subnodes": "sub-nodes",
"nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe": "Dont worry, you can probably do it with the", "nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe": "Dont worry, you can probably do it with the",
"nodeCreator.noResults.httpRequest": "HTTP Request", "nodeCreator.noResults.httpRequest": "HTTP Request",
"nodeCreator.noResults.node": "node", "nodeCreator.noResults.node": "node",
@ -1007,10 +1011,10 @@
"nodeCreator.searchBar.searchNodes": "Search nodes...", "nodeCreator.searchBar.searchNodes": "Search nodes...",
"nodeCreator.subcategoryDescriptions.appTriggerNodes": "Runs the flow when something happens in an app like Telegram, Notion or Airtable", "nodeCreator.subcategoryDescriptions.appTriggerNodes": "Runs the flow when something happens in an app like Telegram, Notion or Airtable",
"nodeCreator.subcategoryDescriptions.appRegularNodes": "Do something in an app or service like Google Sheets, Telegram or Notion", "nodeCreator.subcategoryDescriptions.appRegularNodes": "Do something in an app or service like Google Sheets, Telegram or Notion",
"nodeCreator.subcategoryDescriptions.dataTransformation": "Manipulate data, run JavaScript code, etc.", "nodeCreator.subcategoryDescriptions.dataTransformation": "Manipulate, filter or convert data",
"nodeCreator.subcategoryDescriptions.files": "CSV, XLS, XML, text, images, etc.", "nodeCreator.subcategoryDescriptions.files": "CSV, XLS, XML, text, images, etc.",
"nodeCreator.subcategoryDescriptions.flow": "IF, Switch, Wait, Compare and Merge data, etc.", "nodeCreator.subcategoryDescriptions.flow": "Branch, merge or loop the flow, etc.",
"nodeCreator.subcategoryDescriptions.helpers": "Code, HTTP Requests (API Calls), Webhook, and other helpers", "nodeCreator.subcategoryDescriptions.helpers": "Run code, make HTTP requests, set webhooks, etc.",
"nodeCreator.subcategoryDescriptions.otherTriggerNodes": "Runs the flow on workflow errors, file changes, etc.", "nodeCreator.subcategoryDescriptions.otherTriggerNodes": "Runs the flow on workflow errors, file changes, etc.",
"nodeCreator.subcategoryDescriptions.agents": "Autonomous entities that interact and make decisions.", "nodeCreator.subcategoryDescriptions.agents": "Autonomous entities that interact and make decisions.",
"nodeCreator.subcategoryDescriptions.chains": "Structured assemblies for specific tasks.", "nodeCreator.subcategoryDescriptions.chains": "Structured assemblies for specific tasks.",
@ -1054,11 +1058,14 @@
"nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName": "On a schedule", "nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName": "On a schedule",
"nodeCreator.triggerHelperPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval", "nodeCreator.triggerHelperPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
"nodeCreator.triggerHelperPanel.webhookTriggerDisplayName": "On webhook call", "nodeCreator.triggerHelperPanel.webhookTriggerDisplayName": "On webhook call",
"nodeCreator.triggerHelperPanel.webhookTriggerDescription": "Runs the flow when another app sends a webhook", "nodeCreator.triggerHelperPanel.webhookTriggerDescription": "Runs the flow on receiving an HTTP request",
"nodeCreator.triggerHelperPanel.formTriggerDisplayName": "On form submission", "nodeCreator.triggerHelperPanel.formTriggerDisplayName": "On form submission",
"nodeCreator.triggerHelperPanel.formTriggerDescription": "Runs the flow when an n8n generated webform is submitted", "nodeCreator.triggerHelperPanel.formTriggerDescription": "Runs the flow when an n8n generated webform is submitted",
"nodeCreator.triggerHelperPanel.manualTriggerDisplayName": "Manually", "nodeCreator.triggerHelperPanel.manualTriggerDisplayName": "Trigger manually",
"nodeCreator.triggerHelperPanel.manualTriggerDescription": "Runs the flow on clicking a button in n8n", "nodeCreator.triggerHelperPanel.manualTriggerDescription": "Runs the flow on clicking a button in n8n. Good for getting started quickly",
"nodeCreator.triggerHelperPanel.manualChatTriggerDisplayName": "On chat message",
"nodeCreator.triggerHelperPanel.manualChatTriggerDescription": "Runs the flow when a user sends a chat message. For use with AI nodes",
"nodeCreator.triggerHelperPanel.manualTriggerTag": "Recommended",
"nodeCreator.triggerHelperPanel.whatHappensNext": "What happens next?", "nodeCreator.triggerHelperPanel.whatHappensNext": "What happens next?",
"nodeCreator.triggerHelperPanel.selectATrigger": "What triggers this workflow?", "nodeCreator.triggerHelperPanel.selectATrigger": "What triggers this workflow?",
"nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow", "nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
@ -1072,7 +1079,8 @@
"nodeCreator.aiPanel.newTag": "New", "nodeCreator.aiPanel.newTag": "New",
"nodeCreator.aiPanel.langchainAiNodes": "Advanced AI", "nodeCreator.aiPanel.langchainAiNodes": "Advanced AI",
"nodeCreator.aiPanel.title": "When should this workflow run?", "nodeCreator.aiPanel.title": "When should this workflow run?",
"nodeCreator.aiPanel.infoBox": "Check out our <a href=\"{link}\" target=\"_blank\">templates</a> for workflow examples and inspiration.", "nodeCreator.aiPanel.linkItem.description": "See what's possible and get started 5x faster",
"nodeCreator.aiPanel.linkItem.title": "AI Templates",
"nodeCreator.aiPanel.scheduleTriggerDisplayName": "On a schedule", "nodeCreator.aiPanel.scheduleTriggerDisplayName": "On a schedule",
"nodeCreator.aiPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval", "nodeCreator.aiPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
"nodeCreator.aiPanel.webhookTriggerDisplayName": "On webhook call", "nodeCreator.aiPanel.webhookTriggerDisplayName": "On webhook call",

View file

@ -15,7 +15,7 @@ import {
scaleReset, scaleReset,
scaleSmaller, scaleSmaller,
} from '@/utils/canvasUtils'; } from '@/utils/canvasUtils';
import { START_NODE_TYPE } from '@/constants'; import { MANUAL_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants';
import type { import type {
BeforeStartEventParams, BeforeStartEventParams,
BrowserJsPlumbInstance, BrowserJsPlumbInstance,
@ -61,6 +61,9 @@ export const useCanvasStore = defineStore('canvas', () => {
(node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type), (node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
), ),
); );
const aiNodes = computed<INodeUi[]>(() =>
nodes.value.filter((node) => node.type.includes('langchain')),
);
const isDemo = ref<boolean>(false); const isDemo = ref<boolean>(false);
const nodeViewScale = ref<number>(1); const nodeViewScale = ref<number>(1);
const canvasAddButtonPosition = ref<XYPosition>([1, 1]); const canvasAddButtonPosition = ref<XYPosition>([1, 1]);
@ -91,6 +94,23 @@ export const useCanvasStore = defineStore('canvas', () => {
}; };
}; };
const getAutoAddManualTriggerNode = (): INodeUi | null => {
const manualTriggerNode = nodeTypesStore.getNodeType(MANUAL_TRIGGER_NODE_TYPE);
if (!manualTriggerNode) {
console.error('Could not find the manual trigger node');
return null;
}
return {
id: uuid(),
name: manualTriggerNode.defaults.name?.toString() ?? manualTriggerNode.displayName,
type: MANUAL_TRIGGER_NODE_TYPE,
parameters: {},
position: canvasAddButtonPosition.value,
typeVersion: 1,
};
};
const getNodesWithPlaceholderNode = (): INodeUi[] => const getNodesWithPlaceholderNode = (): INodeUi[] =>
triggerNodes.value.length > 0 ? nodes.value : [getPlaceholderTriggerNodeUI(), ...nodes.value]; triggerNodes.value.length > 0 ? nodes.value : [getPlaceholderTriggerNodeUI(), ...nodes.value];
@ -298,6 +318,7 @@ export const useCanvasStore = defineStore('canvas', () => {
newNodeInsertPosition, newNodeInsertPosition,
jsPlumbInstance, jsPlumbInstance,
isLoading: loadingService.isLoading, isLoading: loadingService.isLoading,
aiNodes,
startLoading: loadingService.startLoading, startLoading: loadingService.startLoading,
setLoadingText: loadingService.setLoadingText, setLoadingText: loadingService.setLoadingText,
stopLoading: loadingService.stopLoading, stopLoading: loadingService.stopLoading,
@ -311,5 +332,6 @@ export const useCanvasStore = defineStore('canvas', () => {
zoomToFit, zoomToFit,
wheelScroll, wheelScroll,
initInstance, initInstance,
getAutoAddManualTriggerNode,
}; };
}); });

View file

@ -121,7 +121,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
* Constructs URLSearchParams object based on the default parameters for the template repository * Constructs URLSearchParams object based on the default parameters for the template repository
* and provided additional parameters * and provided additional parameters
*/ */
websiteTemplateRepositoryParameters() { websiteTemplateRepositoryParameters(roleOverride?: string) {
const rootStore = useRootStore(); const rootStore = useRootStore();
const userStore = useUsersStore(); const userStore = useUsersStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
@ -133,6 +133,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
}; };
const userRole: string | undefined = const userRole: string | undefined =
userStore.currentUserCloudInfo?.role ?? userStore.currentUser?.personalizationAnswers?.role; userStore.currentUserCloudInfo?.role ?? userStore.currentUser?.personalizationAnswers?.role;
if (userRole) { if (userRole) {
defaultParameters.utm_user_role = userRole; defaultParameters.utm_user_role = userRole;
} }
@ -156,10 +157,15 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
* Construct the URL for the template category page on the website for a given category id * Construct the URL for the template category page on the website for a given category id
*/ */
getWebsiteCategoryURL() { getWebsiteCategoryURL() {
return (id: string) => { return (id?: string, roleOverride?: string) => {
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.websiteTemplateRepositoryParameters({ const payload: Record<string, string> = {};
categories: id, if (id) {
}).toString()}`; payload.categories = id;
}
if (roleOverride) {
payload.utm_user_role = roleOverride;
}
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.websiteTemplateRepositoryParameters(payload).toString()}`;
}; };
}, },
}, },

View file

@ -11,7 +11,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
startData: {}, startData: {},
resultData: { resultData: {
runData: { runData: {
'When clicking "Test workflow"': [ 'When clicking Test workflow': [
{ {
startTime: 1706027170005, startTime: 1706027170005,
executionTime: 0, executionTime: 0,
@ -24,7 +24,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
{ {
startTime: 1706027170005, startTime: 1706027170005,
executionTime: 1, executionTime: 1,
source: [{ previousNode: 'When clicking "Test workflow"' }], source: [{ previousNode: 'When clicking Test workflow' }],
executionStatus: 'success', executionStatus: 'success',
data: { data: {
main: [ main: [
@ -258,54 +258,54 @@ describe('pairedItemUtils', () => {
const actual = getPairedItemsMapping(MOCK_EXECUTION); const actual = getPairedItemsMapping(MOCK_EXECUTION);
const expected = { const expected = {
DebugHelper_r0_o0_i0: new Set([ DebugHelper_r0_o0_i0: new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'If_r0_o0_i0', 'If_r0_o0_i0',
'Edit Fields_r1_o0_i0', 'Edit Fields_r1_o0_i0',
'Edit Fields1_r1_o0_i0', 'Edit Fields1_r1_o0_i0',
]), ]),
DebugHelper_r0_o0_i1: new Set([ DebugHelper_r0_o0_i1: new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'If_r0_o1_i0', 'If_r0_o1_i0',
'Edit Fields_r0_o0_i0', 'Edit Fields_r0_o0_i0',
'Edit Fields1_r0_o0_i0', 'Edit Fields1_r0_o0_i0',
]), ]),
'Edit Fields1_r0_o0_i0': new Set([ 'Edit Fields1_r0_o0_i0': new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i1', 'DebugHelper_r0_o0_i1',
'If_r0_o1_i0', 'If_r0_o1_i0',
'Edit Fields_r0_o0_i0', 'Edit Fields_r0_o0_i0',
]), ]),
'Edit Fields1_r1_o0_i0': new Set([ 'Edit Fields1_r1_o0_i0': new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i0', 'DebugHelper_r0_o0_i0',
'If_r0_o0_i0', 'If_r0_o0_i0',
'Edit Fields_r1_o0_i0', 'Edit Fields_r1_o0_i0',
]), ]),
'Edit Fields_r0_o0_i0': new Set([ 'Edit Fields_r0_o0_i0': new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i1', 'DebugHelper_r0_o0_i1',
'If_r0_o1_i0', 'If_r0_o1_i0',
'Edit Fields1_r0_o0_i0', 'Edit Fields1_r0_o0_i0',
]), ]),
'Edit Fields_r1_o0_i0': new Set([ 'Edit Fields_r1_o0_i0': new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i0', 'DebugHelper_r0_o0_i0',
'If_r0_o0_i0', 'If_r0_o0_i0',
'Edit Fields1_r1_o0_i0', 'Edit Fields1_r1_o0_i0',
]), ]),
If_r0_o0_i0: new Set([ If_r0_o0_i0: new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i0', 'DebugHelper_r0_o0_i0',
'Edit Fields_r1_o0_i0', 'Edit Fields_r1_o0_i0',
'Edit Fields1_r1_o0_i0', 'Edit Fields1_r1_o0_i0',
]), ]),
If_r0_o1_i0: new Set([ If_r0_o1_i0: new Set([
'When clicking "Test workflow"_r0_o0_i0', 'When clicking Test workflow_r0_o0_i0',
'DebugHelper_r0_o0_i1', 'DebugHelper_r0_o0_i1',
'Edit Fields_r0_o0_i0', 'Edit Fields_r0_o0_i0',
'Edit Fields1_r0_o0_i0', 'Edit Fields1_r0_o0_i0',
]), ]),
'When clicking "Test workflow"_r0_o0_i0': new Set([ 'When clicking Test workflow_r0_o0_i0': new Set([
'DebugHelper_r0_o0_i0', 'DebugHelper_r0_o0_i0',
'DebugHelper_r0_o0_i1', 'DebugHelper_r0_o0_i1',
'If_r0_o0_i0', 'If_r0_o0_i0',

View file

@ -250,6 +250,7 @@ import {
UPDATE_WEBHOOK_ID_NODE_TYPES, UPDATE_WEBHOOK_ID_NODE_TYPES,
TIME, TIME,
AI_ASSISTANT_LOCAL_STORAGE_KEY, AI_ASSISTANT_LOCAL_STORAGE_KEY,
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT,
} from '@/constants'; } from '@/constants';
import useGlobalLinkActions from '@/composables/useGlobalLinkActions'; import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
@ -395,6 +396,7 @@ import type { ProjectSharingData } from '@/features/projects/projects.types';
import { useAIStore } from '@/stores/ai.store'; import { useAIStore } from '@/stores/ai.store';
import { useStorage } from '@/composables/useStorage'; import { useStorage } from '@/composables/useStorage';
import { isJSPlumbEndpointElement } from '@/utils/typeGuards'; import { isJSPlumbEndpointElement } from '@/utils/typeGuards';
import { usePostHog } from '@/stores/posthog.store';
import { ProjectTypes } from '@/features/projects/projects.utils'; import { ProjectTypes } from '@/features/projects/projects.utils';
interface AddNodeOptions { interface AddNodeOptions {
@ -944,6 +946,17 @@ export default defineComponent({
action: this.openSelectiveNodeCreator, action: this.openSelectiveNodeCreator,
}); });
this.registerCustomAction({
key: 'showNodeCreator',
action: () => {
this.ndvStore.activeNodeName = null;
void this.$nextTick(() => {
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TAB);
});
},
});
this.readOnlyEnvRouteCheck(); this.readOnlyEnvRouteCheck();
this.canvasStore.isDemo = this.isDemo; this.canvasStore.isDemo = this.isDemo;
}, },
@ -1177,12 +1190,6 @@ export default defineComponent({
? this.$locale.baseText('nodeView.addOrEnableTriggerNode') ? this.$locale.baseText('nodeView.addOrEnableTriggerNode')
: this.$locale.baseText('nodeView.addATriggerNodeFirst'); : this.$locale.baseText('nodeView.addATriggerNodeFirst');
this.registerCustomAction({
key: 'showNodeCreator',
action: () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
});
const notice = this.showMessage({ const notice = this.showMessage({
type: 'info', type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'), title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
@ -1257,9 +1264,15 @@ export default defineComponent({
}, },
showTriggerCreator(source: NodeCreatorOpenSource) { showTriggerCreator(source: NodeCreatorOpenSource) {
if (this.createNodeActive) return; if (this.createNodeActive) return;
this.ndvStore.activeNodeName = null;
this.nodeCreatorStore.setSelectedView(TRIGGER_NODE_CREATOR_VIEW); this.nodeCreatorStore.setSelectedView(TRIGGER_NODE_CREATOR_VIEW);
this.nodeCreatorStore.setShowScrim(true); this.nodeCreatorStore.setShowScrim(true);
this.onToggleNodeCreator({ source, createNodeActive: true }); this.onToggleNodeCreator({
source,
createNodeActive: true,
nodeCreatorView: TRIGGER_NODE_CREATOR_VIEW,
});
}, },
async openExecution(executionId: string) { async openExecution(executionId: string) {
this.canvasStore.startLoading(); this.canvasStore.startLoading();
@ -3659,6 +3672,7 @@ export default defineComponent({
this.workflowsStore.workflow.scopes = scopes; this.workflowsStore.workflow.scopes = scopes;
}, },
async newWorkflow(): Promise<void> { async newWorkflow(): Promise<void> {
const { getVariant } = usePostHog();
this.canvasStore.startLoading(); this.canvasStore.startLoading();
this.resetWorkspace(); this.resetWorkspace();
this.workflowData = await this.workflowsStore.getNewWorkflowData( this.workflowData = await this.workflowsStore.getNewWorkflowData(
@ -3670,15 +3684,24 @@ export default defineComponent({
this.uiStore.stateIsDirty = false; this.uiStore.stateIsDirty = false;
this.canvasStore.setZoomLevel(1, [0, 0]); this.canvasStore.setZoomLevel(1, [0, 0]);
await this.tryToAddWelcomeSticky(); this.canvasStore.zoomToFit();
this.uiStore.nodeViewInitialized = true; this.uiStore.nodeViewInitialized = true;
this.historyStore.reset(); this.historyStore.reset();
this.executionsStore.activeExecution = null; this.executionsStore.activeExecution = null;
this.makeNewWorkflowShareable(); this.makeNewWorkflowShareable();
this.canvasStore.stopLoading(); this.canvasStore.stopLoading();
},
async tryToAddWelcomeSticky(): Promise<void> { // Pre-populate the canvas with the manual trigger node if the experiment is enabled and the user is in the variant group
this.canvasStore.zoomToFit(); if (
getVariant(CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name) ===
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.variant
) {
const manualTriggerNode = this.canvasStore.getAutoAddManualTriggerNode();
if (manualTriggerNode) {
await this.addNodes([manualTriggerNode]);
this.uiStore.lastSelectedNode = manualTriggerNode.name;
}
}
}, },
async initView(): Promise<void> { async initView(): Promise<void> {
if (this.$route.params.action === 'workflowSave') { if (this.$route.params.action === 'workflowSave') {
@ -5375,4 +5398,3 @@ export default defineComponent({
); );
} }
</style> </style>
, IRun, IPushDataExecutionFinished

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "59f5ae0f-52f7-4bc8-b325-29d2b0d810f8", "id": "59f5ae0f-52f7-4bc8-b325-29d2b0d810f8",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -217,7 +217,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -7,7 +7,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "fb826323-2e48-4f11-bb0e-e12de32e22ee", "id": "fb826323-2e48-4f11-bb0e-e12de32e22ee",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [180, 160] "position": [180, 160]
@ -26,7 +26,7 @@
} }
], ],
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "fcc3e9dc-90c9-4b26-9b44-e661e0ebf658", "id": "fcc3e9dc-90c9-4b26-9b44-e661e0ebf658",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -305,7 +305,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -16,7 +16,7 @@ export class ManualTrigger implements INodeType {
eventTriggerDescription: '', eventTriggerDescription: '',
maxNodes: 1, maxNodes: 1,
defaults: { defaults: {
name: 'When clicking "Test workflow"', name: 'When clicking Test workflow',
color: '#909298', color: '#909298',
}, },
@ -25,7 +25,7 @@ export class ManualTrigger implements INodeType {
properties: [ properties: [
{ {
displayName: displayName:
'This node is where a manual workflow execution starts. To make one, go back to the canvas and click test workflow', 'This node is where the workflow execution starts (when you click the test button on the canvas).<br><br> <a data-action="showNodeCreator">Explore other ways to trigger your workflow</a> (e.g on a schedule, or a webhook)',
name: 'notice', name: 'notice',
type: 'notice', type: 'notice',
default: '', default: '',

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "94003e55-6c4e-492f-802a-49f4fb5b5f4b", "id": "94003e55-6c4e-492f-802a-49f4fb5b5f4b",
"name": "When clicking \"Test Workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -485,7 +485,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test Workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "1301e15e-7a64-44bf-bc4b-d60e7b8c629a", "id": "1301e15e-7a64-44bf-bc4b-d60e7b8c629a",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
@ -273,7 +273,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {

View file

@ -667,7 +667,7 @@ describe('generateNodesGraph', () => {
{ {
parameters: {}, parameters: {},
id: 'fe69383c-e418-4f98-9c0e-924deafa7f93', id: 'fe69383c-e418-4f98-9c0e-924deafa7f93',
name: 'When clicking "Test workflow"', name: 'When clicking Test workflow',
type: 'n8n-nodes-base.manualTrigger', type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1, typeVersion: 1,
position: [540, 220], position: [540, 220],
@ -692,7 +692,7 @@ describe('generateNodesGraph', () => {
}, },
], ],
connections: { connections: {
'When clicking "Test workflow"': { 'When clicking Test workflow': {
main: [ main: [
[ [
{ {
@ -758,7 +758,7 @@ describe('generateNodesGraph', () => {
is_pinned: false, is_pinned: false,
}, },
nameIndices: { nameIndices: {
'When clicking "Test workflow"': '0', 'When clicking Test workflow': '0',
Chain: '1', Chain: '1',
Model: '2', Model: '2',
}, },

View file

@ -3,7 +3,7 @@
"startData": {}, "startData": {},
"resultData": { "resultData": {
"runData": { "runData": {
"When clicking \"Test workflow\"": [ "When clicking Test workflow": [
{ {
"startTime": 1707471743600, "startTime": 1707471743600,
"executionTime": 1, "executionTime": 1,
@ -29,7 +29,7 @@
"executionTime": 1, "executionTime": 1,
"source": [ "source": [
{ {
"previousNode": "When clicking \"Test workflow\"" "previousNode": "When clicking Test workflow"
} }
], ],
"executionStatus": "success", "executionStatus": "success",
@ -956,7 +956,7 @@
"source": { "source": {
"main": [ "main": [
{ {
"previousNode": "When clicking \"Test workflow\"" "previousNode": "When clicking Test workflow"
} }
] ]
} }
@ -999,7 +999,7 @@
"source": { "source": {
"main": [ "main": [
{ {
"previousNode": "When clicking \"Test workflow\"" "previousNode": "When clicking Test workflow"
} }
] ]
} }

View file

@ -4,7 +4,7 @@
{ {
"parameters": {}, "parameters": {},
"id": "b5122d27-4bb5-4100-a69b-03b1dcac76c7", "id": "b5122d27-4bb5-4100-a69b-03b1dcac76c7",
"name": "When clicking \"Test workflow\"", "name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger", "type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1, "typeVersion": 1,
"position": [740, 1680] "position": [740, 1680]
@ -561,7 +561,7 @@
] ]
}, },
"connections": { "connections": {
"When clicking \"Test workflow\"": { "When clicking Test workflow": {
"main": [ "main": [
[ [
{ {