diff --git a/.github/scripts/check-tests.mjs b/.github/scripts/check-tests.mjs index 3dc119896f..6c0f37b2aa 100644 --- a/.github/scripts/check-tests.mjs +++ b/.github/scripts/check-tests.mjs @@ -2,90 +2,100 @@ import fs from 'fs'; import path from 'path'; import util from 'util'; import { exec } from 'child_process'; -import { glob } from "glob"; +import { glob } from 'glob'; import ts from 'typescript'; const readFileAsync = util.promisify(fs.readFile); const execAsync = util.promisify(exec); const filterAsync = async (asyncPredicate, arr) => { - const filterResults = await Promise.all(arr.map(async item => ({ - item, - shouldKeep: await asyncPredicate(item) - }))); + const filterResults = await Promise.all( + arr.map(async (item) => ({ + item, + shouldKeep: await asyncPredicate(item), + })), + ); - return filterResults.filter(({shouldKeep}) => shouldKeep).map(({item}) => item); -} + return filterResults.filter(({ shouldKeep }) => shouldKeep).map(({ item }) => item); +}; const isAbstractClass = (node) => { if (ts.isClassDeclaration(node)) { - return node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword) || false; + return ( + node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword) || false + ); } return false; -} - -const isAbstractMethod = (node) => { - return ts.isMethodDeclaration(node) && Boolean(node.modifiers?.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword)); -} +}; +const isAbstractMethod = (node) => { + return ( + ts.isMethodDeclaration(node) && + Boolean(node.modifiers?.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword)) + ); +}; // Function to check if a file has a function declaration, function expression, object method or class -const hasFunctionOrClass = async filePath => { +const hasFunctionOrClass = async (filePath) => { const fileContent = await readFileAsync(filePath, 'utf-8'); const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true); let hasFunctionOrClass = false; - const visit = node => { + const visit = (node) => { if ( - ts.isFunctionDeclaration(node) - || ts.isFunctionExpression(node) - || ts.isArrowFunction(node) - || (ts.isMethodDeclaration(node) && !isAbstractMethod(node)) - || (ts.isClassDeclaration(node) && !isAbstractClass(node)) + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || + (ts.isMethodDeclaration(node) && !isAbstractMethod(node)) || + (ts.isClassDeclaration(node) && !isAbstractClass(node)) ) { hasFunctionOrClass = true; } node.forEachChild(visit); - } + }; visit(sourceFile); return hasFunctionOrClass; -} +}; const main = async () => { + // Run a git command to get a list of all changed files in the branch (branch has to be up to date with master) + const changedFiles = await execAsync( + 'git diff --name-only --diff-filter=d origin/master..HEAD', + ).then(({ stdout }) => stdout.trim().split('\n').filter(Boolean)); - // Run a git command to get a list of all changed files in the branch (branch has to be up to date with master) - const changedFiles = await execAsync('git diff --name-only --diff-filter=d origin/master..HEAD') - .then(({stdout}) => stdout.trim().split('\n').filter(Boolean)); - - // Get all .spec.ts and .test.ts files from the packages + // Get all .spec.ts and .test.ts files from the packages const specAndTestTsFiles = await glob('packages/*/**/{test,__tests__}/**/*.{spec,test}.ts'); - const specAndTestTsFilesNames = specAndTestTsFiles.map(file => path.parse(file).name.replace(/\.(test|spec)/, '')); - - // Filter out the .ts and .vue files from the changed files - const changedVueFiles = changedFiles.filter(file => file.endsWith('.vue')); - // .ts files with any kind of function declaration or class and not in any of the test folders - const changedTsFilesWithFunction = await filterAsync( - async filePath => - filePath.endsWith('.ts') && - !(await glob('packages/*/**/{test,__tests__}/*.ts')).includes(filePath) && - await hasFunctionOrClass(filePath), - changedFiles + const specAndTestTsFilesNames = specAndTestTsFiles.map((file) => + path.parse(file).name.replace(/\.(test|spec)/, ''), ); - // For each .ts or .vue file, check if there's a corresponding .test.ts or .spec.ts file in the repository - const missingTests = changedVueFiles.concat(changedTsFilesWithFunction).reduce((filesList, nextFile) => { - const fileName = path.parse(nextFile).name; + // Filter out the .ts and .vue files from the changed files + const changedVueFiles = changedFiles.filter((file) => file.endsWith('.vue')); + // .ts files with any kind of function declaration or class and not in any of the test folders + const changedTsFilesWithFunction = await filterAsync( + async (filePath) => + filePath.endsWith('.ts') && + !(await glob('packages/*/**/{test,__tests__}/*.ts')).includes(filePath) && + (await hasFunctionOrClass(filePath)), + changedFiles, + ); - if (!specAndTestTsFilesNames.includes(fileName)) { - filesList.push(nextFile); - } + // For each .ts or .vue file, check if there's a corresponding .test.ts or .spec.ts file in the repository + const missingTests = changedVueFiles + .concat(changedTsFilesWithFunction) + .reduce((filesList, nextFile) => { + const fileName = path.parse(nextFile).name; - return filesList; - }, []); + if (!specAndTestTsFilesNames.includes(fileName)) { + filesList.push(nextFile); + } - if(missingTests.length) { + return filesList; + }, []); + + if (missingTests.length) { console.error(`Missing tests for:\n${missingTests.join('\n')}`); process.exit(1); } diff --git a/.prettierignore b/.prettierignore index c6a6424c90..ed42a3b596 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ coverage dist package.json .pnpm-lock.yml +packages/editor-ui/index.html packages/nodes-base/nodes/**/test cypress/fixtures CHANGELOG.md diff --git a/.vscode/settings.default.json b/.vscode/settings.default.json index f98d5cec70..87db2ca2ee 100644 --- a/.vscode/settings.default.json +++ b/.vscode/settings.default.json @@ -9,6 +9,7 @@ "typescript.format.enable": false, "typescript.tsdk": "node_modules/typescript/lib", "workspace-default-settings.runOnActivation": true, + "prettier.prettierPath": "node_modules/prettier/index.cjs", "eslint.probe": ["javascript", "typescript", "vue"], "eslint.workingDirectories": [ { diff --git a/cypress/e2e/26-resource-locator.cy.ts b/cypress/e2e/26-resource-locator.cy.ts index 219a8b8fe7..38de738b56 100644 --- a/cypress/e2e/26-resource-locator.cy.ts +++ b/cypress/e2e/26-resource-locator.cy.ts @@ -54,16 +54,22 @@ describe('Resource Locator', () => { // unlike RMC and remote options, RLC does not support loadOptionDependsOn it('should retrieve list options when other params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', {action: 'Resource Locator'}); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Resource Locator' }); ndv.getters.resourceLocatorInput('rlc').click(); - getVisiblePopper().should('have.length', 1).findChildByTestId('rlc-item').should('have.length', 5); + getVisiblePopper() + .should('have.length', 1) + .findChildByTestId('rlc-item') + .should('have.length', 5); ndv.actions.setInvalidExpression('fieldId'); ndv.getters.container().click(); // remove focus from input, hide expression preview ndv.getters.resourceLocatorInput('rlc').click(); - getVisiblePopper().should('have.length', 1).findChildByTestId('rlc-item').should('have.length', 5); + getVisiblePopper() + .should('have.length', 1) + .findChildByTestId('rlc-item') + .should('have.length', 5); }); }); diff --git a/cypress/e2e/27-two-factor-authentication.cy.ts b/cypress/e2e/27-two-factor-authentication.cy.ts index e2aa0d0f96..adbad2b495 100644 --- a/cypress/e2e/27-two-factor-authentication.cy.ts +++ b/cypress/e2e/27-two-factor-authentication.cy.ts @@ -42,7 +42,7 @@ describe('Two-factor authentication', () => { signinPage.actions.loginWithEmailAndPassword(email, password); personalSettingsPage.actions.enableMfa(); mainSidebar.actions.signout(); - const token = generateOTPToken(user.mfaSecret) + const token = generateOTPToken(user.mfaSecret); mfaLoginPage.actions.loginWithMfaToken(email, password, token); mainSidebar.actions.signout(); }); @@ -61,7 +61,7 @@ describe('Two-factor authentication', () => { signinPage.actions.loginWithEmailAndPassword(email, password); personalSettingsPage.actions.enableMfa(); mainSidebar.actions.signout(); - const token = generateOTPToken(user.mfaSecret) + const token = generateOTPToken(user.mfaSecret); mfaLoginPage.actions.loginWithMfaToken(email, password, token); personalSettingsPage.actions.disableMfa(); mainSidebar.actions.signout(); diff --git a/cypress/e2e/28-debug.cy.ts b/cypress/e2e/28-debug.cy.ts index ede3b500cb..24b7557bc0 100644 --- a/cypress/e2e/28-debug.cy.ts +++ b/cypress/e2e/28-debug.cy.ts @@ -1,5 +1,6 @@ import { - HTTP_REQUEST_NODE_NAME, IF_NODE_NAME, + HTTP_REQUEST_NODE_NAME, + IF_NODE_NAME, INSTANCE_OWNER, MANUAL_TRIGGER_NODE_NAME, SET_NODE_NAME, @@ -11,119 +12,121 @@ const ndv = new NDV(); const executionsTab = new WorkflowExecutionsTab(); describe('Debug', () => { - it('should be able to debug executions', () => { - cy.intercept('GET', '/rest/settings', (req) => { - req.on('response', (res) => { - res.send({ - data: { ...res.body.data, enterprise: { debugInEditor: true } }, - }); - }); - }).as('loadSettings'); - cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); - cy.intercept('GET', '/rest/executions/*').as('getExecution'); - cy.intercept('GET', '/rest/executions-current?filter=*').as('getCurrentExecutions'); - cy.intercept('POST', '/rest/workflows/run').as('postWorkflowRun'); + it('should be able to debug executions', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, enterprise: { debugInEditor: true } }, + }); + }); + }).as('loadSettings'); + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + cy.intercept('GET', '/rest/executions/*').as('getExecution'); + cy.intercept('GET', '/rest/executions-current?filter=*').as('getCurrentExecutions'); + cy.intercept('POST', '/rest/workflows/run').as('postWorkflowRun'); - cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); + cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password }); - workflowPage.actions.visit(); + workflowPage.actions.visit(); - workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); - workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME); - workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); - ndv.actions.typeIntoParameterInput('url', 'https://foo.bar'); - ndv.actions.close(); + workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME); + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.typeIntoParameterInput('url', 'https://foo.bar'); + ndv.actions.close(); - workflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); + workflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); - workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); - workflowPage.actions.executeWorkflow(); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + workflowPage.actions.executeWorkflow(); - cy.wait(['@postWorkflowRun']); + cy.wait(['@postWorkflowRun']); - executionsTab.actions.switchToExecutionsTab(); + executionsTab.actions.switchToExecutionsTab(); - cy.wait(['@getExecutions', '@getCurrentExecutions']); + cy.wait(['@getExecutions', '@getCurrentExecutions']); - executionsTab.getters.executionDebugButton().should('have.text', 'Debug in editor').click(); - cy.get('.el-notification').contains('Execution data imported').should('be.visible'); - cy.get('.matching-pinned-nodes-confirmation').should('not.exist'); + executionsTab.getters.executionDebugButton().should('have.text', 'Debug in editor').click(); + cy.get('.el-notification').contains('Execution data imported').should('be.visible'); + cy.get('.matching-pinned-nodes-confirmation').should('not.exist'); + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.clearParameterInput('url'); + ndv.actions.typeIntoParameterInput('url', 'https://postman-echo.com/get?foo1=bar1&foo2=bar2'); + ndv.actions.close(); - workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); - ndv.actions.clearParameterInput('url'); - ndv.actions.typeIntoParameterInput('url', 'https://postman-echo.com/get?foo1=bar1&foo2=bar2'); - ndv.actions.close(); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + workflowPage.actions.executeWorkflow(); - workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); - workflowPage.actions.executeWorkflow(); + cy.wait(['@postWorkflowRun']); - cy.wait(['@postWorkflowRun']); + workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); + ndv.actions.pinData(); + ndv.actions.close(); - workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME); - ndv.actions.pinData(); - ndv.actions.close(); + executionsTab.actions.switchToExecutionsTab(); - executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions', '@getCurrentExecutions']); - cy.wait(['@getExecutions', '@getCurrentExecutions']); + executionsTab.getters.executionListItems().should('have.length', 2).first().click(); + cy.wait(['@getExecution']); - executionsTab.getters.executionListItems().should('have.length', 2).first().click(); - cy.wait(['@getExecution']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); - executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + let confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 2); + confirmDialog.get('.btn--cancel').click(); - let confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); - confirmDialog.find('li').should('have.length', 2); - confirmDialog.get('.btn--cancel').click(); + cy.wait(['@getExecutions', '@getCurrentExecutions']); - cy.wait(['@getExecutions', '@getCurrentExecutions']); + executionsTab.getters.executionListItems().should('have.length', 2).first().click(); + cy.wait(['@getExecution']); - executionsTab.getters.executionListItems().should('have.length', 2).first().click(); - cy.wait(['@getExecution']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); - executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 2); + confirmDialog.get('.btn--confirm').click(); - confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); - confirmDialog.find('li').should('have.length', 2); - confirmDialog.get('.btn--confirm').click(); + workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon'); + workflowPage.getters + .canvasNodes() + .not(':first') + .should('not.have.descendants', '.node-pin-data-icon'); - workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon'); - workflowPage.getters.canvasNodes().not(':first').should('not.have.descendants', '.node-pin-data-icon'); + cy.reload(true); + cy.wait(['@getExecution']); - cy.reload(true); - cy.wait(['@getExecution']); + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 1); + confirmDialog.get('.btn--confirm').click(); - confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); - confirmDialog.find('li').should('have.length', 1); - confirmDialog.get('.btn--confirm').click(); + workflowPage.getters.canvasNodePlusEndpointByName(SET_NODE_NAME).click(); + workflowPage.actions.addNodeToCanvas(IF_NODE_NAME, false); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); - workflowPage.getters.canvasNodePlusEndpointByName(SET_NODE_NAME).click(); - workflowPage.actions.addNodeToCanvas(IF_NODE_NAME, false); - workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions', '@getCurrentExecutions']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); - executionsTab.actions.switchToExecutionsTab(); - cy.wait(['@getExecutions', '@getCurrentExecutions']); - executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); + confirmDialog.find('li').should('have.length', 1); + confirmDialog.get('.btn--confirm').click(); + workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty'); - confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible'); - confirmDialog.find('li').should('have.length', 1); - confirmDialog.get('.btn--confirm').click(); - workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty'); + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.pinDataButton().click(); + ndv.actions.close(); - workflowPage.getters.canvasNodes().first().dblclick(); - ndv.getters.pinDataButton().click(); - ndv.actions.close(); + workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); + workflowPage.actions.executeWorkflow(); + workflowPage.actions.deleteNode(IF_NODE_NAME); - workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); - workflowPage.actions.executeWorkflow(); - workflowPage.actions.deleteNode(IF_NODE_NAME); - - executionsTab.actions.switchToExecutionsTab(); - cy.wait(['@getExecutions', '@getCurrentExecutions']); - executionsTab.getters.executionListItems().should('have.length', 3).first().click(); - cy.wait(['@getExecution']); - executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); - cy.get('.el-notification').contains('Some execution data wasn\'t imported').should('be.visible'); - }); + executionsTab.actions.switchToExecutionsTab(); + cy.wait(['@getExecutions', '@getCurrentExecutions']); + executionsTab.getters.executionListItems().should('have.length', 3).first().click(); + cy.wait(['@getExecution']); + executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click(); + cy.get('.el-notification').contains("Some execution data wasn't imported").should('be.visible'); + }); }); diff --git a/cypress/e2e/28-resource-mapper.cy.ts b/cypress/e2e/28-resource-mapper.cy.ts index 64e1179365..8c47dda832 100644 --- a/cypress/e2e/28-resource-mapper.cy.ts +++ b/cypress/e2e/28-resource-mapper.cy.ts @@ -9,9 +9,15 @@ describe('Resource Mapper', () => { }); it('should not retrieve list options when required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', {action: 'Resource Mapping Component'}); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); - ndv.getters.resourceMapperFieldsContainer().should('be.visible').findChildByTestId('parameter-input').should('have.length', 2); + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 2); ndv.actions.setInvalidExpression('fieldId'); @@ -20,13 +26,23 @@ describe('Resource Mapper', () => { }); it('should retrieve list options when optional params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', {action: 'Resource Mapping Component'}); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { + action: 'Resource Mapping Component', + }); - ndv.getters.resourceMapperFieldsContainer().should('be.visible').findChildByTestId('parameter-input').should('have.length', 2); + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 2); ndv.actions.setInvalidExpression('otherField'); ndv.actions.refreshResourceMapperColumns(); - ndv.getters.resourceMapperFieldsContainer().should('be.visible').findChildByTestId('parameter-input').should('have.length', 2); + ndv.getters + .resourceMapperFieldsContainer() + .should('be.visible') + .findChildByTestId('parameter-input') + .should('have.length', 2); }); }); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index 7d7dbbd0de..3789c2d5be 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -291,7 +291,7 @@ describe('NDV', () => { }); it('should not retrieve remote options when required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', {action: 'Remote Options'}); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); ndv.getters.parameterInput('remoteOptions').click(); getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); @@ -308,7 +308,7 @@ describe('NDV', () => { }); it('should retrieve remote options when non-required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', {action: 'Remote Options'}); + workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' }); ndv.getters.parameterInput('remoteOptions').click(); getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3); @@ -338,7 +338,7 @@ describe('NDV', () => { workflowPage.getters.nodeIssuesByName('Webhook').should('exist'); workflowPage.getters.canvasNodes().first().dblclick(); - ndv.getters.parameterInput('path').type('t') + ndv.getters.parameterInput('path').type('t'); ndv.getters.nodeExecuteButton().should('not.be.disabled'); ndv.getters.triggerPanelExecuteButton().should('exist'); diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts index bd266562f5..09b3088ca1 100644 --- a/cypress/e2e/6-code-node.cy.ts +++ b/cypress/e2e/6-code-node.cy.ts @@ -13,7 +13,7 @@ describe('Code node', () => { }); it('should show correct placeholders switching modes', () => { - cy.contains("// Loop over input items and add a new field").should('be.visible'); + cy.contains('// Loop over input items and add a new field').should('be.visible'); ndv.getters.parameterInput('mode').click(); ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); @@ -22,8 +22,8 @@ describe('Code node', () => { ndv.getters.parameterInput('mode').click(); ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for All Items'); - cy.contains("// Loop over input items and add a new field").should('be.visible'); - }) + cy.contains('// Loop over input items and add a new field').should('be.visible'); + }); it('should execute the placeholder successfully in both modes', () => { ndv.actions.execute(); @@ -36,7 +36,7 @@ describe('Code node', () => { WorkflowPage.getters.successToast().contains('Node executed successfully'); }); - }) + }); describe('Ask AI', () => { it('tab should display based on experiment', () => { @@ -53,8 +53,8 @@ describe('Code node', () => { win.featureFlags.override('011_ask_AI', undefined); WorkflowPage.actions.openNode('Code'); cy.getByTestId('code-node-tab-ai').should('not.exist'); - }) - }) + }); + }); describe('Enabled', () => { beforeEach(() => { @@ -63,20 +63,19 @@ describe('Code node', () => { win.featureFlags.override('011_ask_AI', 'gpt3'); WorkflowPage.actions.addInitialNodeToCanvas('Manual'); WorkflowPage.actions.addNodeToCanvas('Code', true, true); - }) - }) + }); + }); it('tab should exist if experiment selected and be selectable', () => { - cy.getByTestId('code-node-tab-ai').should('exist'); - cy.get('#tab-ask-ai').click(); - cy.contains('Hey AI, generate JavaScript').should('exist'); - }) + cy.getByTestId('code-node-tab-ai').should('exist'); + cy.get('#tab-ask-ai').click(); + cy.contains('Hey AI, generate JavaScript').should('exist'); + }); it('generate code button should have correct state & tooltips', () => { cy.getByTestId('code-node-tab-ai').should('exist'); cy.get('#tab-ask-ai').click(); - cy.getByTestId('ask-ai-cta').should('be.disabled'); cy.getByTestId('ask-ai-cta').realHover(); cy.getByTestId('ask-ai-cta-tooltip-no-input-data').should('exist'); @@ -85,7 +84,7 @@ describe('Code node', () => { cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist'); cy.getByTestId('ask-ai-prompt-input') // Type random 14 character string - .type([...Array(14)].map(() => (Math.random() * 36 | 0).toString(36)).join('')) + .type([...Array(14)].map(() => ((Math.random() * 36) | 0).toString(36)).join('')); cy.getByTestId('ask-ai-cta').realHover(); cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist'); @@ -93,64 +92,66 @@ describe('Code node', () => { cy.getByTestId('ask-ai-prompt-input') .clear() // Type random 15 character string - .type([...Array(15)].map(() => (Math.random() * 36 | 0).toString(36)).join('')) + .type([...Array(15)].map(() => ((Math.random() * 36) | 0).toString(36)).join('')); cy.getByTestId('ask-ai-cta').should('be.enabled'); cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600'); - }) + }); it('should send correct schema and replace code', () => { - const prompt = [...Array(20)].map(() => (Math.random() * 36 | 0).toString(36)).join(''); + const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''); cy.get('#tab-ask-ai').click(); ndv.actions.executePrevious(); - cy.getByTestId('ask-ai-prompt-input').type(prompt) + cy.getByTestId('ask-ai-prompt-input').type(prompt); cy.intercept('POST', '/rest/ask-ai', { statusCode: 200, body: { data: { - code: 'console.log("Hello World")' + code: 'console.log("Hello World")', }, - } + }, }).as('ask-ai'); cy.getByTestId('ask-ai-cta').click(); - const askAiReq = cy.wait('@ask-ai') + const askAiReq = cy.wait('@ask-ai'); - askAiReq.its('request.body').should('have.keys', ['question', 'model', 'context', 'n8nVersion']); + askAiReq + .its('request.body') + .should('have.keys', ['question', 'model', 'context', 'n8nVersion']); askAiReq.its('context').should('have.keys', ['schema', 'ndvSessionId', 'sessionId']); - cy.contains('Code generation completed').should('be.visible') + cy.contains('Code generation completed').should('be.visible'); cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")'); cy.get('#tab-code').should('have.class', 'is-active'); - }) + }); it('should show error based on status code', () => { - const prompt = [...Array(20)].map(() => (Math.random() * 36 | 0).toString(36)).join(''); - cy.get('#tab-ask-ai').click(); - ndv.actions.executePrevious(); + const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''); + cy.get('#tab-ask-ai').click(); + ndv.actions.executePrevious(); - cy.getByTestId('ask-ai-prompt-input').type(prompt) + cy.getByTestId('ask-ai-prompt-input').type(prompt); - const handledCodes = [ - { code: 400, message: 'Code generation failed due to an unknown reason' }, - { code: 413, message: 'Your workflow data is too large for AI to process' }, - { code: 429, message: 'We\'ve hit our rate limit with our AI partner' }, - { code: 500, message: 'Code generation failed due to an unknown reason' }, - ] + const handledCodes = [ + { code: 400, message: 'Code generation failed due to an unknown reason' }, + { code: 413, message: 'Your workflow data is too large for AI to process' }, + { code: 429, message: "We've hit our rate limit with our AI partner" }, + { code: 500, message: 'Code generation failed due to an unknown reason' }, + ]; - handledCodes.forEach(({ code, message }) => { - cy.intercept('POST', '/rest/ask-ai', { - statusCode: code, - status: code, - }).as('ask-ai'); + handledCodes.forEach(({ code, message }) => { + cy.intercept('POST', '/rest/ask-ai', { + statusCode: code, + status: code, + }).as('ask-ai'); - cy.getByTestId('ask-ai-cta').click(); - cy.contains(message).should('be.visible') - }) - }) - }) + cy.getByTestId('ask-ai-cta').click(); + cy.contains(message).should('be.visible'); + }); + }); + }); }); }); diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index f8be5b991b..dfaf0d0292 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -39,7 +39,11 @@ export class NDV extends BasePage { inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'), nodeParameters: () => cy.getByTestId('node-parameters'), parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`), - parameterInputIssues: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`).should('have.length', 1).findChildByTestId('parameter-issues'), + parameterInputIssues: (parameterName: string) => + cy + .getByTestId(`parameter-input-${parameterName}`) + .should('have.length', 1) + .findChildByTestId('parameter-issues'), parameterExpressionPreview: (parameterName: string) => this.getters .nodeParameters() @@ -102,7 +106,11 @@ export class NDV extends BasePage { clearParameterInput: (parameterName: string) => { this.getters.parameterInput(parameterName).type(`{selectall}{backspace}`); }, - typeIntoParameterInput: (parameterName: string, content: string, opts?: { parseSpecialCharSequences: boolean }) => { + typeIntoParameterInput: ( + parameterName: string, + content: string, + opts?: { parseSpecialCharSequences: boolean }, + ) => { this.getters.parameterInput(parameterName).type(content, opts); }, selectOptionInParameterDropdown: (parameterName: string, content: string) => { @@ -177,16 +185,22 @@ export class NDV extends BasePage { refreshResourceMapperColumns: () => { this.getters.resourceMapperSelectColumn().realHover(); - this.getters.resourceMapperSelectColumn().findChildByTestId('action-toggle').should('have.length', 1).click(); + this.getters + .resourceMapperSelectColumn() + .findChildByTestId('action-toggle') + .should('have.length', 1) + .click(); getVisiblePopper().find('li').last().click(); }, setInvalidExpression: (fieldName: string, invalidExpression?: string) => { - this.actions.typeIntoParameterInput(fieldName, "="); - this.actions.typeIntoParameterInput(fieldName, invalidExpression ?? "{{ $('unknown')", { parseSpecialCharSequences: false }); + this.actions.typeIntoParameterInput(fieldName, '='); + this.actions.typeIntoParameterInput(fieldName, invalidExpression ?? "{{ $('unknown')", { + parseSpecialCharSequences: false, + }); this.actions.validateExpressionPreview(fieldName, `node doesn't exist`); - } + }, }; } diff --git a/cypress/pages/settings-personal.ts b/cypress/pages/settings-personal.ts index 671b5e21a2..06fd218daa 100644 --- a/cypress/pages/settings-personal.ts +++ b/cypress/pages/settings-personal.ts @@ -62,7 +62,7 @@ export class PersonalSettingsPage extends BasePage { this.getters.enableMfaButton().click(); mfaSetupModal.getters.copySecretToClipboardButton().realClick(); cy.readClipboard().then((secret) => { - const token = generateOTPToken(secret) + const token = generateOTPToken(secret); mfaSetupModal.getters.tokenInput().type(token); mfaSetupModal.getters.downloadRecoveryCodesButton().click(); diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 622aa9d98d..183d72b9a4 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -29,9 +29,11 @@ export class WorkflowPage extends BasePage { canvasNodeByName: (nodeName: string) => this.getters.canvasNodes().filter(`:contains(${nodeName})`), nodeIssuesByName: (nodeName: string) => - this.getters.canvasNodes().filter(`:contains(${nodeName})`) - .should('have.length.greaterThan', 0) - .findChildByTestId('node-issues'), + this.getters + .canvasNodes() + .filter(`:contains(${nodeName})`) + .should('have.length.greaterThan', 0) + .findChildByTestId('node-issues'), getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => { return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`; }, @@ -132,14 +134,16 @@ export class WorkflowPage extends BasePage { win.preventNodeViewBeforeUnload = preventNodeViewUnload; }); }, - addInitialNodeToCanvas: (nodeDisplayName: string, opts?: { keepNdvOpen?: boolean, action?: string }) => { + addInitialNodeToCanvas: ( + nodeDisplayName: string, + opts?: { keepNdvOpen?: boolean; action?: string }, + ) => { this.getters.canvasPlusButton().click(); this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); if (opts?.action) { nodeCreator.getters.getCreatorItem(opts.action).click(); - } - else if (!opts?.keepNdvOpen) { + } else if (!opts?.keepNdvOpen) { cy.get('body').type('{esc}'); } }, diff --git a/package.json b/package.json index def591ca2e..d314c86dc4 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@types/node": "^18.16.16", "chokidar": "3.5.2", "jsonwebtoken": "9.0.0", - "prettier": "^3.0.0", + "prettier": "^3.0.3", "semver": "^7.5.4", "tough-cookie": "^4.1.3", "tslib": "^2.6.1", diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index a60b6e29fa..1dc69fb13a 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -6,7 +6,7 @@ This list shows all the versions which include breaking changes and how to upgra ### What changed? -In the Code node, `console.log` does not output to stdout by default. +In the Code node, `console.log` does not output to stdout by default. ### When is action necessary? diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 9c1c79c692..75b6281467 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -56,9 +56,8 @@ export class ActiveExecutions { fullExecutionData.workflowId = workflowId; } - const executionResult = await Container.get(ExecutionRepository).createNewExecution( - fullExecutionData, - ); + const executionResult = + await Container.get(ExecutionRepository).createNewExecution(fullExecutionData); executionId = executionResult.id; if (executionId === undefined) { throw new Error('There was an issue assigning an execution id to the execution'); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 6114c40701..7500ef17c0 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1251,9 +1251,8 @@ export class Server extends AbstractServer { Object.assign(findOptions.where, { workflowId: In(sharedWorkflowIds) }); } - const executions = await Container.get(ExecutionRepository).findMultipleExecutions( - findOptions, - ); + const executions = + await Container.get(ExecutionRepository).findMultipleExecutions(findOptions); if (!executions.length) return []; diff --git a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts index b932673011..0d24a628dc 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts +++ b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts @@ -54,9 +54,8 @@ export class SourceControlController { await this.sourceControlPreferencesService.validateSourceControlPreferences( sanitizedPreferences, ); - const updatedPreferences = await this.sourceControlPreferencesService.setPreferences( - sanitizedPreferences, - ); + const updatedPreferences = + await this.sourceControlPreferencesService.setPreferences(sanitizedPreferences); if (sanitizedPreferences.initRepo === true) { try { await this.sourceControlService.initializeRepository( diff --git a/packages/cli/src/executions/executions.service.ts b/packages/cli/src/executions/executions.service.ts index 73f4a73e10..aab68b449d 100644 --- a/packages/cli/src/executions/executions.service.ts +++ b/packages/cli/src/executions/executions.service.ts @@ -317,9 +317,8 @@ export class ExecutionsService { const workflowRunner = new WorkflowRunner(); const retriedExecutionId = await workflowRunner.run(data); - const executionData = await Container.get(ActiveExecutions).getPostExecutePromise( - retriedExecutionId, - ); + const executionData = + await Container.get(ActiveExecutions).getPostExecutePromise(retriedExecutionId); if (!executionData) { throw new Error('The retry did not start for an unknown reason.'); diff --git a/packages/design-system/src/utils/__tests__/event-bus.spec.ts b/packages/design-system/src/utils/__tests__/event-bus.spec.ts index a5dba6f833..c1e598e43e 100644 --- a/packages/design-system/src/utils/__tests__/event-bus.spec.ts +++ b/packages/design-system/src/utils/__tests__/event-bus.spec.ts @@ -3,7 +3,7 @@ import { createEventBus } from '../event-bus'; // @TODO: Remove when conflicting vitest and jest globals are reconciled declare global { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const vi: typeof import('vitest')['vitest']; + const vi: (typeof import('vitest'))['vitest']; } describe('createEventBus()', () => { diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 65fd3195b9..af3682d2e0 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -65,7 +65,7 @@ "n8n-workflow": "workspace:*", "normalize-wheel": "^1.0.1", "pinia": "^2.1.6", - "prettier": "^3.0.0", + "prettier": "^3.0.3", "stream-browserify": "^3.0.0", "qrcode.vue": "^3.3.4", "timeago.js": "^4.0.2", diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 14a2139df1..5af6123357 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -3891,9 +3891,8 @@ export default defineComponent({ await Promise.all([this.loadCredentials(), this.loadVariables(), this.tagsStore.fetchAll()]); if (workflowId !== null && !this.uiStore.stateIsDirty) { - const workflow: IWorkflowDb | undefined = await this.workflowsStore.fetchWorkflow( - workflowId, - ); + const workflow: IWorkflowDb | undefined = + await this.workflowsStore.fetchWorkflow(workflowId); if (workflow) { this.titleSet(workflow.name, 'IDLE'); await this.openWorkflow(workflow); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f47bb65a00..b6434b976e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,7 +11,7 @@ overrides: '@types/node': ^18.16.16 chokidar: 3.5.2 jsonwebtoken: 9.0.0 - prettier: ^3.0.0 + prettier: ^3.0.3 semver: ^7.5.4 tough-cookie: ^4.1.3 tslib: ^2.6.1 @@ -178,7 +178,7 @@ importers: version: 1.0.0 eslint-plugin-prettier: specifier: ^5.0.0 - version: 5.0.0(@types/eslint@8.44.1)(eslint-config-prettier@8.9.0)(eslint@8.45.0)(prettier@3.0.0) + version: 5.0.0(@types/eslint@8.44.1)(eslint-config-prettier@8.9.0)(eslint@8.45.0)(prettier@3.0.3) eslint-plugin-unicorn: specifier: ^48.0.1 version: 48.0.1(eslint@8.45.0) @@ -894,8 +894,8 @@ importers: specifier: ^2.1.6 version: 2.1.6(typescript@5.2.2)(vue@3.3.4) prettier: - specifier: ^3.0.0 - version: 3.0.0 + specifier: ^3.0.3 + version: 3.0.3 qrcode.vue: specifier: ^3.3.4 version: 3.3.4(vue@3.3.4) @@ -5885,7 +5885,7 @@ packages: less-loader: 11.1.3(less@4.1.3)(webpack@5.75.0) postcss: 8.4.27 postcss-loader: 7.3.3(postcss@8.4.27)(webpack@5.75.0) - prettier: 3.0.0 + prettier: 3.0.3 react: 17.0.2 react-dom: 18.2.0(react@17.0.2) resolve-url-loader: 5.0.0 @@ -6199,7 +6199,7 @@ packages: jscodeshift: 0.14.0(@babel/preset-env@7.22.9) leven: 3.1.0 ora: 5.4.1 - prettier: 3.0.0 + prettier: 3.0.3 prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 @@ -6250,7 +6250,7 @@ packages: globby: 11.1.0 jscodeshift: 0.14.0(@babel/preset-env@7.22.9) lodash: 4.17.21 - prettier: 3.0.0 + prettier: 3.0.3 recast: 0.23.3 transitivePeerDependencies: - supports-color @@ -10620,7 +10620,7 @@ packages: cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x dependencies: cypress: 12.17.2 - prettier: 3.0.0 + prettier: 3.0.3 dev: true /cypress@12.17.2: @@ -11810,14 +11810,14 @@ packages: - typescript dev: true - /eslint-plugin-prettier@5.0.0(@types/eslint@8.44.1)(eslint-config-prettier@8.9.0)(eslint@8.45.0)(prettier@3.0.0): + /eslint-plugin-prettier@5.0.0(@types/eslint@8.44.1)(eslint-config-prettier@8.9.0)(eslint@8.45.0)(prettier@3.0.3): resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' eslint: '>=8.0.0' eslint-config-prettier: '*' - prettier: ^3.0.0 + prettier: ^3.0.3 peerDependenciesMeta: '@types/eslint': optional: true @@ -11827,7 +11827,7 @@ packages: '@types/eslint': 8.44.1 eslint: 8.45.0 eslint-config-prettier: 8.9.0(eslint@8.45.0) - prettier: 3.0.0 + prettier: 3.0.3 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 dev: true @@ -18035,8 +18035,8 @@ packages: fast-diff: 1.2.0 dev: true - /prettier@3.0.0: - resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + /prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true