Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/n8n-io/n8n into node-997-…
Browse files Browse the repository at this point in the history
…mondaycom-node-will-stop-working-in-january
  • Loading branch information
michael-radency committed Jan 10, 2024
2 parents f0b8f05 + f208a6e commit a61a565
Show file tree
Hide file tree
Showing 187 changed files with 4,446 additions and 1,222 deletions.
9 changes: 6 additions & 3 deletions .github/scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"dependencies": {
"cacheable-lookup": "6.1.0",
"conventional-changelog": "^4.0.0",
"glob": "^10.3.0",
"semver": "^7.5.4",
"tempfile": "^5.0.0",
"debug": "4.3.4",
"glob": "10.3.10",
"p-limit": "3.1.0",
"semver": "7.5.4",
"tempfile": "5.0.0",
"typescript": "*"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
#!/usr/bin/env node

const packages = ['nodes-base', '@n8n/nodes-langchain'];
const concurrency = 20;
let exitCode = 0;

const debug = require('debug')('n8n');
const path = require('path');
const https = require('https');
const glob = require('fast-glob');
const glob = require('glob');
const pLimit = require('p-limit');
const Lookup = require('cacheable-lookup').default;

const nodesBaseDir = path.resolve(__dirname, '../packages/nodes-base');
const agent = new https.Agent({ keepAlive: true, keepAliveMsecs: 5000 });
new Lookup().install(agent);
const limiter = pLimit(concurrency);

const validateUrl = async (kind, name, documentationUrl) =>
new Promise((resolve, reject) => {
Expand All @@ -22,21 +30,26 @@ const validateUrl = async (kind, name, documentationUrl) =>
port: 443,
path: url.pathname,
method: 'HEAD',
agent,
},
(res) => {
debug('✓', kind, name);
resolve([name, res.statusCode]);
},
(res) => resolve([name, res.statusCode]),
)
.on('error', (e) => reject(e))
.end();
});

