chore: merge master

This commit is contained in:
Alex Grozav 2023-06-20 13:04:23 +03:00
commit 080a421f02
139 changed files with 3682 additions and 4436 deletions

View file

@ -47,5 +47,5 @@ jobs:
run: exit 0 run: exit 0
- name: Fail job if run-e2e-tests failed - name: Fail job if run-e2e-tests failed
if: ${{ github.event.review.state != 'approved' || needs.run-e2e-tests.result == 'failure' }} if: ${{ (github.event.review.state != 'approved' && github.event.review.state != 'commented') || needs.run-e2e-tests.result == 'failure' }}
run: exit 1 run: exit 1

View file

@ -50,7 +50,7 @@ describe('NDV', () => {
workflowPage.getters.canvasNodes().last().dblclick(); workflowPage.getters.canvasNodes().last().dblclick();
ndv.getters.inputSelect().click(); ndv.getters.inputSelect().click();
ndv.getters.inputOption().last().click(); ndv.getters.inputOption().last().click();
ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist') ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist');
ndv.getters.inputDataContainer().should('contain', 'start'); ndv.getters.inputDataContainer().should('contain', 'start');
}); });
@ -96,10 +96,20 @@ describe('NDV', () => {
ndv.getters.container().should('be.visible'); ndv.getters.container().should('be.visible');
workflowPage.actions.saveWorkflowUsingKeyboardShortcut(); workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.getters.isWorkflowSaved(); workflowPage.getters.isWorkflowSaved();
}) });
describe('test output schema view', () => { describe('test output schema view', () => {
const schemaKeys = ['id', 'name', 'email', 'notes', 'country', 'created', 'objectValue', 'prop1', 'prop2']; const schemaKeys = [
'id',
'name',
'email',
'notes',
'country',
'created',
'objectValue',
'prop1',
'prop2',
];
function setupSchemaWorkflow() { function setupSchemaWorkflow() {
cy.createFixtureWorkflow('Test_workflow_schema_test.json', `NDV test schema view ${uuid()}`); cy.createFixtureWorkflow('Test_workflow_schema_test.json', `NDV test schema view ${uuid()}`);
workflowPage.actions.zoomToFit(); workflowPage.actions.zoomToFit();
@ -108,41 +118,62 @@ describe('NDV', () => {
} }
it('should switch to output schema view and validate it', () => { it('should switch to output schema view and validate it', () => {
setupSchemaWorkflow() setupSchemaWorkflow();
ndv.getters.outputDisplayMode().children().should('have.length', 3); ndv.getters.outputDisplayMode().children().should('have.length', 3);
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table'); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
ndv.getters.outputDisplayMode().contains('Schema').click(); ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
schemaKeys.forEach((key) => { schemaKeys.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('exist'); ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('exist');
}); });
}); });
it('should preserve schema view after execution', () => { it('should preserve schema view after execution', () => {
setupSchemaWorkflow() setupSchemaWorkflow();
ndv.getters.outputDisplayMode().contains('Schema').click(); ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.actions.execute(); ndv.actions.execute();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
}) });
it('should collapse and expand nested schema object', () => { it('should collapse and expand nested schema object', () => {
setupSchemaWorkflow() setupSchemaWorkflow();
const expandedObjectProps = ['prop1', 'prop2'];; const expandedObjectProps = ['prop1', 'prop2'];
const getObjectValueItem = () => ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').filter(':contains("objectValue")'); const getObjectValueItem = () =>
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.filter(':contains("objectValue")');
ndv.getters.outputDisplayMode().contains('Schema').click(); ndv.getters.outputDisplayMode().contains('Schema').click();
expandedObjectProps.forEach((key) => { expandedObjectProps.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('be.visible'); ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('be.visible');
}); });
getObjectValueItem().find('label').click(); getObjectValueItem().find('label').click();
expandedObjectProps.forEach((key) => { expandedObjectProps.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('not.be.visible'); ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('not.be.visible');
});
}); });
})
it('should not display pagination for schema', () => { it('should not display pagination for schema', () => {
setupSchemaWorkflow() setupSchemaWorkflow();
ndv.getters.backToCanvas().click(); ndv.getters.backToCanvas().click();
workflowPage.getters.canvasNodeByName('Set').click(); workflowPage.getters.canvasNodeByName('Set').click();
workflowPage.actions.addNodeToCanvas('Customer Datastore (n8n training)', true, true, 'Get All People'); workflowPage.actions.addNodeToCanvas(
'Customer Datastore (n8n training)',
true,
true,
'Get All People',
);
ndv.actions.execute(); ndv.actions.execute();
ndv.getters.outputPanel().contains('25 items').should('exist'); ndv.getters.outputPanel().contains('25 items').should('exist');
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
@ -150,9 +181,12 @@ describe('NDV', () => {
ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
ndv.getters.outputDisplayMode().contains('JSON').click(); ndv.getters.outputDisplayMode().contains('JSON').click();
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
}) });
it('should display large schema', () => { it('should display large schema', () => {
cy.createFixtureWorkflow('Test_workflow_schema_test_pinned_data.json', `NDV test schema view ${uuid()}`); cy.createFixtureWorkflow(
'Test_workflow_schema_test_pinned_data.json',
`NDV test schema view ${uuid()}`,
);
workflowPage.actions.zoomToFit(); workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set'); workflowPage.actions.openNode('Set');
@ -160,8 +194,11 @@ describe('NDV', () => {
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
ndv.getters.outputDisplayMode().contains('Schema').click(); ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]').should('have.length', 20); ndv.getters
}) .outputPanel()
.find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]')
.should('have.length', 20);
});
}); });
it('can link and unlink run selectors between input and output', () => { it('can link and unlink run selectors between input and output', () => {
@ -170,11 +207,13 @@ describe('NDV', () => {
workflowPage.actions.executeWorkflow(); workflowPage.actions.executeWorkflow();
workflowPage.actions.openNode('Set3'); workflowPage.actions.openNode('Set3');
ndv.getters.inputRunSelector() ndv.getters
.inputRunSelector()
.should('exist') .should('exist')
.find('input') .find('input')
.should('include.value', '2 of 2 (6 items)'); .should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector() ndv.getters
.outputRunSelector()
.should('exist') .should('exist')
.find('input') .find('input')
.should('include.value', '2 of 2 (6 items)'); .should('include.value', '2 of 2 (6 items)');
@ -183,23 +222,20 @@ describe('NDV', () => {
ndv.actions.switchOutputMode('Table'); ndv.actions.switchOutputMode('Table');
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector() ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111'); ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111'); ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)'); ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector() ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
.find('input')
.should('include.value', '2 of 2 (6 items)');
// unlink // unlink
ndv.actions.toggleOutputRunLinking(); ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector() ndv.getters
.inputRunSelector()
.should('exist') .should('exist')
.find('input') .find('input')
.should('include.value', '2 of 2 (6 items)'); .should('include.value', '2 of 2 (6 items)');
@ -207,24 +243,18 @@ describe('NDV', () => {
// link again // link again
ndv.actions.toggleOutputRunLinking(); ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.inputRunSelector() ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
.find('input')
.should('include.value', '1 of 2 (6 items)');
// unlink again // unlink again
ndv.actions.toggleInputRunLinking(); ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)'); ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector() ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
.find('input')
.should('include.value', '1 of 2 (6 items)');
// link again // link again
ndv.actions.toggleInputRunLinking(); ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.outputRunSelector() ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
.find('input')
.should('include.value', '2 of 2 (6 items)');
}); });
it('should display parameter hints correctly', () => { it('should display parameter hints correctly', () => {
@ -247,14 +277,12 @@ describe('NDV', () => {
input: ' test', input: ' test',
}, },
{ {
input: ' ' input: ' ',
}, },
{ {
input: '<div></div>' input: '<div></div>',
}, },
].forEach(({ input, output }) => { ].forEach(({ input, output }) => {
if (input) { if (input) {
ndv.actions.typeIntoParameterInput('value', input); ndv.actions.typeIntoParameterInput('value', input);
} }

View file

@ -77,22 +77,7 @@
"@types/json-diff": "^0.5.1", "@types/json-diff": "^0.5.1",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/localtunnel": "^1.9.0", "@types/localtunnel": "^1.9.0",
"@types/lodash.debounce": "^4.0.7", "@types/lodash": "^4.14.195",
"@types/lodash.difference": "^4",
"@types/lodash.get": "^4.4.6",
"@types/lodash.intersection": "^4.4.7",
"@types/lodash.iteratee": "^4.7.7",
"@types/lodash.merge": "^4.6.6",
"@types/lodash.omit": "^4.5.7",
"@types/lodash.pick": "^4.4.7",
"@types/lodash.remove": "^4.7.7",
"@types/lodash.set": "^4.3.6",
"@types/lodash.split": "^4.4.7",
"@types/lodash.unionby": "^4.8.7",
"@types/lodash.uniq": "^4.5.7",
"@types/lodash.uniqby": "^4.7.7",
"@types/lodash.unset": "^4.5.7",
"@types/lodash.without": "^4.4.7",
"@types/parseurl": "^1.3.1", "@types/parseurl": "^1.3.1",
"@types/passport-jwt": "^3.0.6", "@types/passport-jwt": "^3.0.6",
"@types/psl": "^1.1.0", "@types/psl": "^1.1.0",
@ -109,7 +94,6 @@
"@types/yamljs": "^0.2.31", "@types/yamljs": "^0.2.31",
"chokidar": "^3.5.2", "chokidar": "^3.5.2",
"concurrently": "^5.1.0", "concurrently": "^5.1.0",
"lodash.debounce": "^4.0.8",
"mock-jwks": "^1.0.9", "mock-jwks": "^1.0.9",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",
"run-script-os": "^1.0.7", "run-script-os": "^1.0.7",
@ -161,21 +145,7 @@
"jwks-rsa": "^3.0.1", "jwks-rsa": "^3.0.1",
"ldapts": "^4.2.6", "ldapts": "^4.2.6",
"localtunnel": "^2.0.0", "localtunnel": "^2.0.0",
"lodash.difference": "^4", "lodash": "^4.17.21",
"lodash.get": "^4.4.2",
"lodash.intersection": "^4.4.0",
"lodash.iteratee": "^4.7.0",
"lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.remove": "^4.7.0",
"lodash.set": "^4.3.2",
"lodash.split": "^4.4.2",
"lodash.unionby": "^4.8.0",
"lodash.uniq": "^4.5.0",
"lodash.uniqby": "^4.7.0",
"lodash.unset": "^4.5.2",
"lodash.without": "^4.4.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"mysql2": "~2.3.3", "mysql2": "~2.3.3",
"n8n-core": "workspace:*", "n8n-core": "workspace:*",

View file

@ -7,7 +7,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { Credentials, NodeExecuteFunctions } from 'n8n-core'; import { Credentials, NodeExecuteFunctions } from 'n8n-core';
import get from 'lodash.get'; import get from 'lodash/get';
import type { import type {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import curlconverter from 'curlconverter'; import curlconverter from 'curlconverter';
import get from 'lodash.get'; import get from 'lodash/get';
import type { IDataObject } from 'n8n-workflow'; import type { IDataObject } from 'n8n-workflow';
import { jsonParse } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow';

View file

@ -1,4 +1,4 @@
import uniq from 'lodash.uniq'; import uniq from 'lodash/uniq';
import glob from 'fast-glob'; import glob from 'fast-glob';
import type { DirectoryLoader, Types } from 'n8n-core'; import type { DirectoryLoader, Types } from 'n8n-core';
import { import {

View file

@ -1,6 +1,6 @@
import type { FindManyOptions, UpdateResult } from 'typeorm'; import type { FindManyOptions, UpdateResult } from 'typeorm';
import { In } from 'typeorm'; import { In } from 'typeorm';
import intersection from 'lodash.intersection'; import intersection from 'lodash/intersection';
import type { INode } from 'n8n-workflow'; import type { INode } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';

View file

@ -11,7 +11,7 @@ export const reloadNodesAndCredentials = async (
push: Push, push: Push,
) => { ) => {
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const { default: debounce } = await import('lodash.debounce'); const { default: debounce } = await import('lodash/debounce');
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const { watch } = await import('chokidar'); const { watch } = await import('chokidar');

View file

@ -14,7 +14,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable prefer-destructuring */ /* eslint-disable prefer-destructuring */
import type express from 'express'; import type express from 'express';
import get from 'lodash.get'; import get from 'lodash/get';
import stream from 'stream'; import stream from 'stream';
import { promisify } from 'util'; import { promisify } from 'util';
@ -426,7 +426,7 @@ export async function executeWebhook(
const binaryData = (response.body as IDataObject)?.binaryData as IBinaryData; const binaryData = (response.body as IDataObject)?.binaryData as IBinaryData;
if (binaryData?.id) { if (binaryData?.id) {
res.header(response.headers); res.header(response.headers);
const stream = NodeExecuteFunctions.getBinaryStream(binaryData.id); const stream = BinaryDataManager.getInstance().getBinaryStream(binaryData.id);
void pipeline(stream, res).then(() => void pipeline(stream, res).then(() =>
responseCallback(null, { noWebhookResponse: true }), responseCallback(null, { noWebhookResponse: true }),
); );
@ -643,10 +643,12 @@ export async function executeWebhook(
if (!didSendResponse) { if (!didSendResponse) {
// Send the webhook response manually // Send the webhook response manually
res.setHeader('Content-Type', binaryData.mimeType); res.setHeader('Content-Type', binaryData.mimeType);
const binaryDataBuffer = await BinaryDataManager.getInstance().retrieveBinaryData( if (binaryData.id) {
binaryData, const stream = BinaryDataManager.getInstance().getBinaryStream(binaryData.id);
); await pipeline(stream, res);
res.end(binaryDataBuffer); } else {
res.end(Buffer.from(binaryData.data, BINARY_ENCODING));
}
responseCallback(null, { responseCallback(null, {
noWebhookResponse: true, noWebhookResponse: true,

View file

@ -42,7 +42,7 @@ import {
WorkflowHooks, WorkflowHooks,
} from 'n8n-workflow'; } from 'n8n-workflow';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import type { FindOptionsWhere } from 'typeorm'; import type { FindOptionsWhere } from 'typeorm';
import { LessThanOrEqual, In } from 'typeorm'; import { LessThanOrEqual, In } from 'typeorm';
import { DateUtils } from 'typeorm/util/DateUtils'; import { DateUtils } from 'typeorm/util/DateUtils';

View file

@ -31,7 +31,7 @@ import config from '@/config';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { RoleRepository } from '@db/repositories'; import { RoleRepository } from '@db/repositories';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import { PermissionChecker } from './UserManagement/PermissionChecker'; import { PermissionChecker } from './UserManagement/PermissionChecker';
import { isWorkflowIdValid } from './utils'; import { isWorkflowIdValid } from './utils';
import { UserService } from './user/user.service'; import { UserService } from './user/user.service';

View file

@ -6,7 +6,7 @@ import type { ITaskData } from 'n8n-workflow';
import { sleep } from 'n8n-workflow'; import { sleep } from 'n8n-workflow';
import { sep } from 'path'; import { sep } from 'path';
import { diff } from 'json-diff'; import { diff } from 'json-diff';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import { ActiveExecutions } from '@/ActiveExecutions'; import { ActiveExecutions } from '@/ActiveExecutions';
import * as Db from '@/Db'; import * as Db from '@/Db';

View file

@ -1,4 +1,4 @@
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import { Authorized, Get, Post, Put, RestController } from '@/decorators'; import { Authorized, Get, Post, Put, RestController } from '@/decorators';
import { getLdapConfig, getLdapSynchronizations, updateLdapConfig } from '@/Ldap/helpers'; import { getLdapConfig, getLdapSynchronizations, updateLdapConfig } from '@/Ldap/helpers';
import { LdapService } from '@/Ldap/LdapService.ee'; import { LdapService } from '@/Ldap/LdapService.ee';

View file

@ -1,5 +1,5 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import get from 'lodash.get'; import get from 'lodash/get';
import { Request } from 'express'; import { Request } from 'express';
import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow'; import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
import { Authorized, Post, RestController } from '@/decorators'; import { Authorized, Post, RestController } from '@/decorators';

View file

@ -131,14 +131,14 @@ export class PasswordResetController {
const baseUrl = getInstanceBaseUrl(); const baseUrl = getInstanceBaseUrl();
const { id, firstName, lastName } = user; const { id, firstName, lastName } = user;
const url = UserService.generatePasswordResetUrl(user); const url = await UserService.generatePasswordResetUrl(user);
try { try {
await this.mailer.passwordReset({ await this.mailer.passwordReset({
email, email,
firstName, firstName,
lastName, lastName,
passwordResetUrl: url.toString(), passwordResetUrl: url,
domain: baseUrl, domain: baseUrl,
}); });
} catch (error) { } catch (error) {

View file

@ -2,11 +2,11 @@ import type { ClientOAuth2Options } from '@n8n/client-oauth2';
import { ClientOAuth2 } from '@n8n/client-oauth2'; import { ClientOAuth2 } from '@n8n/client-oauth2';
import Csrf from 'csrf'; import Csrf from 'csrf';
import express from 'express'; import express from 'express';
import get from 'lodash.get'; import get from 'lodash/get';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import set from 'lodash.set'; import set from 'lodash/set';
import split from 'lodash.split'; import split from 'lodash/split';
import unset from 'lodash.unset'; import unset from 'lodash/unset';
import { Credentials, UserSettings } from 'n8n-core'; import { Credentials, UserSettings } from 'n8n-core';
import type { import type {
WorkflowExecuteMode, WorkflowExecuteMode,

View file

@ -29,7 +29,7 @@ import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping'; import { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
import { TagEntity } from '@/databases/entities/TagEntity'; import { TagEntity } from '@/databases/entities/TagEntity';
import { ActiveWorkflowRunner } from '../../ActiveWorkflowRunner'; import { ActiveWorkflowRunner } from '../../ActiveWorkflowRunner';
import without from 'lodash.without'; import without from 'lodash/without';
import type { VersionControllPullOptions } from './types/versionControlPullWorkFolder'; import type { VersionControllPullOptions } from './types/versionControlPullWorkFolder';
import { versionControlFoldersExistCheck } from './versionControlHelper.ee'; import { versionControlFoldersExistCheck } from './versionControlHelper.ee';
import { In } from 'typeorm'; import { In } from 'typeorm';

View file

@ -15,7 +15,7 @@ import {
messageEventBusDestinationFromDb, messageEventBusDestinationFromDb,
incrementPrometheusMetric, incrementPrometheusMetric,
} from '../MessageEventBusDestination/Helpers.ee'; } from '../MessageEventBusDestination/Helpers.ee';
import uniqby from 'lodash.uniqby'; import uniqby from 'lodash/uniqBy';
import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm'; import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm';
import type { EventMessageAuditOptions } from '../EventMessageClasses/EventMessageAudit'; import type { EventMessageAuditOptions } from '../EventMessageClasses/EventMessageAudit';
import { EventMessageAudit } from '../EventMessageClasses/EventMessageAudit'; import { EventMessageAudit } from '../EventMessageClasses/EventMessageAudit';

View file

@ -7,7 +7,7 @@ import { Worker } from 'worker_threads';
import { createReadStream, existsSync, rmSync } from 'fs'; import { createReadStream, existsSync, rmSync } from 'fs';
import readline from 'readline'; import readline from 'readline';
import { jsonParse, LoggerProxy } from 'n8n-workflow'; import { jsonParse, LoggerProxy } from 'n8n-workflow';
import remove from 'lodash.remove'; import remove from 'lodash/remove';
import config from '@/config'; import config from '@/config';
import { getEventMessageObjectByType } from '../EventMessageClasses/Helpers'; import { getEventMessageObjectByType } from '../EventMessageClasses/Helpers';
import type { EventMessageReturnMode } from '../MessageEventBus/MessageEventBus'; import type { EventMessageReturnMode } from '../MessageEventBus/MessageEventBus';

View file

@ -4,7 +4,7 @@ import type { INode, IPinData, JsonObject } from 'n8n-workflow';
import { NodeApiError, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow'; import { NodeApiError, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow';
import type { FindOptionsSelect, FindOptionsWhere, UpdateResult } from 'typeorm'; import type { FindOptionsSelect, FindOptionsWhere, UpdateResult } from 'typeorm';
import { In } from 'typeorm'; import { In } from 'typeorm';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import * as Db from '@/Db'; import * as Db from '@/Db';

View file

@ -5,7 +5,7 @@ import { existsSync } from 'fs';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import { CronJob } from 'cron'; import { CronJob } from 'cron';
import express from 'express'; import express from 'express';
import set from 'lodash.set'; import set from 'lodash/set';
import { BinaryDataManager, UserSettings } from 'n8n-core'; import { BinaryDataManager, UserSettings } from 'n8n-core';
import type { import type {
ICredentialType, ICredentialType,

View file

@ -38,8 +38,7 @@
"@types/cron": "~1.7.1", "@types/cron": "~1.7.1",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.1",
"@types/express": "^4.17.6", "@types/express": "^4.17.6",
"@types/lodash.get": "^4.4.6", "@types/lodash": "^4.14.195",
"@types/lodash.pick": "^4.4.7",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/request-promise-native": "~1.0.15", "@types/request-promise-native": "~1.0.15",
"@types/uuid": "^8.3.2" "@types/uuid": "^8.3.2"
@ -54,8 +53,7 @@
"file-type": "^16.5.4", "file-type": "^16.5.4",
"flatted": "^3.2.4", "flatted": "^3.2.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"lodash.get": "^4.4.2", "lodash": "^4.17.21",
"lodash.pick": "^4.4.0",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"n8n-workflow": "workspace:*", "n8n-workflow": "workspace:*",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",

View file

@ -121,7 +121,7 @@ export class BinaryDataManager {
throw new Error('Storage mode used to store binary data not available'); throw new Error('Storage mode used to store binary data not available');
} }
async retrieveBinaryData(binaryData: IBinaryData): Promise<Buffer> { async getBinaryDataBuffer(binaryData: IBinaryData): Promise<Buffer> {
if (binaryData.id) { if (binaryData.id) {
return this.retrieveBinaryDataByIdentifier(binaryData.id); return this.retrieveBinaryDataByIdentifier(binaryData.id);
} }

View file

@ -79,7 +79,7 @@ import {
validateFieldType, validateFieldType,
} from 'n8n-workflow'; } from 'n8n-workflow';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import { Agent } from 'https'; import { Agent } from 'https';
import { IncomingMessage } from 'http'; import { IncomingMessage } from 'http';
import { stringify } from 'qs'; import { stringify } from 'qs';
@ -92,7 +92,7 @@ import type {
} from '@n8n/client-oauth2'; } from '@n8n/client-oauth2';
import { ClientOAuth2 } from '@n8n/client-oauth2'; import { ClientOAuth2 } from '@n8n/client-oauth2';
import crypto, { createHmac } from 'crypto'; import crypto, { createHmac } from 'crypto';
import get from 'lodash.get'; import get from 'lodash/get';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import FormData from 'form-data'; import FormData from 'form-data';
import path from 'path'; import path from 'path';
@ -747,6 +747,8 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
auth, auth,
proxy, proxy,
url, url,
maxBodyLength: Infinity,
maxContentLength: Infinity,
} as AxiosRequestConfig; } as AxiosRequestConfig;
axiosRequest.params = n8nRequest.qs; axiosRequest.params = n8nRequest.qs;
@ -915,7 +917,7 @@ export async function getBinaryDataBuffer(
inputIndex: number, inputIndex: number,
): Promise<Buffer> { ): Promise<Buffer> {
const binaryData = inputData.main[inputIndex]![itemIndex]!.binary![propertyName]!; const binaryData = inputData.main[inputIndex]![itemIndex]!.binary![propertyName]!;
return BinaryDataManager.getInstance().retrieveBinaryData(binaryData); return BinaryDataManager.getInstance().getBinaryDataBuffer(binaryData);
} }
/** /**

View file

@ -37,7 +37,7 @@ import type {
WorkflowExecuteMode, WorkflowExecuteMode,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { LoggerProxy as Logger, WorkflowOperationError } from 'n8n-workflow'; import { LoggerProxy as Logger, WorkflowOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
import * as NodeExecuteFunctions from './NodeExecuteFunctions'; import * as NodeExecuteFunctions from './NodeExecuteFunctions';
export class WorkflowExecute { export class WorkflowExecute {

View file

@ -1,4 +1,4 @@
import set from 'lodash.set'; import set from 'lodash/set';
import type { import type {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,

View file

@ -84,18 +84,6 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
label: `${prefix}jmespath()`, label: `${prefix}jmespath()`,
info: this.$locale.baseText('codeNodeEditor.completer.$jmespath'), info: this.$locale.baseText('codeNodeEditor.completer.$jmespath'),
}, },
{
label: `${prefix}if()`,
info: this.$locale.baseText('codeNodeEditor.completer.$if'),
},
{
label: `${prefix}min()`,
info: this.$locale.baseText('codeNodeEditor.completer.$min'),
},
{
label: `${prefix}max()`,
info: this.$locale.baseText('codeNodeEditor.completer.$max'),
},
{ {
label: `${prefix}runIndex`, label: `${prefix}runIndex`,
info: this.$locale.baseText('codeNodeEditor.completer.$runIndex'), info: this.$locale.baseText('codeNodeEditor.completer.$runIndex'),

View file

@ -5,25 +5,35 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import emitter from '@/mixins/emitter'; import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({ export default defineComponent({
name: 'IntersectionObserved', name: 'IntersectionObserved',
mixins: [emitter], props: {
props: ['enabled'], enabled: {
type: Boolean,
default: false,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
mounted() { mounted() {
if (!this.enabled) { if (!this.enabled) {
return; return;
} }
this.$nextTick(() => { this.$nextTick(() => {
this.$dispatch('IntersectionObserver', 'observe', this.$refs.observed); this.eventBus.emit('observe', this.$refs.observed);
}); });
}, },
beforeDestroy() { beforeDestroy() {
if (this.enabled) { if (this.enabled) {
this.$dispatch('IntersectionObserver', 'unobserve', this.$refs.observed); this.eventBus.emit('unobserve', this.$refs.observed);
} }
}, },
}); });

View file

@ -5,11 +5,27 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({ export default defineComponent({
name: 'IntersectionObserver', name: 'IntersectionObserver',
props: ['threshold', 'enabled'], props: {
threshold: {
type: Number,
default: 0,
},
enabled: {
type: Boolean,
default: false,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
data() { data() {
return { return {
observer: null, observer: null,
@ -35,13 +51,13 @@ export default defineComponent({
}); });
}, options); }, options);
this.$data.observer = observer; this.observer = observer;
this.$on('observe', (observed: Element) => { this.eventBus.on('observe', (observed: Element) => {
observer.observe(observed); observer.observe(observed);
}); });
this.$on('unobserve', (observed: Element) => { this.eventBus.on('unobserve', (observed: Element) => {
observer.unobserve(observed); observer.unobserve(observed);
}); });
}, },

View file

@ -38,18 +38,6 @@
@update:modelValue="onInput" @update:modelValue="onInput"
@submit="onSubmit" @submit="onSubmit"
/> />
<n8n-info-tip v-if="!settingsStore.isSmtpSetup" class="mt-s">
<i18n path="settings.users.setupSMTPInfo">
<template #link>
<a
href="https://docs.n8n.io/reference/user-management.html#step-one-smtp"
target="_blank"
>
{{ $locale.baseText('settings.users.setupSMTPInfo.link') }}
</a>
</template>
</i18n>
</n8n-info-tip>
</template> </template>
<template v-if="!showInviteUrls" #footer> <template v-if="!showInviteUrls" #footer>
<n8n-button <n8n-button

View file

@ -166,6 +166,7 @@ import type { IPermissions } from '@/permissions';
import { getWorkflowPermissions } from '@/permissions'; import { getWorkflowPermissions } from '@/permissions';
import { createEventBus } from 'n8n-design-system'; import { createEventBus } from 'n8n-design-system';
import { useCloudPlanStore } from '@/stores'; import { useCloudPlanStore } from '@/stores';
import { nodeViewEventBus } from '@/event-bus';
const hasChanged = (prev: string[], curr: string[]) => { const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) { if (prev.length !== curr.length) {
@ -445,7 +446,7 @@ export default defineComponent({
return; return;
} }
this.$root.$emit('importWorkflowData', { data: workflowData }); nodeViewEventBus.emit('importWorkflowData', { data: workflowData });
}; };
const inputRef = this.$refs.importFile as HTMLInputElement | undefined; const inputRef = this.$refs.importFile as HTMLInputElement | undefined;
@ -505,7 +506,7 @@ export default defineComponent({
}, },
)) as MessageBoxInputData; )) as MessageBoxInputData;
this.$root.$emit('importWorkflowUrl', { url: promptResponse.value }); nodeViewEventBus.emit('importWorkflowUrl', { url: promptResponse.value });
} catch (e) {} } catch (e) {}
break; break;
} }

View file

@ -975,6 +975,8 @@ export default defineComponent({
<style lang="scss"> <style lang="scss">
.node-settings { .node-settings {
display: flex;
flex-direction: column;
overflow: hidden; overflow: hidden;
background-color: var(--color-background-xlight); background-color: var(--color-background-xlight);
height: 100%; height: 100%;
@ -1007,7 +1009,6 @@ export default defineComponent({
} }
.node-parameters-wrapper { .node-parameters-wrapper {
height: 100%;
overflow-y: auto; overflow-y: auto;
padding: 0 20px 200px 20px; padding: 0 20px 200px 20px;
} }

View file

@ -28,6 +28,7 @@
:droppable="droppable" :droppable="droppable"
:node="node" :node="node"
:path="path" :path="path"
:event-bus="eventBus"
@input="valueChanged" @input="valueChanged"
@modalOpenerClick="openExpressionEditorModal" @modalOpenerClick="openExpressionEditorModal"
@focus="setFocus" @focus="setFocus"
@ -390,8 +391,8 @@ import { useCredentialsStore } from '@/stores/credentials.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { htmlEditorEventBus } from '@/event-bus'; import { htmlEditorEventBus } from '@/event-bus';
import Vue from 'vue'; import Vue from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
type ResourceLocatorRef = InstanceType<typeof ResourceLocator>; import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({ export default defineComponent({
name: 'parameter-input', name: 'parameter-input',
@ -462,6 +463,10 @@ export default defineComponent({
size: 'small', size: 'small',
}), }),
}, },
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
}, },
data() { data() {
return { return {
@ -1111,9 +1116,7 @@ export default defineComponent({
} }
} else if (command === 'refreshOptions') { } else if (command === 'refreshOptions') {
if (this.isResourceLocatorParameter) { if (this.isResourceLocatorParameter) {
const resourceLocatorRef = this.$refs.resourceLocator as ResourceLocatorRef | undefined; this.eventBus.emit('refreshList');
resourceLocatorRef?.$emit('refreshList');
} }
void this.loadRemoteParameterOptions(); void this.loadRemoteParameterOptions();
} else if (command === 'formatHtml') { } else if (command === 'formatHtml') {
@ -1145,7 +1148,7 @@ export default defineComponent({
}); });
}, },
mounted() { mounted() {
this.$on('optionSelected', this.optionSelected); this.eventBus.on('optionSelected', this.optionSelected);
this.tempValue = this.displayValue as string; this.tempValue = this.displayValue as string;
if (this.node !== null) { if (this.node !== null) {
@ -1185,6 +1188,9 @@ export default defineComponent({
inputFieldRef: this.$refs['inputField'], inputFieldRef: this.$refs['inputField'],
}); });
}, },
beforeDestroy() {
this.eventBus.off('optionSelected', this.optionSelected);
},
}); });
</script> </script>

View file

@ -32,6 +32,7 @@
:isForCredential="true" :isForCredential="true"
:eventSource="eventSource" :eventSource="eventSource"
:hint="!showRequiredErrors ? hint : ''" :hint="!showRequiredErrors ? hint : ''"
:event-bus="eventBus"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
@textInput="valueChanged" @textInput="valueChanged"
@ -65,6 +66,7 @@ import { isValueExpression } from '@/utils';
import type { INodeParameterResourceLocator, INodeProperties, IParameterLabel } from 'n8n-workflow'; import type { INodeParameterResourceLocator, INodeProperties, IParameterLabel } from 'n8n-workflow';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { createEventBus } from 'n8n-design-system/utils';
type ParamRef = InstanceType<typeof ParameterInputWrapper>; type ParamRef = InstanceType<typeof ParameterInputWrapper>;
@ -100,6 +102,7 @@ export default defineComponent({
focused: false, focused: false,
blurredEver: false, blurredEver: false,
menuExpanded: false, menuExpanded: false,
eventBus: createEventBus(),
}; };
}, },
computed: { computed: {
@ -147,9 +150,7 @@ export default defineComponent({
this.menuExpanded = expanded; this.menuExpanded = expanded;
}, },
optionSelected(command: string) { optionSelected(command: string) {
if (this.$refs.param) { this.eventBus.emit('optionSelected', command);
(this.$refs.param as ParamRef).$emit('optionSelected', command);
}
}, },
valueChanged(parameterData: IUpdateInformation) { valueChanged(parameterData: IUpdateInformation) {
this.$emit('change', parameterData); this.$emit('change', parameterData);

View file

@ -56,6 +56,7 @@
:hint="hint" :hint="hint"
:hide-issues="hideIssues" :hide-issues="hideIssues"
:label="label" :label="label"
:event-bus="eventBus"
@valueChanged="valueChanged" @valueChanged="valueChanged"
@textInput="onTextInput" @textInput="onTextInput"
@focus="onFocus" @focus="onFocus"
@ -98,6 +99,7 @@ import { useNDVStore } from '@/stores/ndv.store';
import { useSegment } from '@/stores/segment.store'; import { useSegment } from '@/stores/segment.store';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { getMappedResult } from '@/utils/mappingUtils'; import { getMappedResult } from '@/utils/mappingUtils';
import { createEventBus } from 'n8n-design-system/utils';
type ParameterInputWrapperRef = InstanceType<typeof ParameterInputWrapper>; type ParameterInputWrapperRef = InstanceType<typeof ParameterInputWrapper>;
@ -112,7 +114,10 @@ export default defineComponent({
ParameterInputWrapper, ParameterInputWrapper,
}, },
setup() { setup() {
const eventBus = createEventBus();
return { return {
eventBus,
...useToast(), ...useToast(),
}; };
}, },
@ -234,17 +239,14 @@ export default defineComponent({
this.menuExpanded = expanded; this.menuExpanded = expanded;
}, },
optionSelected(command: string) { optionSelected(command: string) {
const paramRef = this.$refs.param as ParameterInputWrapperRef | undefined; this.eventBus.emit('optionSelected', command);
paramRef?.$emit('optionSelected', command);
}, },
valueChanged(parameterData: IUpdateInformation) { valueChanged(parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData); this.$emit('valueChanged', parameterData);
}, },
onTextInput(parameterData: IUpdateInformation) { onTextInput(parameterData: IUpdateInformation) {
const paramRef = this.$refs.param as ParameterInputWrapperRef | undefined;
if (isValueExpression(this.parameter, parameterData.value)) { if (isValueExpression(this.parameter, parameterData.value)) {
paramRef?.$emit('optionSelected', 'addExpression'); this.eventBus.emit('optionSelected', 'addExpression');
} }
}, },
onDrop(newParamValue: string) { onDrop(newParamValue: string) {

View file

@ -18,6 +18,7 @@
:expressionEvaluated="expressionValueComputed" :expressionEvaluated="expressionValueComputed"
:label="label" :label="label"
:data-test-id="`parameter-input-${parameter.name}`" :data-test-id="`parameter-input-${parameter.name}`"
:event-bus="internalEventBus"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
@drop="onDrop" @drop="onDrop"
@ -61,8 +62,8 @@ import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers'; import { workflowHelpers } from '@/mixins/workflowHelpers';
import { isValueExpression } from '@/utils'; import { isValueExpression } from '@/utils';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import type { EventBus } from 'n8n-design-system/utils';
type ParamRef = InstanceType<typeof ParameterInput>; import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({ export default defineComponent({
name: 'parameter-input-wrapper', name: 'parameter-input-wrapper',
@ -71,8 +72,16 @@ export default defineComponent({
ParameterInput, ParameterInput,
InputHint, InputHint,
}, },
data() {
return {
internalEventBus: createEventBus(),
};
},
mounted() { mounted() {
this.$on('optionSelected', this.optionSelected); this.eventBus.on('optionSelected', this.optionSelected);
},
beforeDestroy() {
this.eventBus.off('optionSelected', this.optionSelected);
}, },
props: { props: {
isReadOnly: { isReadOnly: {
@ -124,6 +133,10 @@ export default defineComponent({
size: 'small', size: 'small',
}), }),
}, },
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
}, },
computed: { computed: {
...mapStores(useNDVStore), ...mapStores(useNDVStore),
@ -217,9 +230,7 @@ export default defineComponent({
this.$emit('drop', data); this.$emit('drop', data);
}, },
optionSelected(command: string) { optionSelected(command: string) {
const paramRef = this.$refs.param as ParamRef | undefined; this.internalEventBus.emit('optionSelected', command);
paramRef?.$emit('optionSelected', command);
}, },
onValueChanged(parameterData: IUpdateInformation) { onValueChanged(parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData); this.$emit('valueChanged', parameterData);

View file

@ -15,11 +15,11 @@
:hasMore="currentQueryHasMore" :hasMore="currentQueryHasMore"
:errorView="currentQueryError" :errorView="currentQueryError"
:width="width" :width="width"
:event-bus="eventBus"
@input="onListItemSelected" @input="onListItemSelected"
@hide="onDropdownHide" @hide="onDropdownHide"
@filter="onSearchFilter" @filter="onSearchFilter"
@loadMore="loadResourcesDebounced" @loadMore="loadResourcesDebounced"
ref="dropdown"
> >
<template #error> <template #error>
<div :class="$style.error" data-test-id="rlc-error-container"> <div :class="$style.error" data-test-id="rlc-error-container">
@ -176,8 +176,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/n8nRoot.store'; import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { createEventBus } from 'n8n-design-system/utils';
type ResourceLocatorDropdownRef = InstanceType<typeof ResourceLocatorDropdown>; import type { EventBus } from 'n8n-design-system/utils';
interface IResourceLocatorQuery { interface IResourceLocatorQuery {
results: INodeListSearchItems[]; results: INodeListSearchItems[];
@ -256,6 +256,10 @@ export default defineComponent({
loadOptionsMethod: { loadOptionsMethod: {
type: String, type: String,
}, },
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
}, },
data() { data() {
return { return {
@ -475,17 +479,20 @@ export default defineComponent({
}, },
}, },
mounted() { mounted() {
this.$on('refreshList', this.refreshList); this.eventBus.on('refreshList', this.refreshList);
window.addEventListener('resize', this.setWidth); window.addEventListener('resize', this.setWidth);
useNDVStore().$subscribe((mutation, state) => { useNDVStore().$subscribe((mutation, state) => {
// Update the width when main panel dimension change // Update the width when main panel dimension change
this.setWidth(); this.setWidth();
}); });
setTimeout(() => { setTimeout(() => {
this.setWidth(); this.setWidth();
}, 0); }, 0);
}, },
beforeDestroy() { beforeDestroy() {
this.eventBus.off('refreshList', this.refreshList);
window.removeEventListener('resize', this.setWidth); window.removeEventListener('resize', this.setWidth);
}, },
methods: { methods: {
@ -510,9 +517,8 @@ export default defineComponent({
this.trackEvent('User refreshed resource locator list'); this.trackEvent('User refreshed resource locator list');
}, },
onKeyDown(e: MouseEvent) { onKeyDown(e: MouseEvent) {
const dropdownRef = this.$refs.dropdown as ResourceLocatorDropdownRef | undefined; if (this.showResourceDropdown && !this.isSearchable) {
if (dropdownRef && this.showResourceDropdown && !this.isSearchable) { this.eventBus.emit('keyDown', e);
dropdownRef.$emit('keyDown', e);
} }
}, },
openResource(url: string) { openResource(url: string) {

View file

@ -82,6 +82,8 @@
import type { IResourceLocatorResultExpanded } from '@/Interface'; import type { IResourceLocatorResultExpanded } from '@/Interface';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
const SEARCH_BAR_HEIGHT_PX = 40; const SEARCH_BAR_HEIGHT_PX = 40;
const SCROLL_MARGIN_PX = 10; const SCROLL_MARGIN_PX = 10;
@ -120,6 +122,10 @@ export default defineComponent({
width: { width: {
type: Number, type: Number,
}, },
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
}, },
data() { data() {
return { return {
@ -128,7 +134,10 @@ export default defineComponent({
}; };
}, },
mounted() { mounted() {
this.$on('keyDown', this.onKeyDown); this.eventBus.on('keyDown', this.onKeyDown);
},
beforeDestroy() {
this.eventBus.off('keyDown', this.onKeyDown);
}, },
computed: { computed: {
sortedResources(): IResourceLocatorResultExpanded[] { sortedResources(): IResourceLocatorResultExpanded[] {

View file

@ -1,11 +1,26 @@
<template> <template>
<div class="__html-display ph-no-capture" v-html="html"></div> <iframe class="__html-display ph-no-capture" :srcdoc="html" />
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import sanitizeHtml, { defaults, type IOptions as SanitizeOptions } from 'sanitize-html';
import type { INodeExecutionData } from 'n8n-workflow'; import type { INodeExecutionData } from 'n8n-workflow';
const sanitizeOptions: SanitizeOptions = {
allowVulnerableTags: false,
enforceHtmlBoundary: false,
disallowedTagsMode: 'discard',
allowedTags: [...defaults.allowedTags, 'style', 'img', 'title'],
allowedAttributes: {
...defaults.allowedAttributes,
'*': ['class', 'style'],
},
transformTags: {
head: '',
},
};
export default { export default {
name: 'RunDataHtml', name: 'RunDataHtml',
props: { props: {
@ -15,29 +30,8 @@ export default {
}, },
computed: { computed: {
html() { html() {
if (!this.inputData) return ''; const markup = (this.inputData?.[0].json.html as string) ?? '';
return sanitizeHtml(markup, sanitizeOptions);
return this.scopeCss(this.inputData[0].json.html as string);
},
},
methods: {
/**
* Scope all CSS selectors to prevent user stylesheets from leaking.
*/
scopeCss(str: string) {
const stylesheets = str.match(/<style>([\s\S]*?)<\/style>/g);
if (!stylesheets) return str;
const map = stylesheets.reduce<Record<string, string>>((acc, match) => {
match.split('\n').forEach((line) => {
if (line.endsWith('{')) acc[line] = ['.__html-display', line].join(' ');
});
return acc;
}, {});
return Object.entries(map).reduce((acc, [key, value]) => acc.replace(key, value), str);
}, },
}, },
}; };
@ -45,6 +39,7 @@ export default {
<style lang="scss"> <style lang="scss">
.__html-display { .__html-display {
padding: 0 var(--spacing-s); width: 100%;
height: 100%;
} }
</style> </style>

View file

@ -4,6 +4,7 @@
@observed="onObserved" @observed="onObserved"
class="tags-container" class="tags-container"
:enabled="responsive" :enabled="responsive"
:event-bus="intersectionEventBus"
> >
<template> <template>
<span class="tags"> <span class="tags">
@ -26,6 +27,7 @@
:class="{ hidden: tag.hidden }" :class="{ hidden: tag.hidden }"
:data-id="tag.id" :data-id="tag.id"
:enabled="responsive" :enabled="responsive"
:event-bus="intersectionEventBus"
v-else v-else
> >
<el-tag :title="tag.name" type="info" size="small" :class="{ hoverable }"> <el-tag :title="tag.name" type="info" size="small" :class="{ hoverable }">
@ -46,6 +48,7 @@ import IntersectionObserver from './IntersectionObserver.vue';
import IntersectionObserved from './IntersectionObserved.vue'; import IntersectionObserved from './IntersectionObserved.vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useTagsStore } from '@/stores/tags.store'; import { useTagsStore } from '@/stores/tags.store';
import { createEventBus } from 'n8n-design-system/utils';
// random upper limit if none is set to minimize performance impact of observers // random upper limit if none is set to minimize performance impact of observers
const DEFAULT_MAX_TAGS_LIMIT = 20; const DEFAULT_MAX_TAGS_LIMIT = 20;
@ -62,6 +65,7 @@ export default defineComponent({
props: ['tagIds', 'limit', 'clickable', 'responsive', 'hoverable'], props: ['tagIds', 'limit', 'clickable', 'responsive', 'hoverable'],
data() { data() {
return { return {
intersectionEventBus: createEventBus(),
visibility: {} as { [id: string]: boolean }, visibility: {} as { [id: string]: boolean },
}; };
}, },

View file

@ -3,13 +3,14 @@
* unsafe onclick attribute * unsafe onclick attribute
*/ */
import { reactive, del, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'; import { reactive, del, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { globalLinkActionsEventBus } from '@/event-bus';
const state = reactive({ const state = reactive({
customActions: {} as Record<string, Function>, customActions: {} as Record<string, Function>,
}); });
export default () => { export default () => {
function registerCustomAction(key: string, action: Function) { function registerCustomAction({ key, action }: { key: string; action: Function }) {
state.customActions[key] = action; state.customActions[key] = action;
} }
function unregisterCustomAction(key: string) { function unregisterCustomAction(key: string) {
@ -42,13 +43,15 @@ export default () => {
onMounted(() => { onMounted(() => {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
window.addEventListener('click', delegateClick); window.addEventListener('click', delegateClick);
instance?.proxy.$root.$on('registerGlobalLinkAction', registerCustomAction);
globalLinkActionsEventBus.on('registerGlobalLinkAction', registerCustomAction);
}); });
onUnmounted(() => { onUnmounted(() => {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
window.removeEventListener('click', delegateClick); window.removeEventListener('click', delegateClick);
instance?.proxy.$root.$off('registerGlobalLinkAction', registerCustomAction);
globalLinkActionsEventBus.off('registerGlobalLinkAction', registerCustomAction);
}); });
return { return {

View file

@ -1,4 +1,5 @@
export * from './code-node-editor'; export * from './code-node-editor';
export * from './data-pinning'; export * from './data-pinning';
export * from './link-actions';
export * from './html-editor'; export * from './html-editor';
export * from './node-view'; export * from './node-view';

View file

@ -0,0 +1,3 @@
import { createEventBus } from 'n8n-design-system';
export const globalLinkActionsEventBus = createEventBus();

View file

@ -1,50 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { defineComponent } from 'vue';
function broadcast(
this: InstanceType<typeof EmitterMixin>,
componentName: string,
eventName: string,
params: any,
) {
this.$children.forEach((child) => {
const name = child.$options.name;
if (name === componentName) {
// eslint-disable-next-line prefer-spread
child.$emit.apply(child, [eventName].concat(params) as Parameters<typeof child.$emit>);
} else {
broadcast.apply(
child as InstanceType<typeof EmitterMixin>,
[componentName, eventName].concat([params]) as Parameters<typeof broadcast>,
);
}
});
}
const EmitterMixin = defineComponent({
methods: {
$dispatch(componentName: string, eventName: string, params: any) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent as InstanceType<typeof EmitterMixin>;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
// eslint-disable-next-line prefer-spread
parent.$emit.apply(parent, [eventName].concat(params) as Parameters<typeof parent.$emit>);
}
},
$broadcast(componentName: string, eventName: string, params: any) {
broadcast.call(this, componentName, eventName, params);
},
},
});
export default EmitterMixin;

View file

@ -24,7 +24,7 @@ import { TelemetryHelpers } from 'n8n-workflow';
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { getTriggerNodeServiceName } from '@/utils'; import { getTriggerNodeServiceName } from '@/utils';
import { codeNodeEditorEventBus } from '@/event-bus'; import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
@ -351,9 +351,12 @@ export const pushConnection = defineComponent({
let action; let action;
if (!isSavingExecutions) { if (!isSavingExecutions) {
this.$root.$emit('registerGlobalLinkAction', 'open-settings', async () => { globalLinkActionsEventBus.emit('registerGlobalLinkAction', {
key: 'open-settings',
action: async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow(); if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
}); });
action = action =

View file

@ -1231,8 +1231,6 @@
"settings.users.setupMyAccount": "Set up my owner account", "settings.users.setupMyAccount": "Set up my owner account",
"settings.users.setupToInviteUsers": "To invite users, set up your own account", "settings.users.setupToInviteUsers": "To invite users, set up your own account",
"settings.users.setupToInviteUsersInfo": "Invited users wont be able to see workflows and credentials of other users unless you upgrade. <a href=\"https://docs.n8n.io/user-management/\" target=\"_blank\">More info</a> <br /> <br />", "settings.users.setupToInviteUsersInfo": "Invited users wont be able to see workflows and credentials of other users unless you upgrade. <a href=\"https://docs.n8n.io/user-management/\" target=\"_blank\">More info</a> <br /> <br />",
"settings.users.setupSMTPInfo": "You will need details of an {link} to complete the setup.",
"settings.users.setupSMTPInfo.link": "SMTP server",
"settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). <a target=\"_blank\" href=\"https://docs.n8n.io/hosting/authentication/user-management-self-hosted/\">Instructions</a>", "settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). <a target=\"_blank\" href=\"https://docs.n8n.io/hosting/authentication/user-management-self-hosted/\">Instructions</a>",
"settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user", "settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user",
"settings.users.transferredToUser": "Data transferred to {user}", "settings.users.transferredToUser": "Data transferred to {user}",

View file

@ -672,9 +672,12 @@ export default defineComponent({
? this.$locale.baseText('nodeView.addOrEnableTriggerNode') ? this.$locale.baseText('nodeView.addOrEnableTriggerNode')
: this.$locale.baseText('nodeView.addATriggerNodeFirst'); : this.$locale.baseText('nodeView.addATriggerNodeFirst');
this.registerCustomAction('showNodeCreator', () => this.registerCustomAction({
key: 'showNodeCreator',
action: () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP), this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
); });
const notice = this.showMessage({ const notice = this.showMessage({
type: 'info', type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'), title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
@ -1050,7 +1053,7 @@ export default defineComponent({
} }
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) { if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow'); nodeViewEventBus.emit('newWorkflow');
} else { } else {
void this.$router.push({ name: VIEWS.NEW_WORKFLOW }); void this.$router.push({ name: VIEWS.NEW_WORKFLOW });
} }
@ -3913,9 +3916,9 @@ export default defineComponent({
window.addEventListener('message', this.onPostMessageReceived); window.addEventListener('message', this.onPostMessageReceived);
window.addEventListener('pageshow', this.onPageShow); window.addEventListener('pageshow', this.onPageShow);
this.$root.$on('newWorkflow', this.newWorkflow); nodeViewEventBus.on('newWorkflow', this.newWorkflow);
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent); nodeViewEventBus.on('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent); nodeViewEventBus.on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.on('nodeMove', this.onMoveNode); historyBus.on('nodeMove', this.onMoveNode);
historyBus.on('revertAddNode', this.onRevertAddNode); historyBus.on('revertAddNode', this.onRevertAddNode);
historyBus.on('revertRemoveNode', this.onRevertRemoveNode); historyBus.on('revertRemoveNode', this.onRevertRemoveNode);
@ -3938,9 +3941,9 @@ export default defineComponent({
window.removeEventListener('beforeunload', this.onBeforeUnload); window.removeEventListener('beforeunload', this.onBeforeUnload);
window.removeEventListener('pageshow', this.onPageShow); window.removeEventListener('pageshow', this.onPageShow);
this.$root.$off('newWorkflow', this.newWorkflow); nodeViewEventBus.off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent); nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent); nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.off('nodeMove', this.onMoveNode); historyBus.off('nodeMove', this.onMoveNode);
historyBus.off('revertAddNode', this.onRevertAddNode); historyBus.off('revertAddNode', this.onRevertAddNode);
historyBus.off('revertRemoveNode', this.onRevertRemoveNode); historyBus.off('revertRemoveNode', this.onRevertRemoveNode);
@ -3959,9 +3962,9 @@ export default defineComponent({
this.instance.destroy(); this.instance.destroy();
this.uiStore.stateIsDirty = false; this.uiStore.stateIsDirty = false;
window.removeEventListener('message', this.onPostMessageReceived); window.removeEventListener('message', this.onPostMessageReceived);
this.$root.$off('newWorkflow', this.newWorkflow); nodeViewEventBus.off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent); nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent); nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID); this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
}, },
}); });

View file

@ -117,7 +117,7 @@ const repoUrlValidationRules: Array<Rule | RuleGroup> = [
{ {
name: 'MATCH_REGEX', name: 'MATCH_REGEX',
config: { config: {
regex: /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/, regex: /^(?!https?:\/\/)(?:git|ssh|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/,
message: locale.baseText('settings.versionControl.repoUrlInvalid'), message: locale.baseText('settings.versionControl.repoUrlInvalid'),
}, },
}, },

View file

@ -2,8 +2,8 @@ import type { IDataObject, IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-
import type { OptionsWithUri } from 'request'; import type { OptionsWithUri } from 'request';
import flow from 'lodash.flow'; import flow from 'lodash/flow';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import type { import type {
AllFieldsUi, AllFieldsUi,

View file

@ -8,7 +8,7 @@ import type {
INodePropertyOptions, INodePropertyOptions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
/** /**
* Make an API request to Asana * Make an API request to Asana

View file

@ -12,7 +12,7 @@ import { jsonParse, NodeOperationError } from 'n8n-workflow';
import { awsApiRequestSOAP } from './GenericFunctions'; import { awsApiRequestSOAP } from './GenericFunctions';
import get from 'lodash.get'; import get from 'lodash/get';
export class AwsSnsTrigger implements INodeType { export class AwsSnsTrigger implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -1,4 +1,4 @@
import get from 'lodash.get'; import get from 'lodash/get';
import type { import type {
IDataObject, IDataObject,

View file

@ -1,4 +1,4 @@
import get from 'lodash.get'; import get from 'lodash/get';
import { parseString } from 'xml2js'; import { parseString } from 'xml2js';

View file

@ -1,4 +1,4 @@
import get from 'lodash.get'; import get from 'lodash/get';
import { parseString } from 'xml2js'; import { parseString } from 'xml2js';

View file

@ -1,4 +1,4 @@
import get from 'lodash.get'; import get from 'lodash/get';
import { parseString } from 'xml2js'; import { parseString } from 'xml2js';

View file

@ -1,4 +1,4 @@
import get from 'lodash.get'; import get from 'lodash/get';
import { parseString } from 'xml2js'; import { parseString } from 'xml2js';

View file

@ -11,7 +11,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
export async function awsApiRequest( export async function awsApiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,

View file

@ -16,7 +16,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
function getEndpointForService( function getEndpointForService(
service: string, service: string,

View file

@ -32,8 +32,8 @@ import type {
} from './descriptions/MemberDescription'; } from './descriptions/MemberDescription';
import { memberFields, memberOperations } from './descriptions/MemberDescription'; import { memberFields, memberOperations } from './descriptions/MemberDescription';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import partialRight from 'lodash.partialright'; import partialRight from 'lodash/partialRight';
export class Bitwarden implements INodeType { export class Bitwarden implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -12,7 +12,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import upperFirst from 'lodash.upperfirst'; import upperFirst from 'lodash/upperFirst';
import { createHash } from 'crypto'; import { createHash } from 'crypto';

View file

@ -1,14 +1,14 @@
import type { IDataObject, INodeExecutionData } from 'n8n-workflow'; import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
import difference from 'lodash.difference'; import difference from 'lodash/difference';
import get from 'lodash.get'; import get from 'lodash/get';
import intersection from 'lodash.intersection'; import intersection from 'lodash/intersection';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import unset from 'lodash.unset'; import unset from 'lodash/unset';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import set from 'lodash.set'; import set from 'lodash/set';
import union from 'lodash.union'; import union from 'lodash/union';
import { fuzzyCompare } from '../../utils/utilities'; import { fuzzyCompare } from '../../utils/utilities';
type PairToMatch = { type PairToMatch = {

View file

@ -14,8 +14,8 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import flow from 'lodash.flow'; import flow from 'lodash/flow';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import type { import type {
AddressFixedCollection, AddressFixedCollection,

View file

@ -16,7 +16,7 @@ import { responderFields, respondersOperations } from './ResponderDescription';
import { jobFields, jobOperations } from './JobDescription'; import { jobFields, jobOperations } from './JobDescription';
import upperFirst from 'lodash.upperfirst'; import upperFirst from 'lodash/upperFirst';
import type { IJob } from './AnalyzerInterface'; import type { IJob } from './AnalyzerInterface';

View file

@ -1,4 +1,4 @@
import set from 'lodash.set'; import set from 'lodash/set';
import type { import type {
IExecuteFunctions, IExecuteFunctions,

View file

@ -7,7 +7,7 @@ import type {
IHttpRequestOptions, IHttpRequestOptions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
export async function customerIoApiRequest( export async function customerIoApiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,

View file

@ -12,7 +12,7 @@ import type {
import { deepCopy, NodeOperationError } from 'n8n-workflow'; import { deepCopy, NodeOperationError } from 'n8n-workflow';
import set from 'lodash.set'; import set from 'lodash/set';
import moment from 'moment-timezone'; import moment from 'moment-timezone';

View file

@ -0,0 +1,10 @@
{
"node": "n8n-nodes-base.debughelper",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development"],
"resources": {
"credentialDocumentation": [],
"primaryDocumentation": []
}
}

View file

@ -0,0 +1,374 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import {
generateCreditCard,
generateIPv4,
generateIPv6,
generateLocation,
generateMAC,
generateNanoid,
generateRandomAddress,
generateRandomEmail,
generateRandomUser,
generateURL,
generateUUID,
generateVersion,
} from './randomData';
import { setSeed, array as mfArray } from 'minifaker';
import { generateGarbageMemory, runGarbageCollector } from './functions';
export class DebugHelper implements INodeType {
description: INodeTypeDescription = {
displayName: 'DebugHelper',
name: 'debugHelper',
icon: 'file:DebugHelper.svg',
group: ['output'],
subtitle: '={{$parameter["category"]}}',
description: 'Causes problems intentionally and generates useful data for debugging',
version: 1,
defaults: {
name: 'DebugHelper',
},
inputs: ['main'],
outputs: ['main'],
credentials: [],
properties: [
{
displayName: 'Category',
name: 'category',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Do Nothing',
value: 'doNothing',
description: 'Does nothing',
},
{
name: 'Throw Error',
value: 'throwError',
description: 'Throws an error with the specified type and message',
},
{
name: 'Out Of Memory',
value: 'oom',
description: 'Generates a large amount of memory to cause an out of memory error',
},
{
name: 'Generate Random Data',
value: 'randomData',
description: 'Generates random data sets',
},
],
default: 'throwError',
},
{
displayName: 'Error Type',
name: 'throwErrorType',
type: 'options',
noDataExpression: true,
options: [
{
name: 'NodeApiError',
value: 'NodeApiError',
},
{
name: 'NodeOperationError',
value: 'NodeOperationError',
},
{
name: 'Error',
value: 'Error',
},
],
default: 'NodeApiError',
displayOptions: {
show: {
category: ['throwError'],
},
},
},
{
displayName: 'Error Message',
name: 'throwErrorMessage',
type: 'string',
default: 'Node has thrown an error',
description: 'The message to send as part of the error',
displayOptions: {
show: {
category: ['throwError'],
},
},
},
{
displayName: 'Memory Size to Generate',
name: 'memorySizeValue',
type: 'number',
default: 10,
description: 'The approximate amount of memory to generate. Be generous...',
displayOptions: {
show: {
category: ['oom'],
},
},
},
{
displayName: 'Data Type',
name: 'randomDataType',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Address',
value: 'address',
},
{
name: 'Coordinates',
value: 'latLong',
},
{
name: 'Credit Card',
value: 'creditCard',
},
{
name: 'Email',
value: 'email',
},
{
name: 'IPv4',
value: 'ipv4',
},
{
name: 'IPv6',
value: 'ipv6',
},
{
name: 'MAC',
value: 'macAddress',
},
{
name: 'NanoIds',
value: 'nanoid',
},
{
name: 'URL',
value: 'url',
},
{
name: 'User Data',
value: 'user',
},
{
name: 'UUID',
value: 'uuid',
},
{
name: 'Version',
value: 'semver',
},
],
default: 'user',
displayOptions: {
show: {
category: ['randomData'],
},
},
},
{
displayName: 'NanoId Alphabet',
name: 'nanoidAlphabet',
type: 'string',
default: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
description: 'The alphabet to use for generating the nanoIds',
displayOptions: {
show: {
category: ['randomData'],
randomDataType: ['nanoid'],
},
},
},
{
displayName: 'NanoId Length',
name: 'nanoidLength',
type: 'string',
default: '16',
description: 'The length of each nanoIds',
displayOptions: {
show: {
category: ['randomData'],
randomDataType: ['nanoid'],
},
},
},
{
displayName: 'Seed',
name: 'randomDataSeed',
type: 'string',
default: '',
placeholder: 'Leave empty for random seed',
description:
'If set, seed to use for generating the data (same seed will generate the same data)',
displayOptions: {
show: {
category: ['randomData'],
},
},
},
{
displayName: 'Number of Items to Generate',
name: 'randomDataCount',
type: 'number',
default: 10,
description: 'The number of random data items to generate into an array',
displayOptions: {
show: {
category: ['randomData'],
},
},
},
{
displayName: 'Output as Single Array',
name: 'randomDataSingleArray',
type: 'boolean',
default: false,
description: 'Whether to output a single array instead of multiple items',
displayOptions: {
show: {
category: ['randomData'],
},
},
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const category = this.getNodeParameter('category', 0) as string;
for (let i = 0; i < items.length; i++) {
try {
switch (category) {
case 'doNothing':
// as it says on the tin...
break;
case 'throwError':
const throwErrorType = this.getNodeParameter('throwErrorType', 0) as string;
const throwErrorMessage = this.getNodeParameter('throwErrorMessage', 0) as string;
switch (throwErrorType) {
case 'NodeApiError':
throw new NodeApiError(
this.getNode(),
{ message: throwErrorMessage },
{ message: throwErrorMessage },
);
case 'NodeOperationError':
throw new NodeOperationError(this.getNode(), throwErrorMessage, {
message: throwErrorMessage,
});
case 'Error':
// eslint-disable-next-line n8n-nodes-base/node-execute-block-wrong-error-thrown
throw new Error(throwErrorMessage);
default:
break;
}
case 'oom':
const memorySizeValue = this.getNodeParameter('memorySizeValue', 0) as number;
runGarbageCollector();
const memUsed = generateGarbageMemory(memorySizeValue);
items[i].json = memUsed;
returnData.push(items[i]);
break;
case 'randomData':
const randomDataType = this.getNodeParameter('randomDataType', 0) as string;
const randomDataCount = this.getNodeParameter('randomDataCount', 0) as number;
const randomDataSeed = this.getNodeParameter('randomDataSeed', 0) as string;
const randomDataSingleArray = this.getNodeParameter(
'randomDataSingleArray',
0,
) as boolean;
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: i },
};
if (randomDataSeed !== '') {
setSeed(randomDataSeed);
}
let randomFn: () => any = generateRandomUser;
switch (randomDataType) {
case 'user':
randomFn = generateRandomUser;
break;
case 'email':
randomFn = generateRandomEmail;
break;
case 'address':
randomFn = generateRandomAddress;
break;
case 'creditCard':
randomFn = generateCreditCard;
break;
case 'uuid':
randomFn = generateUUID;
break;
case 'macAddress':
randomFn = generateMAC;
break;
case 'ipv4':
randomFn = generateIPv4;
break;
case 'ipv6':
randomFn = generateIPv6;
break;
case 'latLong':
randomFn = generateLocation;
break;
case 'semver':
randomFn = generateVersion;
break;
case 'url':
randomFn = generateURL;
break;
case 'nanoid':
const nanoidAlphabet = this.getNodeParameter('nanoidAlphabet', 0) as string;
const nanoidLength = this.getNodeParameter('nanoidLength', 0) as string;
randomFn = () => generateNanoid(nanoidAlphabet, nanoidLength);
break;
}
const generatedItems = mfArray(randomDataCount, randomFn);
if (randomDataSingleArray) {
newItem.json = { generatedItems };
returnData.push(newItem);
} else {
for (const generatedItem of generatedItems) {
returnData.push({
json: generatedItem,
pairedItem: { item: i },
});
}
}
break;
default:
break;
}
} catch (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: i } },
);
returnData.push(...executionErrorData);
continue;
}
throw error;
}
}
return this.prepareOutputData(returnData);
}
}

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve">
<path style="fill:#F4A026;" d="M345,167c32.953,0,46.333,40.047,46.333,73v100.333c0,74.375-60.292,134.667-134.667,134.667l0,0
C182.292,475,122,414.708,122,340.333V240c0-32.953,13.38-73,46.333-73H345z"/>
<path style="fill:#CA463D;" d="M309,200.333H204.333C185.372,200.333,170,184.961,170,166l0,0c0-39.948,32.385-72.333,72.333-72.333
H271c39.948,0,72.333,32.385,72.333,72.333l0,0C343.333,184.961,327.961,200.333,309,200.333z"/>
<path d="M498.667,290.667H404V240c0-1.016,0.313-2.036,0.291-3.055C452.4,231.927,480,200.272,480,148.333v-12
c0-7.364-5.971-13.333-13.333-13.333s-13.333,5.969-13.333,13.333v12c0,38.399-17.005,58.821-51.885,62.167
c-6.069-27.025-20.875-50.381-45.537-55.7c-3.745-28.54-21.413-52.689-46.115-65.227c10.321-10.501,16.871-24.887,16.871-40.74V37
c0-7.364-5.971-13.333-13.333-13.333S300,29.636,300,37v11.833C300,66.203,285.536,80,268.167,80h-23
c-17.369,0-31.833-13.797-31.833-31.167V37c0-7.364-5.971-13.333-13.333-13.333S186.667,29.636,186.667,37v11.833
c0,15.853,6.549,30.239,16.871,40.741c-24.701,12.537-42.453,36.687-46.199,65.227c-24.695,5.324-39.465,28.736-45.519,55.808
c-35.759-2.96-53.153-23.403-53.153-62.276v-12c0-7.364-5.971-13.333-13.333-13.333S32,128.969,32,136.333v12
c0,52.415,27.439,84.168,76.375,88.739C108.353,238.048,108,239.025,108,240v50.667H13.333C5.971,290.667,0,296.636,0,304
c0,7.364,5.971,13.333,13.333,13.333H108v23c0,10.628,1.469,20.993,3.608,30.992C60.659,374.777,32,406.773,32,460.333v12
c0,7.364,5.971,13.333,13.333,13.333s13.333-5.969,13.333-13.333v-12c0-41.795,20.151-62.291,61.565-62.649
c22.451,53.208,75.151,90.649,136.435,90.649c61.276,0,113.971-37.432,136.425-90.629c40.519,0.784,60.241,21.283,60.241,62.629v12
c0,7.364,5.971,13.333,13.333,13.333S480,479.697,480,472.333v-12c0-53.1-28.823-85.013-78.96-88.921
c2.151-10.025,2.96-20.421,2.96-31.079v-23h94.667c7.363,0,13.333-5.969,13.333-13.333C512,296.636,506.029,290.667,498.667,290.667
z M242.333,106.667h2.833h23H271c32.532,0,59,26.468,59,59c0,11.58-9.42,21-21,21H204.333c-11.58,0-21-9.42-21-21
C183.333,133.135,209.801,106.667,242.333,106.667z M134.667,340.333V240c0-20.793,6.948-50.531,24.483-58.035
c6.627,18.368,24.56,31.368,45.184,31.368h38.333v247.521C182.333,453.891,134.667,402.501,134.667,340.333z M269.333,461.007
V213.333H309c20.624,0,37.891-13,44.517-31.368c17.535,7.504,23.816,37.241,23.816,58.035v100.333
C377.333,402.96,330.307,454.653,269.333,461.007z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,30 @@
import { setFlagsFromString } from 'v8';
import { runInNewContext } from 'vm';
export const runGarbageCollector = () => {
try {
setFlagsFromString('--expose_gc');
const gc = runInNewContext('gc'); // nocommit
gc();
} catch (error) {
console.log(error);
}
};
export const generateGarbageMemory = (sizeInMB: number, onHeap = true) => {
const divider = onHeap ? 8 : 1;
const size = Math.max(1, Math.floor((sizeInMB * 1024 * 1024) / divider));
if (onHeap) {
// arrays are allocated on the heap
// size in this case is only an approximation...
const array = Array(size);
array.fill(0);
} else {
const array = new Uint8Array(size);
array.fill(0);
}
// const used = process.memoryUsage().heapUsed / 1024 / 1024;
// const external = process.memoryUsage().external / 1024 / 1024;
// console.log(`heap: ${used} MB / external: ${external} MB`);
return { ...process.memoryUsage() };
};

View file

@ -0,0 +1,101 @@
import {
firstName,
lastName,
streetAddress,
cityName,
zipCode,
state,
country,
password,
creditCardNumber,
creditCardCVV,
email,
boolean,
uuid,
nanoId,
domainUrl,
semver,
latLong,
macAddress,
ip,
ipv6,
number,
} from 'minifaker';
import 'minifaker/locales/en';
export function generateRandomUser() {
return {
uid: uuid.v4(),
email: email(),
firstname: firstName(),
lastname: lastName(),
password: password(),
};
}
export function generateRandomAddress() {
return {
firstname: firstName(),
lastname: lastName(),
street: streetAddress(),
city: cityName(),
zip: zipCode({ format: '#####' }),
state: state(),
country: country(),
};
}
export function generateRandomEmail() {
return {
email: email(),
confirmed: boolean(),
};
}
export function generateUUID() {
return { uuid: uuid.v4() };
}
export function generateNanoid(customAlphabet: string, length: string) {
return { nanoId: nanoId.customAlphabet(customAlphabet, parseInt(length, 10))().toString() };
}
export function generateCreditCard() {
return {
type: boolean() ? 'MasterCard' : 'Visa',
number: creditCardNumber(),
ccv: creditCardCVV(),
exp: `${number({ min: 1, max: 12, float: false }).toString().padStart(2, '0')}/${number({
min: 1,
max: 40,
float: false,
})
.toString()
.padStart(2, '0')}`,
holder_name: `${firstName()} ${lastName()}`,
};
}
export function generateURL() {
return { url: domainUrl() };
}
export function generateIPv4() {
return { ip: ip() };
}
export function generateIPv6() {
return { ipv6: ipv6() };
}
export function generateMAC() {
return { mac: macAddress() };
}
export function generateLocation() {
return { location: latLong() };
}
export function generateVersion() {
return { version: semver() };
}

View file

@ -1,6 +1,6 @@
import flow from 'lodash.flow'; import flow from 'lodash/flow';
import sortBy from 'lodash.sortby'; import sortBy from 'lodash/sortBy';
import uniqBy from 'lodash.uniqby'; import uniqBy from 'lodash/uniqBy';
export type DocumentProperties = { export type DocumentProperties = {
customProperty: Array<{ field: string; value: string }>; customProperty: Array<{ field: string; value: string }>;

View file

@ -13,7 +13,7 @@ import { documentFields, documentOperations, indexFields, indexOperations } from
import type { DocumentGetAllOptions, FieldsUiValues } from './types'; import type { DocumentGetAllOptions, FieldsUiValues } from './types';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
export class Elasticsearch implements INodeType { export class Elasticsearch implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -21,8 +21,8 @@ import { connect as imapConnect, getParts } from 'imap-simple';
import type { Source as ParserSource } from 'mailparser'; import type { Source as ParserSource } from 'mailparser';
import { simpleParser } from 'mailparser'; import { simpleParser } from 'mailparser';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import find from 'lodash.find'; import find from 'lodash/find';
export async function parseRawEmail( export async function parseRawEmail(
this: ITriggerFunctions, this: ITriggerFunctions,

View file

@ -20,8 +20,8 @@ import { connect as imapConnect, getParts } from 'imap-simple';
import type { Source as ParserSource } from 'mailparser'; import type { Source as ParserSource } from 'mailparser';
import { simpleParser } from 'mailparser'; import { simpleParser } from 'mailparser';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import find from 'lodash.find'; import find from 'lodash/find';
import type { ICredentialsDataImap } from '../../../credentials/Imap.credentials'; import type { ICredentialsDataImap } from '../../../credentials/Imap.credentials';
import { isCredentialsDataImap } from '../../../credentials/Imap.credentials'; import { isCredentialsDataImap } from '../../../credentials/Imap.credentials';

View file

@ -13,7 +13,7 @@ import { campaignFields, campaignOperations } from './CampaignDescription';
import { contactListFields, contactListOperations } from './ContactListDescription'; import { contactListFields, contactListOperations } from './ContactListDescription';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
export class Emelia implements INodeType { export class Emelia implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -16,7 +16,7 @@ import type {
import type { OptionsWithUri } from 'request'; import type { OptionsWithUri } from 'request';
import omit from 'lodash.omit'; import { omit } from 'lodash';
export async function freshserviceApiRequest( export async function freshserviceApiRequest(
this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions, this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions,

View file

@ -15,7 +15,7 @@ import type {
ViewsResponse, ViewsResponse,
} from './types'; } from './types';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
export async function freshworksCrmApiRequest( export async function freshworksCrmApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions, this: IExecuteFunctions | ILoadOptionsFunctions,

View file

@ -35,8 +35,8 @@ import {
loadWebinarSessions, loadWebinarSessions,
} from './GenericFunctions'; } from './GenericFunctions';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
import moment from 'moment-timezone'; import moment from 'moment-timezone';

View file

@ -17,13 +17,14 @@ import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
export interface IEmail { export interface IEmail {
from?: string; from?: string;
to?: string; to?: string;
cc?: string; cc?: string;
bcc?: string; bcc?: string;
replyTo?: string;
inReplyTo?: string; inReplyTo?: string;
reference?: string; reference?: string;
subject: string; subject: string;
@ -222,6 +223,7 @@ export async function encodeEmail(email: IEmail) {
to: email.to, to: email.to,
cc: email.cc, cc: email.cc,
bcc: email.bcc, bcc: email.bcc,
replyTo: email.replyTo,
inReplyTo: email.inReplyTo, inReplyTo: email.inReplyTo,
references: email.reference, references: email.reference,
subject: email.subject, subject: email.subject,
@ -509,7 +511,7 @@ export function unescapeSnippets(items: INodeExecutionData[]) {
return result; return result;
} }
export async function replayToEmail( export async function replyToEmail(
this: IExecuteFunctions, this: IExecuteFunctions,
items: INodeExecutionData[], items: INodeExecutionData[],
gmailId: string, gmailId: string,

View file

@ -28,7 +28,7 @@ import { labelFields, labelOperations } from './LabelDescription';
import { draftFields, draftOperations } from './DraftDescription'; import { draftFields, draftOperations } from './DraftDescription';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import { oldVersionNotice } from '../../../../utils/descriptions'; import { oldVersionNotice } from '../../../../utils/descriptions';

View file

@ -143,6 +143,14 @@ export const draftFields: INodeProperties[] = [
placeholder: 'info@example.com', placeholder: 'info@example.com',
default: '', default: '',
}, },
{
displayName: 'Send Replies To',
name: 'replyTo',
type: 'string',
placeholder: 'reply@example.com',
default: '',
description: 'The email address that the reply message is sent to',
},
{ {
displayName: 'Attachments', displayName: 'Attachments',
name: 'attachmentsUi', name: 'attachmentsUi',

View file

@ -21,7 +21,7 @@ import {
prepareEmailBody, prepareEmailBody,
prepareEmailsInput, prepareEmailsInput,
prepareQuery, prepareQuery,
replayToEmail, replyToEmail,
simplifyOutput, simplifyOutput,
unescapeSnippets, unescapeSnippets,
} from '../GenericFunctions'; } from '../GenericFunctions';
@ -282,6 +282,7 @@ export class GmailV2 implements INodeType {
const to = prepareEmailsInput.call(this, sendTo, 'To', i); const to = prepareEmailsInput.call(this, sendTo, 'To', i);
let cc = ''; let cc = '';
let bcc = ''; let bcc = '';
let replyTo = '';
if (options.ccList) { if (options.ccList) {
cc = prepareEmailsInput.call(this, options.ccList as string, 'CC', i); cc = prepareEmailsInput.call(this, options.ccList as string, 'CC', i);
@ -291,6 +292,10 @@ export class GmailV2 implements INodeType {
bcc = prepareEmailsInput.call(this, options.bccList as string, 'BCC', i); bcc = prepareEmailsInput.call(this, options.bccList as string, 'BCC', i);
} }
if (options.replyTo) {
replyTo = prepareEmailsInput.call(this, options.replyTo as string, 'ReplyTo', i);
}
let attachments: IDataObject[] = []; let attachments: IDataObject[] = [];
if (options.attachmentsUi) { if (options.attachmentsUi) {
@ -323,6 +328,7 @@ export class GmailV2 implements INodeType {
to, to,
cc, cc,
bcc, bcc,
replyTo,
subject: this.getNodeParameter('subject', i) as string, subject: this.getNodeParameter('subject', i) as string,
...prepareEmailBody.call(this, i), ...prepareEmailBody.call(this, i),
attachments, attachments,
@ -340,7 +346,7 @@ export class GmailV2 implements INodeType {
const messageIdGmail = this.getNodeParameter('messageId', i) as string; const messageIdGmail = this.getNodeParameter('messageId', i) as string;
const options = this.getNodeParameter('options', i); const options = this.getNodeParameter('options', i);
responseData = await replayToEmail.call(this, items, messageIdGmail, options, i); responseData = await replyToEmail.call(this, items, messageIdGmail, options, i);
} }
if (operation === 'get') { if (operation === 'get') {
//https://developers.google.com/gmail/api/v1/reference/users/messages/get //https://developers.google.com/gmail/api/v1/reference/users/messages/get
@ -514,6 +520,7 @@ export class GmailV2 implements INodeType {
let to = ''; let to = '';
let cc = ''; let cc = '';
let bcc = ''; let bcc = '';
let replyTo = '';
if (options.sendTo) { if (options.sendTo) {
to += prepareEmailsInput.call(this, options.sendTo as string, 'To', i); to += prepareEmailsInput.call(this, options.sendTo as string, 'To', i);
@ -527,6 +534,10 @@ export class GmailV2 implements INodeType {
bcc = prepareEmailsInput.call(this, options.bccList as string, 'BCC', i); bcc = prepareEmailsInput.call(this, options.bccList as string, 'BCC', i);
} }
if (options.replyTo) {
replyTo = prepareEmailsInput.call(this, options.replyTo as string, 'ReplyTo', i);
}
let attachments: IDataObject[] = []; let attachments: IDataObject[] = [];
if (options.attachmentsUi) { if (options.attachmentsUi) {
attachments = await prepareEmailAttachments.call( attachments = await prepareEmailAttachments.call(
@ -547,6 +558,7 @@ export class GmailV2 implements INodeType {
to, to,
cc, cc,
bcc, bcc,
replyTo,
subject: this.getNodeParameter('subject', i) as string, subject: this.getNodeParameter('subject', i) as string,
...prepareEmailBody.call(this, i), ...prepareEmailBody.call(this, i),
attachments, attachments,
@ -741,7 +753,7 @@ export class GmailV2 implements INodeType {
const messageIdGmail = this.getNodeParameter('messageId', i) as string; const messageIdGmail = this.getNodeParameter('messageId', i) as string;
const options = this.getNodeParameter('options', i); const options = this.getNodeParameter('options', i);
responseData = await replayToEmail.call(this, items, messageIdGmail, options, i); responseData = await replyToEmail.call(this, items, messageIdGmail, options, i);
} }
if (operation === 'trash') { if (operation === 'trash') {
//https://developers.google.com/gmail/api/reference/rest/v1/users.threads/trash //https://developers.google.com/gmail/api/reference/rest/v1/users.threads/trash

View file

@ -225,6 +225,19 @@ export const messageFields: INodeProperties[] = [
default: '', default: '',
description: "The name that will be shown in recipients' inboxes", description: "The name that will be shown in recipients' inboxes",
}, },
{
displayName: 'Send Replies To',
name: 'replyTo',
type: 'string',
placeholder: 'reply@example.com',
default: '',
description: 'The email address that the reply message is sent to',
displayOptions: {
hide: {
'/operation': ['reply'],
},
},
},
{ {
displayName: 'Reply to Sender Only', displayName: 'Reply to Sender Only',
name: 'replyToSenderOnly', name: 'replyToSenderOnly',

View file

@ -3,8 +3,8 @@ import { apiRequest } from './v2/transport';
import type { SheetDataRow, SheetRangeData } from './v2/helpers/GoogleSheets.types'; import type { SheetDataRow, SheetRangeData } from './v2/helpers/GoogleSheets.types';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash/isEqual';
import zip from 'lodash.zip'; import zip from 'lodash/zip';
export const BINARY_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; export const BINARY_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';

View file

@ -5,7 +5,7 @@ import { googleApiRequest } from './GenericFunctions';
import { utils as xlsxUtils } from 'xlsx'; import { utils as xlsxUtils } from 'xlsx';
import get from 'lodash.get'; import get from 'lodash/get';
export interface ISheetOptions { export interface ISheetOptions {
scope: string[]; scope: string[];

View file

@ -7,7 +7,7 @@ import type {
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import { apiRequest } from '../transport'; import { apiRequest } from '../transport';
import { utils as xlsxUtils } from 'xlsx'; import { utils as xlsxUtils } from 'xlsx';
import get from 'lodash.get'; import get from 'lodash/get';
import type { import type {
ILookupValues, ILookupValues,
ISheetUpdateData, ISheetUpdateData,

View file

@ -10,7 +10,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
export async function helpscoutApiRequest( export async function helpscoutApiRequest(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions,

View file

@ -8,7 +8,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
type Cheerio = ReturnType<typeof cheerio>; type Cheerio = ReturnType<typeof cheerio>;

View file

@ -1,7 +1,7 @@
import type { IDataObject, INodeExecutionData, IOAuth2Options } from 'n8n-workflow'; import type { IDataObject, INodeExecutionData, IOAuth2Options } from 'n8n-workflow';
import type { OptionsWithUri } from 'request-promise-native'; import type { OptionsWithUri } from 'request-promise-native';
import set from 'lodash.set'; import set from 'lodash/set';
export type BodyParameter = { name: string; value: string }; export type BodyParameter = { name: string; value: string };

View file

@ -10,7 +10,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow'; import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
export const eventID: { [key: string]: string } = { export const eventID: { [key: string]: string } = {
create_client: '1', create_client: '1',

View file

@ -12,16 +12,16 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash/isEqual';
import isObject from 'lodash.isobject'; import isObject from 'lodash/isObject';
import lt from 'lodash.lt'; import lt from 'lodash/lt';
import merge from 'lodash.merge'; import merge from 'lodash/merge';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import reduce from 'lodash.reduce'; import reduce from 'lodash/reduce';
import set from 'lodash.set'; import set from 'lodash/set';
import unset from 'lodash.unset'; import unset from 'lodash/unset';
const compareItems = ( const compareItems = (
obj: INodeExecutionData, obj: INodeExecutionData,

View file

@ -7,7 +7,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
type AggregationType = type AggregationType =
| 'append' | 'append'

View file

@ -12,16 +12,16 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError, deepCopy } from 'n8n-workflow'; import { NodeOperationError, deepCopy } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash/isEqual';
import isObject from 'lodash.isobject'; import isObject from 'lodash/isObject';
import lt from 'lodash.lt'; import lt from 'lodash/lt';
import merge from 'lodash.merge'; import merge from 'lodash/merge';
import pick from 'lodash.pick'; import pick from 'lodash/pick';
import reduce from 'lodash.reduce'; import reduce from 'lodash/reduce';
import set from 'lodash.set'; import set from 'lodash/set';
import unset from 'lodash.unset'; import unset from 'lodash/unset';
const compareItems = ( const compareItems = (
obj: INodeExecutionData, obj: INodeExecutionData,

View file

@ -7,7 +7,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
type AggregationType = type AggregationType =
| 'append' | 'append'

View file

@ -1,5 +1,5 @@
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import mergeWith from 'lodash.mergewith'; import mergeWith from 'lodash/mergeWith';
import type { import type {
IBinaryKeyData, IBinaryKeyData,

View file

@ -9,21 +9,21 @@ import type {
IWebhookFunctions, IWebhookFunctions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import set from 'lodash.set'; import set from 'lodash/set';
import concat from 'lodash.concat'; import concat from 'lodash/concat';
import split from 'lodash.split'; import split from 'lodash/split';
import every from 'lodash.every'; import every from 'lodash/every';
import toString from 'lodash.tostring'; import toString from 'lodash/toString';
import toNumber from 'lodash.tonumber'; import toNumber from 'lodash/toNumber';
import isString from 'lodash.isstring'; import isString from 'lodash/isString';
import compact from 'lodash.compact'; import compact from 'lodash/compact';
import first from 'lodash.first'; import first from 'lodash/first';
import last from 'lodash.last'; import last from 'lodash/last';
import clone from 'lodash.clone'; import clone from 'lodash/clone';
import some from 'lodash.some'; import some from 'lodash/some';
import isArray from 'lodash.isarray'; import isArray from 'lodash/isArray';
import trim from 'lodash.trim'; import trim from 'lodash/trim';
import escapeRegExp from 'lodash.escaperegexp'; import escapeRegExp from 'lodash/escapeRegExp';
export async function koBoToolboxApiRequest( export async function koBoToolboxApiRequest(
this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions,

View file

@ -22,8 +22,8 @@ import {
import { lemlistApiRequest, lemlistApiRequestAllItems } from './GenericFunctions'; import { lemlistApiRequest, lemlistApiRequestAllItems } from './GenericFunctions';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash/isEmpty';
import omit from 'lodash.omit'; import omit from 'lodash/omit';
export class Lemlist implements INodeType { export class Lemlist implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -12,7 +12,7 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import get from 'lodash.get'; import get from 'lodash/get';
import { query } from './Queries'; import { query } from './Queries';

View file

@ -113,9 +113,6 @@ export class LinkedIn implements INodeType {
}; };
if (shareMediaCategory === 'IMAGE') { if (shareMediaCategory === 'IMAGE') {
if (additionalFields.description) {
description = additionalFields.description as string;
}
if (additionalFields.title) { if (additionalFields.title) {
title = additionalFields.title as string; title = additionalFields.title as string;
} }
@ -145,7 +142,6 @@ export class LinkedIn implements INodeType {
media: { media: {
title, title,
id: image, id: image,
description,
}, },
}, },
commentary: text, commentary: text,

Some files were not shown because too many files have changed in this diff Show more