Skip to content

Commit

Permalink
feat(editor): Add node context menu (#7620)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsmr committed Nov 20, 2023
1 parent 4dbae0e commit 8d12c1a
Show file tree
Hide file tree
Showing 46 changed files with 1,611 additions and 372 deletions.
23 changes: 6 additions & 17 deletions cypress/e2e/10-undo-redo.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,10 @@ describe('Undo/Redo', () => {
.should('have.css', 'top', '220px');
});

it('should undo/redo deleting node using delete button', () => {
it('should undo/redo deleting node using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodeByName(CODE_NODE_NAME)
.find('[data-test-id=delete-node-button]')
.click({ force: true });
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
WorkflowPage.getters.nodeConnections().should('have.length', 0);
WorkflowPage.actions.hitUndo();
Expand Down Expand Up @@ -151,7 +148,7 @@ describe('Undo/Redo', () => {
.should('have.css', 'top', '320px');
});

it('should undo/redo deleting a connection by pressing delete button', () => {
it('should undo/redo deleting a connection using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters.nodeConnections().realHover();
Expand All @@ -177,14 +174,10 @@ describe('Undo/Redo', () => {
WorkflowPage.getters.nodeConnections().should('have.length', 0);
});

it('should undo/redo disabling a node using disable button', () => {
it('should undo/redo disabling a node using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodes()
.last()
.find('[data-test-id="disable-node-button"]')
.click({ force: true });
WorkflowPage.actions.disableNode(CODE_NODE_NAME);
WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.disabledNodes().should('have.length', 0);
Expand Down Expand Up @@ -252,11 +245,7 @@ describe('Undo/Redo', () => {
it('should undo/redo duplicating a node', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodes()
.last()
.find('[data-test-id="duplicate-node-button"]')
.click({ force: true });
WorkflowPage.actions.duplicateNode(CODE_NODE_NAME);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.canvasNodes().should('have.length', 2);
WorkflowPage.actions.hitRedo();
Expand Down
27 changes: 17 additions & 10 deletions cypress/e2e/12-canvas-actions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('Canvas Actions', () => {
.canvasNodes()
.last()
.should('have.css', 'left', '860px')
.should('have.css', 'top', '220px')
.should('have.css', 'top', '220px');
});

it('should delete connections by pressing the delete button', () => {
Expand Down Expand Up @@ -163,21 +163,29 @@ describe('Canvas Actions', () => {
.find('[data-test-id="execute-node-button"]')
.click({ force: true });
WorkflowPage.getters.successToast().should('contain', 'Node executed successfully');
WorkflowPage.actions.executeNode(CODE_NODE_NAME);
WorkflowPage.getters.successToast().should('contain', 'Node executed successfully');
});

it('should copy selected nodes', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.selectAll();

WorkflowPage.actions.hitCopy();
WorkflowPage.getters.successToast().should('contain', 'Copied!');

WorkflowPage.actions.copyNode(CODE_NODE_NAME);
WorkflowPage.getters.successToast().should('contain', 'Copied!');
});

it('should select all nodes', () => {
it('should select/deselect all nodes', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.selectAll();
WorkflowPage.getters.selectedNodes().should('have.length', 2);
WorkflowPage.actions.deselectAll();
WorkflowPage.getters.selectedNodes().should('have.length', 0);
});

it('should select nodes using arrow keys', () => {
Expand Down Expand Up @@ -205,22 +213,21 @@ describe('Canvas Actions', () => {
WorkflowPage.getters
.canvasNodes()
.last()
.findChildByTestId('disable-node-button').as('disableNodeButton');
cy.drag('@disableNodeButton', [200, 200]);
.findChildByTestId('execute-node-button')
.as('executeNodeButton');
cy.drag('@executeNodeButton', [200, 200]);
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
});

it('should not break lasso selection with multiple clicks on node action buttons', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
WorkflowPage.getters
.canvasNodes()
.last().as('lastNode');
cy.get('@lastNode').findChildByTestId('disable-node-button').as('disableNodeButton');
WorkflowPage.getters.canvasNodes().last().as('lastNode');
cy.get('@lastNode').findChildByTestId('execute-node-button').as('executeNodeButton');
for (let i = 0; i < 20; i++) {
cy.get('@lastNode').realHover();
cy.get('@disableNodeButton').should('be.visible');
cy.get('@disableNodeButton').realTouch();
cy.get('@executeNodeButton').should('be.visible');
cy.get('@executeNodeButton').realTouch();
cy.getByTestId('execute-workflow-button').realHover();
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
}
Expand Down
100 changes: 63 additions & 37 deletions cypress/e2e/12-canvas.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ZOOM_OUT_X2_FACTOR = 0.64;
const PINCH_ZOOM_IN_FACTOR = 1.05702;
const PINCH_ZOOM_OUT_FACTOR = 0.946058;
const RENAME_NODE_NAME = 'Something else';
const RENAME_NODE_NAME2 = 'Something different';

describe('Canvas Node Manipulation and Navigation', () => {
beforeEach(() => {
Expand Down Expand Up @@ -129,13 +130,10 @@ describe('Canvas Node Manipulation and Navigation', () => {
cy.get('.jtk-connector').should('have.length', 4);
});

it('should delete node using node action button', () => {
it('should delete node using context menu', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodeByName(CODE_NODE_NAME)
.find('[data-test-id=delete-node-button]')
.click({ force: true });
WorkflowPage.actions.deleteNodeFromContextMenu(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodes().should('have.length', 1);
WorkflowPage.getters.nodeConnections().should('have.length', 0);
});
Expand All @@ -162,13 +160,38 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.nodeConnections().should('have.length', 1);
});

it('should delete multiple nodes', () => {
it('should delete multiple nodes (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAll();
cy.get('body').type('{backspace}');
WorkflowPage.getters.canvasNodes().should('have.length', 0);

WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAllFromContextMenu();
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('delete');
WorkflowPage.getters.canvasNodes().should('have.length', 0);
});

it('should delete multiple nodes (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAll();
cy.get('body').type('{backspace}');
WorkflowPage.getters.canvasNodes().should('have.length', 0);

WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAllFromContextMenu();
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('delete');
WorkflowPage.getters.canvasNodes().should('have.length', 0);
});

it('should move node', () => {
Expand Down Expand Up @@ -272,39 +295,42 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodes().last().should('be.visible');
});

it('should disable node by pressing the disable button', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodes()
.last()
.find('[data-test-id="disable-node-button"]')
.click({ force: true });
WorkflowPage.getters.disabledNodes().should('have.length', 1);
});

it('should disable node using keyboard shortcut', () => {
it('should disable node (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodes().last().click();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 1);

WorkflowPage.actions.disableNode(CODE_NODE_NAME);
WorkflowPage.getters.disabledNodes().should('have.length', 0);
});

it('should disable multiple nodes', () => {
it('should disable multiple nodes (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.get('body').type('{esc}');
cy.get('body').type('{esc}');
WorkflowPage.actions.selectAll();

// Keyboard shortcut
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 2);
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 0);

// Context menu
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 2);
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 0);
});

it('should rename node using keyboard shortcut', () => {
it('should rename node (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodes().last().click();
Expand All @@ -313,19 +339,25 @@ describe('Canvas Node Manipulation and Navigation', () => {
cy.get('body').type(RENAME_NODE_NAME);
cy.get('body').type('{enter}');
WorkflowPage.getters.canvasNodeByName(RENAME_NODE_NAME).should('exist');

WorkflowPage.actions.renameNode(RENAME_NODE_NAME);
cy.get('.rename-prompt').should('be.visible');
cy.get('body').type(RENAME_NODE_NAME2);
cy.get('body').type('{enter}');
WorkflowPage.getters.canvasNodeByName(RENAME_NODE_NAME2).should('exist');
});

it('should duplicate node', () => {
it('should duplicate nodes (context menu or shortcut)', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters
.canvasNodes()
.last()
.find('[data-test-id="duplicate-node-button"]')
.click({ force: true });
WorkflowPage.actions.duplicateNode(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodes().should('have.length', 3);
WorkflowPage.getters.nodeConnections().should('have.length', 1);

WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitDuplicateNodeShortcut();
WorkflowPage.getters.canvasNodes().should('have.length', 5);
});

// ADO-1240: Connections would get deleted after activating and deactivating NodeView
Expand Down Expand Up @@ -365,7 +397,7 @@ describe('Canvas Node Manipulation and Navigation', () => {

WorkflowPage.getters.canvasNodes().should('have.have.length', 2);

WorkflowPage.actions.openNode('n8n');
WorkflowPage.actions.openNodeFromContextMenu('n8n');
cy.get('[class*=hasIssues]').should('have.length', 1);
NDVDialog.actions.close();
});
Expand All @@ -392,15 +424,9 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.executeWorkflow();
cy.contains('Unrecognized node type').should('be.visible');

WorkflowPage.getters
.canvasNodeByName(`${unknownNodeName} 1`)
.find('[data-test-id=delete-node-button]')
.click({ force: true });

WorkflowPage.getters
.canvasNodeByName(`${unknownNodeName} 2`)
.find('[data-test-id=delete-node-button]')
.click({ force: true });
WorkflowPage.actions.deselectAll();
WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 1`);
WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 2`);

WorkflowPage.actions.executeWorkflow();

Expand Down
34 changes: 31 additions & 3 deletions cypress/e2e/13-pinning.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ describe('Data pinning', () => {

it('Should be duplicating pin data when duplicating node', () => {
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
workflowPage.actions.addNodeToCanvas('Edit Fields', true, true);
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
ndv.getters.container().should('be.visible');
ndv.getters.pinDataButton().should('not.exist');
ndv.getters.editPinnedDataButton().should('be.visible');

ndv.actions.setPinnedData([{ test: 1 }]);
ndv.actions.close();

workflowPage.actions.duplicateNode(workflowPage.getters.canvasNodes().last());
workflowPage.actions.duplicateNode(EDIT_FIELDS_SET_NODE_NAME);

workflowPage.actions.saveWorkflowOnButtonClick();

Expand All @@ -88,9 +88,37 @@ describe('Data pinning', () => {
ndv.getters.outputTbodyCell(1, 0).should('include.text', 1);
});

it('Should be able to pin data from canvas (context menu or shortcut)', () => {
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
workflowPage.actions.openContextMenu(EDIT_FIELDS_SET_NODE_NAME, 'overflow-button');
workflowPage.getters
.contextMenuAction('toggle_pin')
.parent()
.should('have.class', 'is-disabled');

// Unpin using context menu
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
ndv.actions.setPinnedData([{ test: 1 }]);
ndv.actions.close();
workflowPage.actions.pinNode(EDIT_FIELDS_SET_NODE_NAME);
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
ndv.getters.nodeOutputHint().should('exist');
ndv.actions.close();

// Unpin using shortcut
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
ndv.actions.setPinnedData([{ test: 1 }]);
ndv.actions.close();
workflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click();
workflowPage.actions.hitPinNodeShortcut();
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
ndv.getters.nodeOutputHint().should('exist');
});

it('Should show an error when maximum pin data size is exceeded', () => {
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
workflowPage.actions.addNodeToCanvas('Edit Fields', true, true);
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
ndv.getters.container().should('be.visible');
ndv.getters.pinDataButton().should('not.exist');
ndv.getters.editPinnedDataButton().should('be.visible');
Expand Down
5 changes: 5 additions & 0 deletions cypress/e2e/25-stickies.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ describe('Canvas Actions', () => {
workflowPage.getters.addStickyButton().should('not.be.visible');

addDefaultSticky();
workflowPage.actions.deselectAll();
workflowPage.actions.addStickyFromContextMenu();
workflowPage.actions.hitAddStickyShortcut();

workflowPage.getters.stickies().should('have.length', 3);
workflowPage.getters
.stickies()
.eq(0)
Expand Down
1 change: 1 addition & 0 deletions cypress/pages/ndv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class NDV extends BasePage {
editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'),
pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller'),
runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'),
nodeOutputHint: () => cy.getByTestId('ndv-output-run-node-hint'),
savePinnedDataButton: () =>
this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
Expand Down

0 comments on commit 8d12c1a

Please sign in to comment.