mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
feat(editor): Add Python to Code actions (#18668)
This commit is contained in:
@@ -40,6 +40,8 @@ export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
|
|||||||
export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received';
|
export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received';
|
||||||
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';
|
||||||
|
export const CODE_NODE_DISPLAY_NAME = 'Code in JavaScript';
|
||||||
|
export const CODE_NODE_ACTION = CODE_NODE_DISPLAY_NAME;
|
||||||
export const SET_NODE_NAME = 'Set';
|
export const SET_NODE_NAME = 'Set';
|
||||||
export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields';
|
export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields';
|
||||||
export const LOOP_OVER_ITEMS_NODE_NAME = 'Loop Over Items';
|
export const LOOP_OVER_ITEMS_NODE_NAME = 'Loop Over Items';
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { getCanvasNodes } from '../composables/workflow';
|
import { getCanvasNodes } from '../composables/workflow';
|
||||||
import {
|
import {
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
CODE_NODE_NAME,
|
|
||||||
SET_NODE_NAME,
|
SET_NODE_NAME,
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
@@ -19,9 +19,9 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo deleting node using context menu', () => {
|
it('should undo/redo deleting node using context menu', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.actions.zoomToFit();
|
WorkflowPage.actions.zoomToFit();
|
||||||
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
|
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_DISPLAY_NAME, {
|
||||||
method: 'right-click',
|
method: 'right-click',
|
||||||
anchor: 'topLeft',
|
anchor: 'topLeft',
|
||||||
});
|
});
|
||||||
@@ -37,8 +37,8 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo deleting node using keyboard shortcut', () => {
|
it('should undo/redo deleting node using keyboard shortcut', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
|
||||||
cy.get('body').type('{backspace}');
|
cy.get('body').type('{backspace}');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||||
@@ -52,9 +52,9 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo deleting node between two connected nodes', () => {
|
it('should undo/redo deleting node between two connected nodes', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
||||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
|
||||||
WorkflowPage.actions.zoomToFit();
|
WorkflowPage.actions.zoomToFit();
|
||||||
cy.get('body').type('{backspace}');
|
cy.get('body').type('{backspace}');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
|
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
|
||||||
@@ -69,7 +69,7 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo deleting whole workflow', () => {
|
it('should undo/redo deleting whole workflow', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
WorkflowPage.actions.hitDeleteAllNodes();
|
WorkflowPage.actions.hitDeleteAllNodes();
|
||||||
@@ -85,7 +85,7 @@ describe('Undo/Redo', () => {
|
|||||||
it('should undo/redo moving nodes', () => {
|
it('should undo/redo moving nodes', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
|
|
||||||
WorkflowPage.actions.zoomToFit();
|
WorkflowPage.actions.zoomToFit();
|
||||||
|
|
||||||
@@ -131,8 +131,8 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo deleting a connection using context menu', () => {
|
it('should undo/redo deleting a connection using context menu', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.actions.deleteNodeBetweenNodes(SCHEDULE_TRIGGER_NODE_NAME, CODE_NODE_NAME);
|
WorkflowPage.actions.deleteNodeBetweenNodes(SCHEDULE_TRIGGER_NODE_NAME, CODE_NODE_DISPLAY_NAME);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||||
WorkflowPage.actions.hitUndo();
|
WorkflowPage.actions.hitUndo();
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||||
@@ -142,8 +142,8 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo disabling a node using context menu', () => {
|
it('should undo/redo disabling a node using context menu', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.actions.disableNode(CODE_NODE_NAME);
|
WorkflowPage.actions.disableNode(CODE_NODE_DISPLAY_NAME);
|
||||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||||
WorkflowPage.actions.hitUndo();
|
WorkflowPage.actions.hitUndo();
|
||||||
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
||||||
@@ -153,7 +153,7 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo disabling a node using keyboard shortcut', () => {
|
it('should undo/redo disabling a node using keyboard shortcut', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.getters.canvasNodes().last().click();
|
WorkflowPage.getters.canvasNodes().last().click();
|
||||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||||
@@ -165,7 +165,7 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo disabling multiple nodes', () => {
|
it('should undo/redo disabling multiple nodes', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
WorkflowPage.actions.hitSelectAll();
|
WorkflowPage.actions.hitSelectAll();
|
||||||
@@ -179,8 +179,8 @@ describe('Undo/Redo', () => {
|
|||||||
|
|
||||||
it('should undo/redo duplicating a node', () => {
|
it('should undo/redo duplicating a node', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addCodeNodeToCanvas();
|
||||||
WorkflowPage.actions.duplicateNode(CODE_NODE_NAME);
|
WorkflowPage.actions.duplicateNode(CODE_NODE_DISPLAY_NAME);
|
||||||
WorkflowPage.actions.hitUndo();
|
WorkflowPage.actions.hitUndo();
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||||
WorkflowPage.actions.hitRedo();
|
WorkflowPage.actions.hitRedo();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
EDIT_FIELDS_SET_NODE_NAME,
|
EDIT_FIELDS_SET_NODE_NAME,
|
||||||
SWITCH_NODE_NAME,
|
SWITCH_NODE_NAME,
|
||||||
MERGE_NODE_NAME,
|
MERGE_NODE_NAME,
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import {
|
import {
|
||||||
clickContextMenuAction,
|
clickContextMenuAction,
|
||||||
@@ -165,7 +166,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.zoomToFit();
|
WorkflowPage.actions.zoomToFit();
|
||||||
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
|
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_DISPLAY_NAME, {
|
||||||
method: 'right-click',
|
method: 'right-click',
|
||||||
anchor: 'topLeft',
|
anchor: 'topLeft',
|
||||||
});
|
});
|
||||||
@@ -176,7 +177,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||||||
it('should delete node using keyboard shortcut', () => {
|
it('should delete node using keyboard shortcut', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
|
||||||
cy.get('body').type('{backspace}');
|
cy.get('body').type('{backspace}');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 1);
|
WorkflowPage.getters.canvasNodes().should('have.length', 1);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||||
@@ -188,7 +189,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 2);
|
WorkflowPage.getters.nodeConnections().should('have.length', 2);
|
||||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
|
||||||
WorkflowPage.actions.zoomToFit();
|
WorkflowPage.actions.zoomToFit();
|
||||||
cy.get('body').type('{backspace}');
|
cy.get('body').type('{backspace}');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||||
@@ -318,7 +319,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||||
|
|
||||||
WorkflowPage.actions.disableNode(CODE_NODE_NAME);
|
WorkflowPage.actions.disableNode(CODE_NODE_DISPLAY_NAME);
|
||||||
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -394,7 +395,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
WorkflowPage.actions.duplicateNode(CODE_NODE_NAME);
|
WorkflowPage.actions.duplicateNode(CODE_NODE_DISPLAY_NAME);
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ describe('NDV', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not push NDV header out with a lot of code in Code node editor', () => {
|
it('should not push NDV header out with a lot of code in Code node editor', () => {
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
|
workflowPage.actions.addInitialCodeNodeToCanvas({ keepNdvOpen: true });
|
||||||
ndv.getters.parameterInput('jsCode').get('.cm-content').type('{selectall}').type('{backspace}');
|
ndv.getters.parameterInput('jsCode').get('.cm-content').type('{selectall}').type('{backspace}');
|
||||||
cy.fixture('Dummy_javascript.txt').then((code) => {
|
cy.fixture('Dummy_javascript.txt').then((code) => {
|
||||||
ndv.getters.parameterInput('jsCode').get('.cm-content').paste(code);
|
ndv.getters.parameterInput('jsCode').get('.cm-content').paste(code);
|
||||||
@@ -400,7 +400,7 @@ describe('NDV', () => {
|
|||||||
|
|
||||||
it('should allow editing code in fullscreen in the code editors', () => {
|
it('should allow editing code in fullscreen in the code editors', () => {
|
||||||
// Code (JavaScript)
|
// Code (JavaScript)
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
|
workflowPage.actions.addInitialCodeNodeToCanvas({ keepNdvOpen: true });
|
||||||
ndv.actions.openCodeEditorFullscreen();
|
ndv.actions.openCodeEditorFullscreen();
|
||||||
|
|
||||||
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
|
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
|
||||||
@@ -725,8 +725,7 @@ describe('NDV', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should properly show node execution indicator', () => {
|
it('should properly show node execution indicator', () => {
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Code');
|
workflowPage.actions.addInitialCodeNodeToCanvas();
|
||||||
workflowPage.actions.openNode('Code');
|
|
||||||
// Should not show run info before execution
|
// Should not show run info before execution
|
||||||
ndv.getters.nodeRunSuccessIndicator().should('not.exist');
|
ndv.getters.nodeRunSuccessIndicator().should('not.exist');
|
||||||
ndv.getters.nodeRunErrorIndicator().should('not.exist');
|
ndv.getters.nodeRunErrorIndicator().should('not.exist');
|
||||||
@@ -737,8 +736,7 @@ describe('NDV', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should properly show node execution indicator for multiple nodes', () => {
|
it('should properly show node execution indicator for multiple nodes', () => {
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Code');
|
workflowPage.actions.addInitialCodeNodeToCanvas();
|
||||||
workflowPage.actions.openNode('Code');
|
|
||||||
ndv.actions.typeIntoParameterInput('jsCode', 'testets');
|
ndv.actions.typeIntoParameterInput('jsCode', 'testets');
|
||||||
ndv.getters.backToCanvas().click();
|
ndv.getters.backToCanvas().click();
|
||||||
workflowPage.actions.executeWorkflow();
|
workflowPage.actions.executeWorkflow();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BasePage } from './base';
|
import { BasePage } from './base';
|
||||||
import { NodeCreator } from './features/node-creator';
|
import { NodeCreator } from './features/node-creator';
|
||||||
import { clickContextMenuAction, getCanvasPane, openContextMenu } from '../composables/workflow';
|
import { clickContextMenuAction, getCanvasPane, openContextMenu } from '../composables/workflow';
|
||||||
import { META_KEY } from '../constants';
|
import { CODE_NODE_ACTION, CODE_NODE_NAME, META_KEY } from '../constants';
|
||||||
import type { OpenContextMenuOptions } from '../types';
|
import type { OpenContextMenuOptions } from '../types';
|
||||||
import { getVisibleSelect } from '../utils';
|
import { getVisibleSelect } from '../utils';
|
||||||
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
||||||
@@ -179,6 +179,12 @@ export class WorkflowPage extends BasePage {
|
|||||||
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
|
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addInitialCodeNodeToCanvas(opts: { keepNdvOpen: boolean } = { keepNdvOpen: false }) {
|
||||||
|
this.addInitialNodeToCanvas(CODE_NODE_NAME, {
|
||||||
|
action: CODE_NODE_ACTION,
|
||||||
|
keepNdvOpen: opts.keepNdvOpen,
|
||||||
|
});
|
||||||
|
},
|
||||||
addInitialNodeToCanvas: (
|
addInitialNodeToCanvas: (
|
||||||
nodeDisplayName: string,
|
nodeDisplayName: string,
|
||||||
opts?: { keepNdvOpen?: boolean; action?: string; isTrigger?: boolean },
|
opts?: { keepNdvOpen?: boolean; action?: string; isTrigger?: boolean },
|
||||||
@@ -202,6 +208,9 @@ export class WorkflowPage extends BasePage {
|
|||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addCodeNodeToCanvas(plusButtonClick = true, preventNdvClose?: boolean) {
|
||||||
|
this.addNodeToCanvas(CODE_NODE_NAME, plusButtonClick, preventNdvClose, CODE_NODE_ACTION);
|
||||||
|
},
|
||||||
addNodeToCanvas: (
|
addNodeToCanvas: (
|
||||||
nodeDisplayName: string,
|
nodeDisplayName: string,
|
||||||
plusButtonClick = true,
|
plusButtonClick = true,
|
||||||
|
|||||||
@@ -733,6 +733,7 @@ export type NodeTypeSelectedPayload = {
|
|||||||
parameters?: {
|
parameters?: {
|
||||||
resource?: string;
|
resource?: string;
|
||||||
operation?: string;
|
operation?: string;
|
||||||
|
language?: string;
|
||||||
};
|
};
|
||||||
actionName?: string;
|
actionName?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -185,6 +185,11 @@ export const useActions = () => {
|
|||||||
actionName: actionData.name,
|
actionName: actionData.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof actionData.value.language === 'string') {
|
||||||
|
result.parameters = { language: actionData.value.language };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof actionData.value.resource === 'string' ||
|
typeof actionData.value.resource === 'string' ||
|
||||||
typeof actionData.value.operation === 'string'
|
typeof actionData.value.operation === 'string'
|
||||||
|
|||||||
@@ -54,6 +54,25 @@ const customNodeActionsParsers: {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
['n8n-nodes-base.code']: (matchedProperty, nodeTypeDescription) => {
|
||||||
|
if (matchedProperty.name !== 'language') return;
|
||||||
|
|
||||||
|
const languageOptions = matchedProperty.options as INodePropertyOptions[] | undefined;
|
||||||
|
if (!languageOptions) return;
|
||||||
|
|
||||||
|
return languageOptions.map(
|
||||||
|
(option): ActionTypeDescription => ({
|
||||||
|
...getNodeTypeBase(nodeTypeDescription),
|
||||||
|
actionKey: `language_${option.value}`,
|
||||||
|
displayName: `Code in ${option.name}`,
|
||||||
|
description: `Run custom ${option.name} code`,
|
||||||
|
displayOptions: matchedProperty.displayOptions,
|
||||||
|
values: {
|
||||||
|
language: option.value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, label?: string) {
|
function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, label?: string) {
|
||||||
@@ -79,6 +98,21 @@ function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, label?: stri
|
|||||||
function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTypeDescription[] {
|
function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTypeDescription[] {
|
||||||
if (nodeTypeDescription.properties.find((property) => property.name === 'resource')) return [];
|
if (nodeTypeDescription.properties.find((property) => property.name === 'resource')) return [];
|
||||||
|
|
||||||
|
if (nodeTypeDescription.name === 'n8n-nodes-base.code') {
|
||||||
|
const languageProperty = nodeTypeDescription.properties.find(
|
||||||
|
(property) =>
|
||||||
|
property.name === 'language' && property.displayOptions?.show?.['@version']?.[0] === 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (languageProperty) {
|
||||||
|
const customParsedItems = customNodeActionsParsers[nodeTypeDescription.name]?.(
|
||||||
|
languageProperty,
|
||||||
|
nodeTypeDescription,
|
||||||
|
);
|
||||||
|
if (customParsedItems) return customParsedItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const matchedProperty = nodeTypeDescription.properties.find(
|
const matchedProperty = nodeTypeDescription.properties.find(
|
||||||
(property) => property.name?.toLowerCase() === 'operation',
|
(property) => property.name?.toLowerCase() === 'operation',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ const getV2LanguageProperty = (): INodeProperties => {
|
|||||||
{
|
{
|
||||||
name: 'JavaScript',
|
name: 'JavaScript',
|
||||||
value: 'javaScript',
|
value: 'javaScript',
|
||||||
|
action: 'Code in JavaScript',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Python (Beta)',
|
name: 'Python (Beta)',
|
||||||
value: 'python',
|
value: 'python',
|
||||||
|
action: 'Code in Python (Beta)',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ const getV2LanguageProperty = (): INodeProperties => {
|
|||||||
options.push({
|
options.push({
|
||||||
name: 'Python (Native) (Beta)',
|
name: 'Python (Native) (Beta)',
|
||||||
value: 'pythonNative',
|
value: 'pythonNative',
|
||||||
|
action: 'Code in Python (Native) (Beta)',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
|
|||||||
export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received';
|
export const CHAT_TRIGGER_NODE_DISPLAY_NAME = 'When chat message received';
|
||||||
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';
|
||||||
|
export const CODE_NODE_DISPLAY_NAME = 'Code in JavaScript';
|
||||||
export const SET_NODE_NAME = 'Set';
|
export const SET_NODE_NAME = 'Set';
|
||||||
export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields (Set)';
|
export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields (Set)';
|
||||||
export const LOOP_OVER_ITEMS_NODE_NAME = 'Loop Over Items';
|
export const LOOP_OVER_ITEMS_NODE_NAME = 'Loop Over Items';
|
||||||
|
|||||||
@@ -473,4 +473,9 @@ export class CanvasPage extends BasePage {
|
|||||||
|
|
||||||
await this.page.goto('/workflow/new');
|
await this.page.goto('/workflow/new');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addNodeWithSubItem(searchText: string, subItemText: string): Promise<void> {
|
||||||
|
await this.addNode(searchText);
|
||||||
|
await this.nodeCreatorSubItem(subItemText).click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { MANUAL_TRIGGER_NODE_NAME, MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../config/constants';
|
import {
|
||||||
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
|
CODE_NODE_NAME,
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
|
} from '../../config/constants';
|
||||||
import { test, expect } from '../../fixtures/base';
|
import { test, expect } from '../../fixtures/base';
|
||||||
|
|
||||||
test.describe('Canvas Node Actions', () => {
|
test.describe('Canvas Node Actions', () => {
|
||||||
@@ -52,8 +57,9 @@ test.describe('Canvas Node Actions', () => {
|
|||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
|
|
||||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||||
await n8n.canvas.fillNodeCreatorSearchBar('Code');
|
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
||||||
await n8n.page.keyboard.press('Enter');
|
await n8n.page.keyboard.press('Enter');
|
||||||
|
await n8n.canvas.nodeCreatorSubItem(CODE_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.page.keyboard.press('Escape');
|
await n8n.page.keyboard.press('Escape');
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
@@ -63,7 +69,11 @@ test.describe('Canvas Node Actions', () => {
|
|||||||
test('should add disconnected node when nothing selected', async ({ n8n }) => {
|
test('should add disconnected node when nothing selected', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.deselectAll();
|
await n8n.canvas.deselectAll();
|
||||||
await n8n.canvas.addNode('Code', { closeNDV: true });
|
await n8n.canvas.clickNodeCreatorPlusButton();
|
||||||
|
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
||||||
|
await n8n.page.keyboard.press('Enter');
|
||||||
|
await n8n.canvas.nodeCreatorSubItem(CODE_NODE_DISPLAY_NAME).click();
|
||||||
|
await n8n.page.keyboard.press('Escape');
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
CODE_NODE_NAME,
|
CODE_NODE_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
} from '../../config/constants';
|
} from '../../config/constants';
|
||||||
import { test, expect } from '../../fixtures/base';
|
import { test, expect } from '../../fixtures/base';
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||||
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
||||||
await n8n.page.keyboard.press('Enter');
|
await n8n.page.keyboard.press('Enter');
|
||||||
|
await n8n.canvas.nodeCreatorSubItem(CODE_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.page.keyboard.press('Escape');
|
await n8n.page.keyboard.press('Escape');
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
@@ -32,12 +34,10 @@ test.describe('Canvas Actions', () => {
|
|||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||||
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
||||||
|
await n8n.page.keyboard.press('Enter');
|
||||||
const sourceElement = n8n.canvas
|
await n8n.canvas
|
||||||
.nodeCreatorNodeItems()
|
.nodeCreatorSubItem(CODE_NODE_DISPLAY_NAME)
|
||||||
.filter({ hasText: CODE_NODE_NAME })
|
.dragTo(n8n.canvas.canvasPane(), { targetPosition: { x: 100, y: 100 } });
|
||||||
.first();
|
|
||||||
await sourceElement.dragTo(n8n.canvas.canvasPane(), { targetPosition: { x: 100, y: 100 } });
|
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||||
@@ -61,7 +61,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
test('should add disconnected node if nothing is selected', async ({ n8n }) => {
|
test('should add disconnected node if nothing is selected', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.deselectAll();
|
await n8n.canvas.deselectAll();
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||||
@@ -70,14 +70,14 @@ test.describe('Canvas Actions', () => {
|
|||||||
test('should add node between two connected nodes', async ({ n8n }) => {
|
test('should add node between two connected nodes', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||||
|
|
||||||
await n8n.canvas.addNodeBetweenNodes(
|
await n8n.canvas.addNodeBetweenNodes(
|
||||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
CODE_NODE_NAME,
|
CODE_NODE_DISPLAY_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -96,8 +96,11 @@ test.describe('Canvas Actions', () => {
|
|||||||
test('should delete connections by clicking on the delete button', async ({ n8n }) => {
|
test('should delete connections by clicking on the delete button', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvas.deleteConnectionBetweenNodes(MANUAL_TRIGGER_NODE_DISPLAY_NAME, CODE_NODE_NAME);
|
await n8n.canvas.deleteConnectionBetweenNodes(
|
||||||
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
@@ -116,9 +119,9 @@ test.describe('Canvas Actions', () => {
|
|||||||
|
|
||||||
test('should disable and enable node', async ({ n8n }) => {
|
test('should disable and enable node', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
|
|
||||||
const disableButton = n8n.canvas.nodeDisableButton(CODE_NODE_NAME);
|
const disableButton = n8n.canvas.nodeDisableButton(CODE_NODE_DISPLAY_NAME);
|
||||||
await disableButton.click();
|
await disableButton.click();
|
||||||
|
|
||||||
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
|
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
|
||||||
@@ -130,8 +133,8 @@ test.describe('Canvas Actions', () => {
|
|||||||
|
|
||||||
test('should delete node', async ({ n8n }) => {
|
test('should delete node', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvas.deleteNodeByName(CODE_NODE_NAME);
|
await n8n.canvas.deleteNodeByName(CODE_NODE_DISPLAY_NAME);
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||||
await expect(n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME)).toBeVisible();
|
await expect(n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME)).toBeVisible();
|
||||||
@@ -140,9 +143,9 @@ test.describe('Canvas Actions', () => {
|
|||||||
|
|
||||||
test('should copy selected nodes', async ({ n8n }) => {
|
test('should copy selected nodes', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvasComposer.selectAllAndCopy();
|
await n8n.canvasComposer.selectAllAndCopy();
|
||||||
await n8n.canvas.nodeByName(CODE_NODE_NAME).click();
|
await n8n.canvas.nodeByName(CODE_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.canvasComposer.copySelectedNodesWithToast();
|
await n8n.canvasComposer.copySelectedNodesWithToast();
|
||||||
|
|
||||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||||
@@ -150,7 +153,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
|
|
||||||
test('should select/deselect all nodes', async ({ n8n }) => {
|
test('should select/deselect all nodes', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvas.selectAll();
|
await n8n.canvas.selectAll();
|
||||||
|
|
||||||
await expect(n8n.canvas.selectedNodes()).toHaveCount(2);
|
await expect(n8n.canvas.selectedNodes()).toHaveCount(2);
|
||||||
@@ -162,7 +165,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
test('should select nodes using arrow keys', async ({ n8n }) => {
|
test('should select nodes using arrow keys', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvas.getCanvasNodes().first().waitFor();
|
await n8n.canvas.getCanvasNodes().first().waitFor();
|
||||||
await n8n.canvas.navigateNodesWithArrows('left');
|
await n8n.canvas.navigateNodesWithArrows('left');
|
||||||
|
|
||||||
@@ -177,7 +180,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
test('should select nodes using shift and arrow keys', async ({ n8n }) => {
|
test('should select nodes using shift and arrow keys', async ({ n8n }) => {
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
await n8n.canvas.addNode(CODE_NODE_NAME, { action: CODE_NODE_DISPLAY_NAME, closeNDV: true });
|
||||||
await n8n.canvas.getCanvasNodes().first().waitFor();
|
await n8n.canvas.getCanvasNodes().first().waitFor();
|
||||||
await n8n.canvas.extendSelectionWithArrows('left');
|
await n8n.canvas.extendSelectionWithArrows('left');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
import { CODE_NODE_NAME, MANUAL_TRIGGER_NODE_NAME } from '../../config/constants';
|
import {
|
||||||
|
CODE_NODE_DISPLAY_NAME,
|
||||||
|
CODE_NODE_NAME,
|
||||||
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
|
} from '../../config/constants';
|
||||||
import { test, expect } from '../../fixtures/base';
|
import { test, expect } from '../../fixtures/base';
|
||||||
|
|
||||||
test.describe('Code node', () => {
|
test.describe('Code node', () => {
|
||||||
@@ -9,7 +13,7 @@ test.describe('Code node', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
await n8n.workflows.clickAddWorkflowButton();
|
await n8n.workflows.clickAddWorkflowButton();
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME);
|
await n8n.canvas.addNodeWithSubItem(CODE_NODE_NAME, CODE_NODE_DISPLAY_NAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show correct placeholders switching modes', async ({ n8n }) => {
|
test('should show correct placeholders switching modes', async ({ n8n }) => {
|
||||||
@@ -53,17 +57,17 @@ test.describe('Code node', () => {
|
|||||||
await n8n.ndv.getCodeEditor().fill("console.log('code node 1')");
|
await n8n.ndv.getCodeEditor().fill("console.log('code node 1')");
|
||||||
await n8n.ndv.close();
|
await n8n.ndv.close();
|
||||||
|
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME);
|
await n8n.canvas.addNodeWithSubItem(CODE_NODE_NAME, CODE_NODE_DISPLAY_NAME);
|
||||||
|
|
||||||
await n8n.ndv.getCodeEditor().fill("console.log('code node 2')");
|
await n8n.ndv.getCodeEditor().fill("console.log('code node 2')");
|
||||||
await n8n.ndv.close();
|
await n8n.ndv.close();
|
||||||
|
|
||||||
await n8n.canvas.openNode(CODE_NODE_NAME);
|
await n8n.canvas.openNode(CODE_NODE_DISPLAY_NAME);
|
||||||
|
|
||||||
await n8n.ndv.clickFloatingNode('Code1');
|
await n8n.ndv.clickFloatingNode(CODE_NODE_DISPLAY_NAME + '1');
|
||||||
await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('code node 2')");
|
await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('code node 2')");
|
||||||
|
|
||||||
await n8n.ndv.clickFloatingNode('Code');
|
await n8n.ndv.clickFloatingNode(CODE_NODE_DISPLAY_NAME);
|
||||||
await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('code node 1')");
|
await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('code node 1')");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,7 +116,10 @@ return []
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
await n8n.workflows.clickAddWorkflowButton();
|
await n8n.workflows.clickAddWorkflowButton();
|
||||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||||
await n8n.canvas.addNode(CODE_NODE_NAME);
|
await n8n.canvas.clickNodeCreatorPlusButton();
|
||||||
|
await n8n.canvas.fillNodeCreatorSearchBar(CODE_NODE_NAME);
|
||||||
|
await n8n.page.keyboard.press('Enter');
|
||||||
|
await n8n.canvas.clickNodeCreatorItemName(CODE_NODE_DISPLAY_NAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tab should exist if experiment selected and be selectable', async ({ n8n }) => {
|
test('tab should exist if experiment selected and be selectable', async ({ n8n }) => {
|
||||||
|
|||||||
@@ -1586,6 +1586,15 @@ function resolveResourceAndOperation(
|
|||||||
nodeParameters: INodeParameters,
|
nodeParameters: INodeParameters,
|
||||||
nodeTypeDescription: INodeTypeDescription,
|
nodeTypeDescription: INodeTypeDescription,
|
||||||
) {
|
) {
|
||||||
|
if (nodeTypeDescription.name === 'n8n-nodes-base.code') {
|
||||||
|
const language = nodeParameters.language as string;
|
||||||
|
const langProp = nodeTypeDescription.properties.find((p) => p.name === 'language');
|
||||||
|
if (langProp?.options && isINodePropertyOptionsList(langProp.options)) {
|
||||||
|
const found = langProp.options.find((o) => o.value === language);
|
||||||
|
if (found?.action) return { action: found.action };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resource = nodeParameters.resource as string;
|
const resource = nodeParameters.resource as string;
|
||||||
const operation = nodeParameters.operation as string;
|
const operation = nodeParameters.operation as string;
|
||||||
const nodeTypeOperation = nodeTypeDescription.properties.find(
|
const nodeTypeOperation = nodeTypeDescription.properties.find(
|
||||||
|
|||||||
@@ -5630,6 +5630,44 @@ describe('NodeHelpers', () => {
|
|||||||
// Assert
|
// Assert
|
||||||
expect(result).toBe('Create user in Test Node');
|
expect(result).toBe('Create user in Test Node');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['javaScript', 'Code in JavaScript'],
|
||||||
|
['python', 'Code in Python (Beta)'],
|
||||||
|
['pythonNative', 'Code in Python (Native) (Beta)'],
|
||||||
|
])(
|
||||||
|
'should return action-based name for Code node with %s language',
|
||||||
|
(language, expectedAction) => {
|
||||||
|
mockNodeTypeDescription.name = 'n8n-nodes-base.code';
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Language',
|
||||||
|
name: 'language',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'JavaScript',
|
||||||
|
value: 'javaScript',
|
||||||
|
action: 'Code in JavaScript',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Python (Beta)',
|
||||||
|
value: 'python',
|
||||||
|
action: 'Code in Python (Beta)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Python (Native) (Beta)',
|
||||||
|
value: 'pythonNative',
|
||||||
|
action: 'Code in Python (Native) (Beta)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'javaScript',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = makeNodeName({ language }, mockNodeTypeDescription);
|
||||||
|
expect(result).toBe(expectedAction);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
describe('isTool', () => {
|
describe('isTool', () => {
|
||||||
it('should return true for a node with AiTool output', () => {
|
it('should return true for a node with AiTool output', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user