const checkLinks = async (kind) => {
let types = require(path.join(nodesBaseDir, `dist/types/${kind}.json`));
const checkLinks = async (baseDir, kind) => {
let types = require(path.join(baseDir, `dist/types/${kind}.json`));
if (kind === 'nodes')
types = types.filter(({ codex }) => !!codex?.resources?.primaryDocumentation);
const limit = pLimit(30);
debug(kind, types.length);

const statuses = await Promise.all(
types.map((type) =>
limit(() => {
limiter(() => {
const documentationUrl =
kind === 'credentials'
? type.documentationUrl
Expand All @@ -55,10 +68,13 @@ const checkLinks = async (kind) => {

if (missingDocs.length) console.log('Documentation URL missing for %s', kind, missingDocs);
if (invalidUrls.length) console.log('Documentation URL invalid for %s', kind, invalidUrls);
if (missingDocs.length || invalidUrls.length) process.exit(1);
if (missingDocs.length || invalidUrls.length) exitCode = 1;
};

(async () => {
await checkLinks('credentials');
await checkLinks('nodes');
for (const packageName of packages) {
const baseDir = path.resolve(__dirname, '../../packages', packageName);
await Promise.all([checkLinks(baseDir, 'credentials'), checkLinks(baseDir, 'nodes')]);
if (exitCode !== 0) process.exit(exitCode);
}
})();
6 changes: 4 additions & 2 deletions .github/workflows/check-documentation-urls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Build nodes-base
run: pnpm --filter @n8n/client-oauth2 --filter n8n-workflow --filter n8n-core --filter n8n-nodes-base build
run: pnpm --filter @n8n/client-oauth2 --filter n8n-workflow --filter n8n-core --filter n8n-nodes-base --filter @n8n/n8n-nodes-langchain build

- run: npm install --prefix=.github/scripts --no-package-lock

- name: Test URLs
run: node scripts/validate-docs-links.js
run: node .github/scripts/validate-docs-links.js

- name: Notify Slack on failure
uses: act10ns/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/units-tests-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ jobs:
if: ${{ inputs.collectCoverage == 'true' }}
uses: codecov/codecov-action@v3
with:
files: packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml
files: packages/@n8n/chat/coverage/cobertura-coverage.xml,packages/@n8n/nodes-langchain/coverage/cobertura-coverage.xml,packages/@n8n/permissions/coverage/cobertura-coverage.xml,packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml
5 changes: 3 additions & 2 deletions cypress/composables/modals/workflow-credential-setup-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

export const getWorkflowCredentialsModal = () => cy.getByTestId('setup-workflow-credentials-modal');

export const getContinueButton = () => cy.getByTestId('continue-button');

/**
* Actions
*/

export const closeModal = () =>
getWorkflowCredentialsModal().find("button[aria-label='Close this dialog']").click();
export const closeModalFromContinueButton = () => getContinueButton().click();
2 changes: 1 addition & 1 deletion cypress/composables/setup-workflow-credentials-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Getters
*/

export const getSetupWorkflowCredentialsButton = () => cy.get(`button:contains("Set up Template")`);
export const getSetupWorkflowCredentialsButton = () => cy.get(`button:contains("Set up template")`);
2 changes: 1 addition & 1 deletion cypress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const INSTANCE_MEMBERS = [

export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Test Workflow"';
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Manual Chat Trigger';
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
export const CODE_NODE_NAME = 'Code';
export const SET_NODE_NAME = 'Set';
Expand Down
33 changes: 21 additions & 12 deletions cypress/e2e/34-template-credentials-setup.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ const workflowPage = new WorkflowPage();

const testTemplate = templateCredentialsSetupPage.testData.simpleTemplate;

// NodeView uses beforeunload listener that will show a browser
// native popup, which will block cypress from continuing / exiting.
// This prevent the registration of the listener.
Cypress.on('window:before:load', (win) => {
const origAddEventListener = win.addEventListener;
win.addEventListener = (eventName: string, listener: any, opts: any) => {
if (eventName === 'beforeunload') {
return;
}

return origAddEventListener.call(win, eventName, listener, opts);
};
});

describe('Template credentials setup', () => {
beforeEach(() => {
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${testTemplate.id}`, {
Expand All @@ -29,7 +43,7 @@ describe('Template credentials setup', () => {
templateWorkflowPage.actions.clickUseThisWorkflowButton();

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');
});

Expand All @@ -39,23 +53,23 @@ describe('Template credentials setup', () => {
clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram');

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');
});

it('can be opened with a direct url', () => {
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');
});

it('has all the elements on page', () => {
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');

templateCredentialsSetupPage.getters
Expand Down Expand Up @@ -159,10 +173,6 @@ describe('Template credentials setup', () => {
templateCredentialsSetupPage.getters.skipLink().click();

getSetupWorkflowCredentialsButton().should('be.visible');

// We need to save the workflow or otherwise a browser native popup
// will block cypress from continuing
workflowPage.actions.saveWorkflowOnButtonClick();
});

it('should allow credential setup from workflow editor if user fills in credentials partially during template setup', () => {
Expand All @@ -185,7 +195,8 @@ describe('Template credentials setup', () => {
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');

setupCredsModal.closeModal();
setupCredsModal.closeModalFromContinueButton();
setupCredsModal.getWorkflowCredentialsModal().should('not.exist');

// Focus the canvas so the copy to clipboard works
workflowPage.getters.canvasNodes().eq(0).realClick();
Expand All @@ -202,9 +213,7 @@ describe('Template credentials setup', () => {
});
});

// We need to save the workflow or otherwise a browser native popup
// will block cypress from continuing
workflowPage.actions.saveWorkflowOnButtonClick();
getSetupWorkflowCredentialsButton().should('not.exist');
});
});
});
2 changes: 1 addition & 1 deletion cypress/e2e/4-node-creator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('Node Creator', () => {
nodeCreatorFeature.actions.openNodeCreator();

nodeCreatorFeature.getters.searchBar().find('input').type('manual');
nodeCreatorFeature.getters.creatorItem().should('have.length', 3);
nodeCreatorFeature.getters.creatorItem().should('have.length', 2);
nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123');
nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
nodeCreatorFeature.getters
Expand Down
2 changes: 1 addition & 1 deletion cypress/pages/template-credential-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const getters = {

export const enableTemplateCredentialSetupFeatureFlag = () => {
cy.window().then((win) => {
win.featureFlags.override('016_template_credential_setup', true);
win.featureFlags.override('017_template_credential_setup_v2', true);
});
};

Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const config = {
}, {}),
setupFilesAfterEnv: ['jest-expect-message'],
collectCoverage: process.env.COVERAGE_ENABLED === 'true',
coverageReporters: [process.env.COVERAGE_REPORT === 'true' ? 'text' : 'text-summary'],
coverageReporters: ['text-summary'],
collectCoverageFrom: ['src/**/*.ts'],
};

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
"typescript": "^5.3.0",
"xml2js": "^0.5.0",
"cpy@8>globby": "^11.1.0",
"qqjs>globby": "^11.1.0"
"qqjs>globby": "^11.1.0",
"@langchain/core": "^0.1.8"
},
"patchedDependencies": {
"[email protected]": "patches/[email protected]",
Expand Down
41 changes: 0 additions & 41 deletions packages/@n8n/chat/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,4 @@ module.exports = {
extends: ['@n8n_io/eslint-config/frontend'],

...sharedOptions(__dirname, 'frontend'),

rules: {
'n8n-local-rules/dangerously-use-html-string-missing': 'off',

// TODO: Remove these
'id-denylist': 'warn',
'import/extensions': 'warn',
'import/no-default-export': 'warn',
'import/order': 'off',
'import/no-cycle': 'warn',
'import/no-duplicates': 'warn',
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/dot-notation': 'warn',
'@typescript-eslint/lines-between-class-members': 'warn',
'@typescript-eslint/member-delimiter-style': 'warn',
'@typescript-eslint/naming-convention': 'warn',
'@typescript-eslint/no-empty-interface': 'warn',
'@typescript-eslint/no-for-in-array': 'warn',
'@typescript-eslint/no-loop-func': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-shadow': 'warn',
'@typescript-eslint/no-this-alias': 'warn',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-use-before-define': 'warn',
'@typescript-eslint/no-var-requires': 'warn',
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
'@typescript-eslint/prefer-optional-chain': 'warn',
'@typescript-eslint/restrict-plus-operands': 'warn',
'@typescript-eslint/unbound-method': 'warn',
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
'@typescript-eslint/no-redundant-type-constituents': 'warn',
'@typescript-eslint/no-base-to-string': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
},
};
25 changes: 21 additions & 4 deletions packages/@n8n/chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
This is an embeddable Chat widget for n8n. It allows the execution of AI-Powered Workflows through a Chat window.

## Prerequisites
Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Webhook** node and return data using the **Respond to Webhook** node.
Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Chat Trigger** node.

Open the **Webhook** node and add your domain to the **Domain Allowlist** field. This makes sure that only requests from your domain are accepted.
Open the **Chat Trigger** node and add your domain to the **Allowed Origins (CORS)** field. This makes sure that only requests from your domain are accepted.

[See example workflow](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/chat/resources/workflow.json)

Expand All @@ -17,8 +17,6 @@ Each request is accompanied by an `action` query parameter, where `action` can b
- `loadPreviousSession` - When the user opens the Chatbot again and the previous chat session should be loaded
- `sendMessage` - When the user sends a message

We use the `Switch` node to handle the different actions.

## Installation

Open the **Webhook** node and replace `YOUR_PRODUCTION_WEBHOOK_URL` with your production URL. This is the URL that the Chat widget will use to send requests to.
Expand Down Expand Up @@ -106,6 +104,10 @@ createChat({
},
target: '#n8n-chat',
mode: 'window',
chatInputKey: 'chatInput',
chatSessionKey: 'sessionId',
metadata: {},
showWelcomeScreen: false,
defaultLanguage: 'en',
initialMessages: [
'Hi there! 👋',
Expand Down Expand Up @@ -148,6 +150,21 @@ createChat({
- In `window` mode, the Chat window will be embedded in the target element as a chat toggle button and a fixed size chat window.
- In `fullscreen` mode, the Chat will take up the entire width and height of its target container.

### `showWelcomeScreen`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Whether to show the welcome screen when the Chat window is opened.

### `chatSessionKey`
- **Type**: `string`
- **Default**: `'sessionId'`
- **Description**: The key to use for sending the chat history session ID for the AI Memory node.

### `chatInputKey`
- **Type**: `string`
- **Default**: `'chatInput'`
- **Description**: The key to use for sending the chat input for the AI Agent node.

### `defaultLanguage`
- **Type**: `string`
- **Default**: `'en'`
Expand Down
21 changes: 21 additions & 0 deletions packages/@n8n/chat/build.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineBuildConfig } from 'unbuild';

export default defineBuildConfig({
entries: [
{
builder: 'mkdist',
format: 'esm',
input: './src',
outDir: './tmp/lib',
},
{
builder: 'mkdist',
format: 'cjs',
input: './src',
outDir: './tmp/cjs',
},
],
clean: true,
declaration: true,
failOnWarn: false,
});
Loading

0 comments on commit a61a565

Please sign in to comment.