feat(editor): Add Python to Code actions (#18668)

This commit is contained in:
Iván Ovejero
2025-08-26 14:29:50 +02:00
committed by GitHub
parent b73f2393b4
commit 38f25d74eb
16 changed files with 185 additions and 59 deletions

View File

@@ -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 SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
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 EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields';
export const LOOP_OVER_ITEMS_NODE_NAME = 'Loop Over Items';

View File

@@ -1,10 +1,10 @@
import { getCanvasNodes } from '../composables/workflow';
import {
SCHEDULE_TRIGGER_NODE_NAME,
CODE_NODE_NAME,
SET_NODE_NAME,
MANUAL_TRIGGER_NODE_NAME,
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
CODE_NODE_DISPLAY_NAME,
} from '../constants';
import { NDV } from '../pages/ndv';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
@@ -19,9 +19,9 @@ describe('Undo/Redo', () => {
it('should undo/redo deleting node using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_DISPLAY_NAME, {
method: 'right-click',
anchor: 'topLeft',
});
@@ -37,8 +37,8 @@ describe('Undo/Redo', () => {
it('should undo/redo deleting node using keyboard shortcut', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
cy.get('body').type('{backspace}');
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
WorkflowPage.getters.nodeConnections().should('have.length', 0);
@@ -52,9 +52,9 @@ describe('Undo/Redo', () => {
it('should undo/redo deleting node between two connected nodes', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
WorkflowPage.getters.canvasNodeByName(CODE_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.zoomToFit();
cy.get('body').type('{backspace}');
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
@@ -69,7 +69,7 @@ describe('Undo/Redo', () => {
it('should undo/redo deleting whole workflow', () => {
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}');
WorkflowPage.actions.hitDeleteAllNodes();
@@ -85,7 +85,7 @@ describe('Undo/Redo', () => {
it('should undo/redo moving nodes', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.zoomToFit();
@@ -131,8 +131,8 @@ describe('Undo/Redo', () => {
it('should undo/redo deleting a connection using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.deleteNodeBetweenNodes(SCHEDULE_TRIGGER_NODE_NAME, CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.deleteNodeBetweenNodes(SCHEDULE_TRIGGER_NODE_NAME, CODE_NODE_DISPLAY_NAME);
WorkflowPage.getters.nodeConnections().should('have.length', 0);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.nodeConnections().should('have.length', 1);
@@ -142,8 +142,8 @@ describe('Undo/Redo', () => {
it('should undo/redo disabling a node using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.disableNode(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.disableNode(CODE_NODE_DISPLAY_NAME);
WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.disabledNodes().should('have.length', 0);
@@ -153,7 +153,7 @@ describe('Undo/Redo', () => {
it('should undo/redo disabling a node using keyboard shortcut', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.getters.canvasNodes().last().click();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 1);
@@ -165,7 +165,7 @@ describe('Undo/Redo', () => {
it('should undo/redo disabling multiple nodes', () => {
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}');
WorkflowPage.actions.hitSelectAll();
@@ -179,8 +179,8 @@ describe('Undo/Redo', () => {
it('should undo/redo duplicating a node', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.duplicateNode(CODE_NODE_NAME);
WorkflowPage.actions.addCodeNodeToCanvas();
WorkflowPage.actions.duplicateNode(CODE_NODE_DISPLAY_NAME);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.canvasNodes().should('have.length', 2);
WorkflowPage.actions.hitRedo();

View File

@@ -6,6 +6,7 @@ import {
EDIT_FIELDS_SET_NODE_NAME,
SWITCH_NODE_NAME,
MERGE_NODE_NAME,
CODE_NODE_DISPLAY_NAME,
} from './../constants';
import {
clickContextMenuAction,
@@ -165,7 +166,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME, {
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_DISPLAY_NAME, {
method: 'right-click',
anchor: 'topLeft',
});
@@ -176,7 +177,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
it('should delete node using keyboard shortcut', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_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}');
WorkflowPage.getters.canvasNodes().should('have.length', 1);
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.getters.canvasNodes().should('have.length', 3);
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();
cy.get('body').type('{backspace}');
WorkflowPage.getters.canvasNodes().should('have.length', 2);
@@ -318,7 +319,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.hitDisableNodeShortcut();
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);
});
@@ -394,7 +395,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
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.nodeConnections().should('have.length', 1);

View File

@@ -390,7 +390,7 @@ describe('NDV', () => {
});
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}');
cy.fixture('Dummy_javascript.txt').then((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', () => {
// Code (JavaScript)
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
workflowPage.actions.addInitialCodeNodeToCanvas({ keepNdvOpen: true });
ndv.actions.openCodeEditorFullscreen();
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
@@ -725,8 +725,7 @@ describe('NDV', () => {
});
it('should properly show node execution indicator', () => {
workflowPage.actions.addInitialNodeToCanvas('Code');
workflowPage.actions.openNode('Code');
workflowPage.actions.addInitialCodeNodeToCanvas();
// Should not show run info before execution
ndv.getters.nodeRunSuccessIndicator().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', () => {
workflowPage.actions.addInitialNodeToCanvas('Code');
workflowPage.actions.openNode('Code');
workflowPage.actions.addInitialCodeNodeToCanvas();
ndv.actions.typeIntoParameterInput('jsCode', 'testets');
ndv.getters.backToCanvas().click();
workflowPage.actions.executeWorkflow();

View File

@@ -1,7 +1,7 @@
import { BasePage } from './base';
import { NodeCreator } from './features/node-creator';
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 { getVisibleSelect } from '../utils';
import { getUniqueWorkflowName } from '../utils/workflowUtils';
@@ -179,6 +179,12 @@ export class WorkflowPage extends BasePage {
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
});
},
addInitialCodeNodeToCanvas(opts: { keepNdvOpen: boolean } = { keepNdvOpen: false }) {
this.addInitialNodeToCanvas(CODE_NODE_NAME, {
action: CODE_NODE_ACTION,
keepNdvOpen: opts.keepNdvOpen,
});
},
addInitialNodeToCanvas: (
nodeDisplayName: string,
opts?: { keepNdvOpen?: boolean; action?: string; isTrigger?: boolean },
@@ -202,6 +208,9 @@ export class WorkflowPage extends BasePage {
cy.get('body').type('{esc}');
}
},
addCodeNodeToCanvas(plusButtonClick = true, preventNdvClose?: boolean) {
this.addNodeToCanvas(CODE_NODE_NAME, plusButtonClick, preventNdvClose, CODE_NODE_ACTION);
},
addNodeToCanvas: (
nodeDisplayName: string,
plusButtonClick = true,