From 24dfc95974323e0a1f48f29082c4af6832d1addb Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Fri, 3 Nov 2023 16:22:37 +0200 Subject: [PATCH] feat(editor): Improve performance by importing routes dynamically and add route guards (no-changelog) (#7567) **Before:** image **After:** image --- cypress/e2e/10-undo-redo.cy.ts | 97 +++++----- cypress/e2e/12-canvas.cy.ts | 1 + cypress/e2e/17-sharing.cy.ts | 1 + cypress/e2e/25-stickies.cy.ts | 28 +-- cypress/e2e/27-opt-in-trial-banner.cy.ts | 8 +- cypress/e2e/7-workflow-actions.cy.ts | 1 + cypress/pages/ndv.ts | 20 ++- cypress/pages/workflow.ts | 7 + cypress/support/commands.ts | 4 +- cypress/support/e2e.ts | 1 + cypress/support/index.ts | 2 +- packages/design-system/src/css/reset.scss | 68 +++---- packages/editor-ui/src/App.vue | 154 ++++------------ packages/editor-ui/src/Interface.ts | 2 + .../editor-ui/src/__tests__/router.test.ts | 55 +++++- .../__tests__/server/endpoints/settings.ts | 11 +- .../src/components/layouts/PageViewLayout.vue | 1 - packages/editor-ui/src/main.ts | 6 - packages/editor-ui/src/router.ts | 170 ++++++++++++++---- .../editor-ui/src/stores/settings.store.ts | 33 +++- packages/editor-ui/src/stores/users.store.ts | 11 ++ 21 files changed, 387 insertions(+), 294 deletions(-) diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index d986fe6577..35a43c5c84 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -44,7 +44,7 @@ describe('Undo/Redo', () => { WorkflowPage.getters .canvasNodeByName('Code') .should('have.css', 'left', '860px') - .should('have.css', 'top', '220px') + .should('have.css', 'top', '220px'); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.canvasNodes().should('have.have.length', 2); @@ -62,7 +62,7 @@ describe('Undo/Redo', () => { WorkflowPage.getters .canvasNodeByName('Code') .should('have.css', 'left', '860px') - .should('have.css', 'top', '220px') + .should('have.css', 'top', '220px'); }); it('should undo/redo deleting node using delete button', () => { @@ -137,18 +137,18 @@ describe('Undo/Redo', () => { WorkflowPage.getters .canvasNodeByName('Code') .should('have.css', 'left', '740px') - .should('have.css', 'top', '320px') + .should('have.css', 'top', '320px'); WorkflowPage.actions.hitUndo(); WorkflowPage.getters .canvasNodeByName('Code') .should('have.css', 'left', '640px') - .should('have.css', 'top', '220px') + .should('have.css', 'top', '220px'); WorkflowPage.actions.hitRedo(); WorkflowPage.getters .canvasNodeByName('Code') .should('have.css', 'left', '740px') - .should('have.css', 'top', '320px') + .should('have.css', 'top', '320px'); }); it('should undo/redo deleting a connection by pressing delete button', () => { @@ -276,9 +276,6 @@ describe('Undo/Redo', () => { }); it('should undo/redo multiple steps', () => { - const initialPosition = {left: '420px', top: '220px'}; - const movedPosition = {left: '540px', top: '360px'}; - WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); // WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); @@ -289,48 +286,54 @@ describe('Undo/Redo', () => { // Disable last node WorkflowPage.getters.canvasNodes().last().click(); WorkflowPage.actions.hitDisableNodeShortcut(); + // Move first one - WorkflowPage.getters.canvasNodes() - .first() - .should('have.css', 'left', initialPosition.left) - .should('have.css', 'top', initialPosition.top) + WorkflowPage.actions + .getNodePosition(WorkflowPage.getters.canvasNodes().first()) + .then((initialPosition) => { + WorkflowPage.getters.canvasNodes().first().click(); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { + clickToFinish: true, + }); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left + 120}px`) + .should('have.css', 'top', `${initialPosition.top + 140}px`); - WorkflowPage.getters.canvasNodes().first().click(); - cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true }); - WorkflowPage.getters.canvasNodes() - .first() - .should('have.css', 'left', movedPosition.left) - .should('have.css', 'top', movedPosition.top) - // Delete the set node - WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click().click(); - cy.get('body').type('{backspace}'); + // Delete the set node + WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click().click(); + cy.get('body').type('{backspace}'); - // First undo: Should return deleted node - WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.canvasNodes().should('have.length', 4); - WorkflowPage.getters.nodeConnections().should('have.length', 3); - // Second undo: Should move first node to it's original position - WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.canvasNodes() - .first() - .should('have.css', 'left', initialPosition.left) - .should('have.css', 'top', initialPosition.top) - // Third undo: Should enable last node - WorkflowPage.actions.hitUndo(); - WorkflowPage.getters.disabledNodes().should('have.length', 0); + // First undo: Should return deleted node + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.canvasNodes().should('have.length', 4); + WorkflowPage.getters.nodeConnections().should('have.length', 3); + // Second undo: Should move first node to it's original position + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left}px`) + .should('have.css', 'top', `${initialPosition.top}px`); + // Third undo: Should enable last node + WorkflowPage.actions.hitUndo(); + WorkflowPage.getters.disabledNodes().should('have.length', 0); - // First redo: Should disable last node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.disabledNodes().should('have.length', 1); - // Second redo: Should move the first node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes() - .first() - .should('have.css', 'left', movedPosition.left) - .should('have.css', 'top', movedPosition.top) - // Third redo: Should delete the Set node - WorkflowPage.actions.hitRedo(); - WorkflowPage.getters.canvasNodes().should('have.length', 3); - WorkflowPage.getters.nodeConnections().should('have.length', 2); + // First redo: Should disable last node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters.disabledNodes().should('have.length', 1); + // Second redo: Should move the first node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters + .canvasNodes() + .first() + .should('have.css', 'left', `${initialPosition.left + 120}px`) + .should('have.css', 'top', `${initialPosition.top + 140}px`); + // Third redo: Should delete the Set node + WorkflowPage.actions.hitRedo(); + WorkflowPage.getters.canvasNodes().should('have.length', 3); + WorkflowPage.getters.nodeConnections().should('have.length', 2); + }); }); }); diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 385470ac51..8170b9caa1 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -176,6 +176,7 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.zoomToFit(); + cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true }); WorkflowPage.getters .canvasNodes() diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index 40454dde3f..49b8122593 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -59,6 +59,7 @@ describe('Sharing', { disableAutoLogin: true }, () => { cy.visit(workflowsPage.url); workflowsPage.getters.createWorkflowButton().click(); cy.createFixtureWorkflow('Test_workflow_1.json', 'Workflow W2'); + workflowPage.actions.saveWorkflowOnButtonClick(); cy.url().then((url) => { workflowW2Url = url; }); diff --git a/cypress/e2e/25-stickies.cy.ts b/cypress/e2e/25-stickies.cy.ts index ac94f882dd..e5ef8d6eca 100644 --- a/cypress/e2e/25-stickies.cy.ts +++ b/cypress/e2e/25-stickies.cy.ts @@ -84,8 +84,11 @@ describe('Canvas Actions', () => { moveSticky({ top: 200, left: 200 }); - dragRightEdge({ left: 200, top: 200, height: 160, width: 240 }, 100); - dragRightEdge({ left: 200, top: 200, height: 160, width: 240 }, -50); + cy.drag('[data-test-id="sticky"] [data-dir="right"]', [100, 100]); + checkStickiesStyle(100, 20, 160, 346); + + cy.drag('[data-test-id="sticky"] [data-dir="right"]', [-50, -50]); + checkStickiesStyle(100, 20, 160, 302); }); it('expands/shrinks sticky from the left edge', () => { @@ -205,27 +208,6 @@ type Position = { left: number; }; -type BoundingBox = { - height: number; - width: number; - top: number; - left: number; -}; - -function dragRightEdge(curr: BoundingBox, move: number) { - workflowPage.getters - .stickies() - .first() - .then(($el) => { - const { left, top, height, width } = curr; - cy.drag(`[data-test-id="sticky"] [data-dir="right"]`, [left + width + move, 0], { - abs: true, - }); - stickyShouldBePositionedCorrectly({ top, left }); - stickyShouldHaveCorrectSize([height, width * 1.5 + move]); - }); -} - function shouldHaveOneSticky() { workflowPage.getters.stickies().should('have.length', 1); } diff --git a/cypress/e2e/27-opt-in-trial-banner.cy.ts b/cypress/e2e/27-opt-in-trial-banner.cy.ts index 0f66236bb0..8b60981c99 100644 --- a/cypress/e2e/27-opt-in-trial-banner.cy.ts +++ b/cypress/e2e/27-opt-in-trial-banner.cy.ts @@ -14,6 +14,10 @@ describe('BannerStack', { disableAutoLogin: true }, () => { }); it('should render trial banner for opt-in cloud user', () => { + cy.intercept('GET', '/rest/admin/cloud-plan', { + body: planData, + }).as('getPlanData'); + cy.intercept('GET', '/rest/settings', (req) => { req.on('response', (res) => { res.send({ @@ -22,10 +26,6 @@ describe('BannerStack', { disableAutoLogin: true }, () => { }); }).as('loadSettings'); - cy.intercept('GET', '/rest/admin/cloud-plan', { - body: planData, - }).as('getPlanData'); - cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); cy.visit(workflowPage.url); diff --git a/cypress/e2e/7-workflow-actions.cy.ts b/cypress/e2e/7-workflow-actions.cy.ts index 7a0eda770c..65795cf949 100644 --- a/cypress/e2e/7-workflow-actions.cy.ts +++ b/cypress/e2e/7-workflow-actions.cy.ts @@ -100,6 +100,7 @@ describe('Workflow Actions', () => { cy.get('body').type(META_KEY, { release: false }).type('s'); cy.get('body').type(META_KEY, { release: false }).type('s'); cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(0)); + cy.waitForLoad(); WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); cy.get('body').type(META_KEY, { release: false }).type('s'); cy.wait('@saveWorkflow'); diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index ea0f821ebe..8fe9c19f9e 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -72,15 +72,17 @@ export class NDV extends BasePage { this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'), resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'), resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'), - resourceMapperRemoveFieldButton: (fieldName: string) => cy.getByTestId(`remove-field-button-${fieldName}`), - resourceMapperColumnsOptionsButton: () => cy.getByTestId('columns-parameter-input-options-container'), + resourceMapperRemoveFieldButton: (fieldName: string) => + cy.getByTestId(`remove-field-button-${fieldName}`), + resourceMapperColumnsOptionsButton: () => + cy.getByTestId('columns-parameter-input-options-container'), resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'), sqlEditorContainer: () => cy.getByTestId('sql-editor-container'), }; actions = { pinData: () => { - this.getters.pinDataButton().click(); + this.getters.pinDataButton().click({ force: true }); }, editPinnedData: () => { this.getters.editPinnedDataButton().click(); @@ -119,7 +121,7 @@ export class NDV extends BasePage { typeIntoParameterInput: ( parameterName: string, content: string, - opts?: { parseSpecialCharSequences: boolean, delay?: number }, + opts?: { parseSpecialCharSequences: boolean; delay?: number }, ) => { this.getters.parameterInput(parameterName).type(content, opts); }, @@ -204,7 +206,15 @@ export class NDV extends BasePage { getVisiblePopper().find('li').last().click(); }, - setInvalidExpression: ({ fieldName, invalidExpression, delay }: { fieldName: string, invalidExpression?: string, delay?: number }) => { + setInvalidExpression: ({ + fieldName, + invalidExpression, + delay, + }: { + fieldName: string; + invalidExpression?: string; + delay?: number; + }) => { this.actions.typeIntoParameterInput(fieldName, '='); this.actions.typeIntoParameterInput(fieldName, invalidExpression ?? "{{ $('unknown')", { parseSpecialCharSequences: false, diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 8b9bc04cf6..028d31264d 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -214,6 +214,7 @@ export class WorkflowPage extends BasePage { this.getters.saveButton().should('contain', 'Save'); this.getters.saveButton().click(); this.getters.saveButton().should('contain', 'Saved'); + cy.url().should('not.have.string', '/new'); }, saveWorkflowUsingKeyboardShortcut: () => { cy.intercept('POST', '/rest/workflows').as('createWorkflow'); @@ -340,5 +341,11 @@ export class WorkflowPage extends BasePage { cy.getByTestId('node-view-wrapper').trigger('mouseup', to[0], to[1], { force: true }); cy.get('#select-box').should('not.be.visible'); }, + getNodePosition: (node: Cypress.Chainable>) => { + return node.then(($el) => ({ + left: +$el[0].style.left.replace('px', ''), + top: +$el[0].style.top.replace('px', ''), + })); + }, }; } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 6046c23287..8bd9577a97 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -16,8 +16,8 @@ Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => { cy.waitForLoad(false); workflowPage.actions.setWorkflowName(workflowName); - workflowPage.getters.saveButton().should('contain', 'Saved'); + workflowPage.actions.zoomToFit(); }); Cypress.Commands.add( @@ -33,7 +33,7 @@ Cypress.Commands.add('waitForLoad', (waitForIntercepts = true) => { // we can't set them up here because at this point it would be too late // and the requests would already have been made if (waitForIntercepts) { - cy.wait(['@loadSettings']); + cy.wait(['@loadSettings', '@loadNodeTypes']); } cy.getByTestId('node-view-loader', { timeout: 20000 }).should('not.exist'); cy.get('.el-loading-mask', { timeout: 20000 }).should('not.exist'); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index a750918c6d..de5a39d549 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -18,6 +18,7 @@ beforeEach(() => { } cy.intercept('GET', '/rest/settings').as('loadSettings'); + cy.intercept('GET', '/types/nodes.json').as('loadNodeTypes'); // Always intercept the request to test credentials and return a success cy.intercept('POST', '/rest/credentials/test', { diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 3390c14d82..bce17e3a2c 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -34,7 +34,7 @@ declare global { drag( selector: string | Cypress.Chainable>, target: [number, number], - options?: { abs?: boolean; index?: number; realMouse?: boolean }, + options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean }, ): void; draganddrop(draggableSelector: string, droppableSelector: string): void; shouldNotHaveConsoleErrors(): void; diff --git a/packages/design-system/src/css/reset.scss b/packages/design-system/src/css/reset.scss index a0a442c027..0a7323c8de 100644 --- a/packages/design-system/src/css/reset.scss +++ b/packages/design-system/src/css/reset.scss @@ -7,40 +7,6 @@ box-sizing: border-box; } -html { - height: 100%; - width: 100%; -} - -body { - height: 100%; - width: 100%; - overscroll-behavior: none; - line-height: 1; - font-size: var(--font-size-m); - font-weight: var(--font-weight-regular); - color: var(--color-text-dark); - background-color: var(--color-background-xlight); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body, -button, -input { - font-family: var(--font-family); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -input { - font-weight: var(--font-weight-regular); -} - -button { - font-weight: var(--font-weight-bold); -} - html, body, div, @@ -117,6 +83,40 @@ video { background: transparent; } +html { + height: 100%; + width: 100%; +} + +body { + height: 100%; + width: 100%; + overscroll-behavior: none; + line-height: 1; + font-size: var(--font-size-m); + font-weight: var(--font-weight-regular); + color: var(--color-text-dark); + background-color: var(--color-background-xlight); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body, +button, +input { + font-family: var(--font-family); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +input { + font-weight: var(--font-weight-regular); +} + +button { + font-weight: var(--font-weight-bold); +} + article, aside, details, diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index fa13036268..d818ab6775 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -15,7 +15,7 @@ -