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
- 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

View file

@ -50,7 +50,7 @@ describe('NDV', () => {
workflowPage.getters.canvasNodes().last().dblclick();
ndv.getters.inputSelect().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');
});
@ -96,10 +96,20 @@ describe('NDV', () => {
ndv.getters.container().should('be.visible');
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.getters.isWorkflowSaved();
})
});
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() {
cy.createFixtureWorkflow('Test_workflow_schema_test.json', `NDV test schema view ${uuid()}`);
workflowPage.actions.zoomToFit();
@ -108,41 +118,62 @@ describe('NDV', () => {
}
it('should switch to output schema view and validate it', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.outputDisplayMode().children().should('have.length', 3);
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
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', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.actions.execute();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
})
});
it('should collapse and expand nested schema object', () => {
setupSchemaWorkflow()
const expandedObjectProps = ['prop1', 'prop2'];;
const getObjectValueItem = () => ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').filter(':contains("objectValue")');
setupSchemaWorkflow();
const expandedObjectProps = ['prop1', 'prop2'];
const getObjectValueItem = () =>
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.filter(':contains("objectValue")');
ndv.getters.outputDisplayMode().contains('Schema').click();
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();
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', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.backToCanvas().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.getters.outputPanel().contains('25 items').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.outputDisplayMode().contains('JSON').click();
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
})
});
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.openNode('Set');
@ -160,8 +194,11 @@ describe('NDV', () => {
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
ndv.getters.outputDisplayMode().contains('Schema').click();
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', () => {
@ -170,11 +207,13 @@ describe('NDV', () => {
workflowPage.actions.executeWorkflow();
workflowPage.actions.openNode('Set3');
ndv.getters.inputRunSelector()
ndv.getters
.inputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector()
ndv.getters
.outputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
@ -183,23 +222,20 @@ describe('NDV', () => {
ndv.actions.switchOutputMode('Table');
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
// unlink
ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector()
ndv.getters
.inputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
@ -207,24 +243,18 @@ describe('NDV', () => {
// link again
ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.inputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
// unlink again
ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
// link again
ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
});
it('should display parameter hints correctly', () => {
@ -247,21 +277,19 @@ describe('NDV', () => {
input: ' test',
},
{
input: ' '
input: ' ',
},
{
input: '<div></div>'
input: '<div></div>',
},
].forEach(({ input, output }) => {
if (input) {
ndv.actions.typeIntoParameterInput('value', input);
}
ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
if (input) {
ndv.actions.typeIntoParameterInput('value', input);
}
ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
ndv.actions.validateExpressionPreview('value', output || input);
ndv.getters.parameterInput('value').clear();
});
ndv.actions.validateExpressionPreview('value', output || input);
ndv.getters.parameterInput('value').clear();
});
});
});

View file

@ -77,22 +77,7 @@
"@types/json-diff": "^0.5.1",
"@types/jsonwebtoken": "^9.0.1",
"@types/localtunnel": "^1.9.0",
"@types/lodash.debounce": "^4.0.7",
"@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/lodash": "^4.14.195",
"@types/parseurl": "^1.3.1",
"@types/passport-jwt": "^3.0.6",
"@types/psl": "^1.1.0",
@ -109,7 +94,6 @@
"@types/yamljs": "^0.2.31",
"chokidar": "^3.5.2",
"concurrently": "^5.1.0",
"lodash.debounce": "^4.0.8",
"mock-jwks": "^1.0.9",
"nodemon": "^2.0.2",
"run-script-os": "^1.0.7",
@ -161,21 +145,7 @@
"jwks-rsa": "^3.0.1",
"ldapts": "^4.2.6",
"localtunnel": "^2.0.0",
"lodash.difference": "^4",
"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",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"mysql2": "~2.3.3",
"n8n-core": "workspace:*",

View file

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

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import curlconverter from 'curlconverter';
import get from 'lodash.get';
import get from 'lodash/get';
import type { IDataObject } 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 type { DirectoryLoader, Types } from 'n8n-core';
import {

View file

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

View file

@ -11,7 +11,7 @@ export const reloadNodesAndCredentials = async (
push: Push,
) => {
// 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
const { watch } = await import('chokidar');

View file

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

View file

@ -42,7 +42,7 @@ import {
WorkflowHooks,
} from 'n8n-workflow';
import pick from 'lodash.pick';
import pick from 'lodash/pick';
import type { FindOptionsWhere } from 'typeorm';
import { LessThanOrEqual, In } from 'typeorm';
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 { User } from '@db/entities/User';
import { RoleRepository } from '@db/repositories';
import omit from 'lodash.omit';
import omit from 'lodash/omit';
import { PermissionChecker } from './UserManagement/PermissionChecker';
import { isWorkflowIdValid } from './utils';
import { UserService } from './user/user.service';

View file

@ -6,7 +6,7 @@ import type { ITaskData } from 'n8n-workflow';
import { sleep } from 'n8n-workflow';
import { sep } from 'path';
import { diff } from 'json-diff';
import pick from 'lodash.pick';
import pick from 'lodash/pick';
import { ActiveExecutions } from '@/ActiveExecutions';
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 { getLdapConfig, getLdapSynchronizations, updateLdapConfig } from '@/Ldap/helpers';
import { LdapService } from '@/Ldap/LdapService.ee';

View file

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

View file

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

View file

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

View file

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

View file

@ -15,7 +15,7 @@ import {
messageEventBusDestinationFromDb,
incrementPrometheusMetric,
} from '../MessageEventBusDestination/Helpers.ee';
import uniqby from 'lodash.uniqby';
import uniqby from 'lodash/uniqBy';
import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm';
import type { EventMessageAuditOptions } 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 readline from 'readline';
import { jsonParse, LoggerProxy } from 'n8n-workflow';
import remove from 'lodash.remove';
import remove from 'lodash/remove';
import config from '@/config';
import { getEventMessageObjectByType } from '../EventMessageClasses/Helpers';
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 type { FindOptionsSelect, FindOptionsWhere, UpdateResult } from 'typeorm';
import { In } from 'typeorm';
import pick from 'lodash.pick';
import pick from 'lodash/pick';
import { v4 as uuid } from 'uuid';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import * as Db from '@/Db';

View file

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

View file

@ -38,8 +38,7 @@
"@types/cron": "~1.7.1",
"@types/crypto-js": "^4.0.1",
"@types/express": "^4.17.6",
"@types/lodash.get": "^4.4.6",
"@types/lodash.pick": "^4.4.7",
"@types/lodash": "^4.14.195",
"@types/mime-types": "^2.1.0",
"@types/request-promise-native": "~1.0.15",
"@types/uuid": "^8.3.2"
@ -54,8 +53,7 @@
"file-type": "^16.5.4",
"flatted": "^3.2.4",
"form-data": "^4.0.0",
"lodash.get": "^4.4.2",
"lodash.pick": "^4.4.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.27",
"n8n-workflow": "workspace:*",
"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');
}
async retrieveBinaryData(binaryData: IBinaryData): Promise<Buffer> {
async getBinaryDataBuffer(binaryData: IBinaryData): Promise<Buffer> {
if (binaryData.id) {
return this.retrieveBinaryDataByIdentifier(binaryData.id);
}

View file

@ -79,7 +79,7 @@ import {
validateFieldType,
} from 'n8n-workflow';
import pick from 'lodash.pick';
import pick from 'lodash/pick';
import { Agent } from 'https';
import { IncomingMessage } from 'http';
import { stringify } from 'qs';
@ -92,7 +92,7 @@ import type {
} from '@n8n/client-oauth2';
import { ClientOAuth2 } from '@n8n/client-oauth2';
import crypto, { createHmac } from 'crypto';
import get from 'lodash.get';
import get from 'lodash/get';
import type { Request, Response } from 'express';
import FormData from 'form-data';
import path from 'path';
@ -747,6 +747,8 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
auth,
proxy,
url,
maxBodyLength: Infinity,
maxContentLength: Infinity,
} as AxiosRequestConfig;
axiosRequest.params = n8nRequest.qs;
@ -915,7 +917,7 @@ export async function getBinaryDataBuffer(
inputIndex: number,
): Promise<Buffer> {
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,
} 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';
export class WorkflowExecute {

View file

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

View file

@ -84,18 +84,6 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
label: `${prefix}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`,
info: this.$locale.baseText('codeNodeEditor.completer.$runIndex'),

View file

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

View file

@ -5,11 +5,27 @@
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({
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() {
return {
observer: null,
@ -35,13 +51,13 @@ export default defineComponent({
});
}, options);
this.$data.observer = observer;
this.observer = observer;
this.$on('observe', (observed: Element) => {
this.eventBus.on('observe', (observed: Element) => {
observer.observe(observed);
});
this.$on('unobserve', (observed: Element) => {
this.eventBus.on('unobserve', (observed: Element) => {
observer.unobserve(observed);
});
},

View file

@ -38,18 +38,6 @@
@update:modelValue="onInput"
@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 v-if="!showInviteUrls" #footer>
<n8n-button

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,26 @@
<template>
<div class="__html-display ph-no-capture" v-html="html"></div>
<iframe class="__html-display ph-no-capture" :srcdoc="html" />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import sanitizeHtml, { defaults, type IOptions as SanitizeOptions } from 'sanitize-html';
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 {
name: 'RunDataHtml',
props: {
@ -15,29 +30,8 @@ export default {
},
computed: {
html() {
if (!this.inputData) return '';
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);
const markup = (this.inputData?.[0].json.html as string) ?? '';
return sanitizeHtml(markup, sanitizeOptions);
},
},
};
@ -45,6 +39,7 @@ export default {
<style lang="scss">
.__html-display {
padding: 0 var(--spacing-s);
width: 100%;
height: 100%;
}
</style>

View file

@ -4,6 +4,7 @@
@observed="onObserved"
class="tags-container"
:enabled="responsive"
:event-bus="intersectionEventBus"
>
<template>
<span class="tags">
@ -26,6 +27,7 @@
:class="{ hidden: tag.hidden }"
:data-id="tag.id"
:enabled="responsive"
:event-bus="intersectionEventBus"
v-else
>
<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 { mapStores } from 'pinia';
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
const DEFAULT_MAX_TAGS_LIMIT = 20;
@ -62,6 +65,7 @@ export default defineComponent({
props: ['tagIds', 'limit', 'clickable', 'responsive', 'hoverable'],
data() {
return {
intersectionEventBus: createEventBus(),
visibility: {} as { [id: string]: boolean },
};
},

View file

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

View file

@ -1,4 +1,5 @@
export * from './code-node-editor';
export * from './data-pinning';
export * from './link-actions';
export * from './html-editor';
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 { getTriggerNodeServiceName } from '@/utils';
import { codeNodeEditorEventBus } from '@/event-bus';
import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
@ -351,9 +351,12 @@ export const pushConnection = defineComponent({
let action;
if (!isSavingExecutions) {
this.$root.$emit('registerGlobalLinkAction', 'open-settings', async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
globalLinkActionsEventBus.emit('registerGlobalLinkAction', {
key: 'open-settings',
action: async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
});
action =

View file

@ -1231,8 +1231,6 @@
"settings.users.setupMyAccount": "Set up my owner 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.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.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another 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.addATriggerNodeFirst');
this.registerCustomAction('showNodeCreator', () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
);
this.registerCustomAction({
key: 'showNodeCreator',
action: () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
});
const notice = this.showMessage({
type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
@ -1050,7 +1053,7 @@ export default defineComponent({
}
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow');
nodeViewEventBus.emit('newWorkflow');
} else {
void this.$router.push({ name: VIEWS.NEW_WORKFLOW });
}
@ -3913,9 +3916,9 @@ export default defineComponent({
window.addEventListener('message', this.onPostMessageReceived);
window.addEventListener('pageshow', this.onPageShow);
this.$root.$on('newWorkflow', this.newWorkflow);
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.on('newWorkflow', this.newWorkflow);
nodeViewEventBus.on('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.on('nodeMove', this.onMoveNode);
historyBus.on('revertAddNode', this.onRevertAddNode);
historyBus.on('revertRemoveNode', this.onRevertRemoveNode);
@ -3938,9 +3941,9 @@ export default defineComponent({
window.removeEventListener('beforeunload', this.onBeforeUnload);
window.removeEventListener('pageshow', this.onPageShow);
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.off('nodeMove', this.onMoveNode);
historyBus.off('revertAddNode', this.onRevertAddNode);
historyBus.off('revertRemoveNode', this.onRevertRemoveNode);
@ -3959,9 +3962,9 @@ export default defineComponent({
this.instance.destroy();
this.uiStore.stateIsDirty = false;
window.removeEventListener('message', this.onPostMessageReceived);
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
},
});

View file

@ -117,7 +117,7 @@ const repoUrlValidationRules: Array<Rule | RuleGroup> = [
{
name: 'MATCH_REGEX',
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'),
},
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ import type {
import { deepCopy, NodeOperationError } from 'n8n-workflow';
import set from 'lodash.set';
import set from 'lodash/set';
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 sortBy from 'lodash.sortby';
import uniqBy from 'lodash.uniqby';
import flow from 'lodash/flow';
import sortBy from 'lodash/sortBy';
import uniqBy from 'lodash/uniqBy';
export type DocumentProperties = {
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 omit from 'lodash.omit';
import omit from 'lodash/omit';
export class Elasticsearch implements INodeType {
description: INodeTypeDescription = {

View file

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

View file

@ -20,8 +20,8 @@ import { connect as imapConnect, getParts } from 'imap-simple';
import type { Source as ParserSource } from 'mailparser';
import { simpleParser } from 'mailparser';
import isEmpty from 'lodash.isempty';
import find from 'lodash.find';
import isEmpty from 'lodash/isEmpty';
import find from 'lodash/find';
import type { ICredentialsDataImap } 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 isEmpty from 'lodash.isempty';
import isEmpty from 'lodash/isEmpty';
export class Emelia implements INodeType {
description: INodeTypeDescription = {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -143,6 +143,14 @@ export const draftFields: INodeProperties[] = [
placeholder: 'info@example.com',
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',
name: 'attachmentsUi',

View file

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

View file

@ -225,6 +225,19 @@ export const messageFields: INodeProperties[] = [
default: '',
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',
name: 'replyToSenderOnly',

View file

@ -3,8 +3,8 @@ import { apiRequest } from './v2/transport';
import type { SheetDataRow, SheetRangeData } from './v2/helpers/GoogleSheets.types';
import * as XLSX from 'xlsx';
import isEqual from 'lodash.isequal';
import zip from 'lodash.zip';
import isEqual from 'lodash/isEqual';
import zip from 'lodash/zip';
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 get from 'lodash.get';
import get from 'lodash/get';
export interface ISheetOptions {
scope: string[];

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ import type {
} 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 } = {
create_client: '1',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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