🔀 Merge branch 'master' into CheckExists-option-Telegram-Trigger-node

This commit is contained in:
Jan Oberhauser 2020-09-02 15:14:31 +02:00
commit 60e31f6e8f
13 changed files with 1243 additions and 22 deletions

View file

@ -10,11 +10,26 @@ export class CustomerIoApi implements ICredentialType {
documentationUrl = 'customerIo';
properties = [
{
displayName: 'App API Key',
name: 'apiKey',
displayName: 'Tracking API Key',
name: 'trackingApiKey',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Required for tracking API.',
required: true,
},
{
displayName: 'Tracking Site ID',
name: 'trackingSiteId',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Required for tracking API.',
},
{
displayName: 'App API Key',
name: 'appApiKey',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Required for App API.',
},
];
}

View file

@ -0,0 +1,199 @@
import { INodeProperties } from 'n8n-workflow';
export const campaignOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'campaign',
],
},
},
options: [
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
{
name: 'Get Metrics',
value: 'getMetrics',
},
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const campaignFields = [
/* -------------------------------------------------------------------------- */
/* campaign:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Campaign ID',
name: 'campaignId',
type: 'number',
required: true,
default: 0,
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'get',
]
},
},
description: 'The unique identifier for the campaign',
},
/* -------------------------------------------------------------------------- */
/* campaign:getMetrics */
/* -------------------------------------------------------------------------- */
{
displayName: 'Campaign ID',
name: 'campaignId',
type: 'number',
required: true,
default: 0,
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'getMetrics',
]
},
},
description: 'The unique identifier for the campaign',
},
{
displayName: 'Period',
name: 'period',
type: 'options',
default: 'days',
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'getMetrics',
]
},
},
description: 'Specify metric period',
options: [
{
name: 'Hours',
value: 'hours',
},
{
name: 'Days',
value: 'days',
},
{
name: 'Weeks',
value: 'weeks',
},
{
name: 'Months',
value: 'months',
},
]
},
{
displayName: 'JSON Parameters',
name: 'jsonParameters',
type: 'boolean',
default: false,
description: '',
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'getMetrics',
],
},
},
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'getMetrics'
],
jsonParameters: [
false,
],
},
},
options: [
{
displayName: 'Steps',
name: 'steps',
type: 'number',
default: 0,
description: 'Integer specifying how many steps to return. Defaults to the maximum number of timeperiods available, or 12 when using the months period. Maximum timeperiods available are 24 hours, 45 days, 12 weeks and 120 months',
typeOptions: {
minValue: 0,
maxValue: 120,
}
},
{
displayName: 'Type',
name: 'type',
type: 'options',
default: 'empty',
description: 'Specify metric type',
options: [
{
name: 'Empty',
value: 'empty',
},
{
name: 'Email',
value: 'email',
},
{
name: 'Push',
value: 'push',
},
{
name: 'Slack',
value: 'slack',
},
{
name: 'twilio',
value: 'twilio',
},
{
name: 'Urban Airship',
value: 'urbanAirship',
},
{
name: 'Webhook',
value: 'webhook',
},
]
},
],
},
] as INodeProperties[];

View file

@ -0,0 +1,189 @@
import { INodeProperties } from 'n8n-workflow';
export const customerOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'customer',
],
},
},
options: [
{
name: 'Create/Update',
value: 'upsert',
description: 'Create/Update a customer.',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a customer.',
},
],
default: 'upsert',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const customerFields = [
/* -------------------------------------------------------------------------- */
/* customer:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'ID',
name: 'id',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'customer',
],
operation: [
'delete',
]
},
},
description: 'The unique identifier for the customer.',
},
/* -------------------------------------------------------------------------- */
/* customer:upsert */
/* -------------------------------------------------------------------------- */
{
displayName: 'ID',
name: 'id',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'customer',
],
operation: [
'upsert',
]
},
},
description: 'The unique identifier for the customer.',
},
{
displayName: 'JSON Parameters',
name: 'jsonParameters',
type: 'boolean',
default: false,
description: '',
displayOptions: {
show: {
resource: [
'customer',
],
operation: [
'upsert',
],
},
},
},
{
displayName: ' Additional Fields',
name: 'additionalFieldsJson',
type: 'json',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
displayOptions: {
show: {
resource: [
'customer',
],
operation: [
'upsert',
],
jsonParameters: [
true,
],
},
},
description: 'Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-companys---companies-api" target="_blank">here</a>.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'customer',
],
operation: [
'upsert',
],
jsonParameters: [
false,
],
},
},
options: [
{
displayName: 'Custom Properties',
name: 'customProperties',
type: 'fixedCollection',
description: 'Custom Properties',
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Property',
name: 'customProperty',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
required: true,
default: '',
description: 'Property name.',
placeholder: 'Plan',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
required: true,
default: '',
description: 'Property value.',
placeholder: 'Basic',
},
],
},
]
},
{
displayName: 'Email',
name: 'email',
type: 'string',
default: '',
description: 'The email address of the user.',
},
{
displayName: 'Created at',
name: 'createdAt',
type: 'dateTime',
default: '',
description: 'The UNIX timestamp from when the user was created.',
},
],
},
] as INodeProperties[];

View file

@ -0,0 +1,327 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeTypeDescription,
INodeExecutionData,
INodeType,
} from 'n8n-workflow';
import { customerIoApiRequest, validateJSON } from './GenericFunctions';
import { campaignOperations, campaignFields } from './CampaignDescription';
import { customerOperations, customerFields } from './CustomerDescription';
import { eventOperations, eventFields } from './EventDescription';
import { segmentOperations, segmentFields } from './SegmentDescription';
export class CustomerIo implements INodeType {
description: INodeTypeDescription = {
displayName: 'Customer.io',
name: 'customerIo',
icon: 'file:customerio.png',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Customer.io API',
defaults: {
name: 'CustomerIo',
color: '#ffcd00',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'customerIoApi',
required: true,
}
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Customer',
value: 'customer',
},
{
name: 'Event',
value: 'event',
},
{
name: 'Campaign',
value: 'campaign',
},
{
name: 'Segment',
value: 'segment',
},
],
default: 'customer',
description: 'Resource to consume.',
},
// CAMPAIGN
...campaignOperations,
...campaignFields,
// CUSTOMER
...customerOperations,
...customerFields,
// EVENT
...eventOperations,
...eventFields,
// SEGMENT
...segmentOperations,
...segmentFields
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const returnData: IDataObject[] = [];
const items = this.getInputData();
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
const body: IDataObject = {};
let responseData;
for (let i = 0; i < items.length; i++) {
if (resource === 'campaign') {
if (operation === 'get') {
const campaignId = this.getNodeParameter('campaignId', i) as number;
const endpoint = `/campaigns/${campaignId}`;
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
responseData = responseData.campaign;
}
if (operation === 'getAll') {
const endpoint = `/campaigns`;
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
responseData = responseData.campaigns;
}
if (operation === 'getMetrics') {
const campaignId = this.getNodeParameter('campaignId', i) as number;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
if (jsonParameters) {
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson));
} else {
throw new Error('Additional fields must be a valid JSON');
}
}
} else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const period = this.getNodeParameter('period', i) as string;
let endpoint = `/campaigns/${campaignId}/metrics`;
if (period !== 'days') {
endpoint = `${endpoint}?period=${period}`;
}
if (additionalFields.steps) {
body.steps = additionalFields.steps as number;
}
if (additionalFields.type) {
if (additionalFields.type === 'urbanAirship') {
additionalFields.type = 'urban_airship';
} else {
body.type = additionalFields.type as string;
}
}
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
responseData = responseData.metric;
}
}
}
if (resource === 'customer') {
if (operation === 'upsert') {
const id = this.getNodeParameter('id', i) as number;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
if (jsonParameters) {
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson));
} else {
throw new Error('Additional fields must be a valid JSON');
}
}
} else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.customProperties) {
const data: any = {}; // tslint:disable-line:no-any
//@ts-ignore
additionalFields.customProperties.customProperty.map(property => {
data[property.key] = property.value;
});
body.data = data;
}
if (additionalFields.email) {
body.email = additionalFields.email as string;
}
if (additionalFields.createdAt) {
body.created_at = new Date(additionalFields.createdAt as string).getTime() / 1000;
}
}
const endpoint = `/customers/${id}`;
responseData = await customerIoApiRequest.call(this, 'PUT', endpoint, body, 'tracking');
responseData = Object.assign({ id }, body);
}
if (operation === 'delete') {
const id = this.getNodeParameter('id', i) as number;
body.id = id;
const endpoint = `/customers/${id}`;
await customerIoApiRequest.call(this, 'DELETE', endpoint, body, 'tracking');
responseData = {
success: true,
};
}
}
if (resource === 'event') {
if (operation === 'track') {
const customerId = this.getNodeParameter('customerId', i) as number;
const eventName = this.getNodeParameter('eventName', i) as string;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
body.name = eventName;
if (jsonParameters) {
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson));
} else {
throw new Error('Additional fields must be a valid JSON');
}
}
} else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const data: any = {}; // tslint:disable-line:no-any
if (additionalFields.customAttributes) {
//@ts-ignore
additionalFields.customAttributes.customAttribute.map(property => {
data[property.key] = property.value;
});
}
if (additionalFields.type) {
data.type = additionalFields.type as string;
}
body.data = data;
}
const endpoint = `/customers/${customerId}/events`;
await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
responseData = {
success: true,
};
}
if (operation === 'trackAnonymous') {
const eventName = this.getNodeParameter('eventName', i) as string;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
body.name = eventName;
if (jsonParameters) {
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson));
} else {
throw new Error('Additional fields must be a valid JSON');
}
}
} else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const data: any = {}; // tslint:disable-line:no-any
if (additionalFields.customAttributes) {
//@ts-ignore
additionalFields.customAttributes.customAttribute.map(property => {
data[property.key] = property.value;
});
}
body.data = data;
}
const endpoint = `/events`;
await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
responseData = {
success: true,
};
}
}
if (resource === 'segment') {
const segmentId = this.getNodeParameter('segmentId', i) as number;
const customerIds = this.getNodeParameter('customerIds', i) as string;
body.id = segmentId;
body.ids = customerIds.split(',');
let endpoint = '';
if (operation === 'add') {
endpoint = `/segments/${segmentId}/add_customers`;
} else {
endpoint = `/segments/${segmentId}/remove_customers`;
}
responseData = await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
responseData = {
success: true,
};
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as unknown as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View file

@ -11,7 +11,7 @@ import {
} from 'n8n-workflow';
import {
apiRequest,
customerIoApiRequest,
eventExists,
} from './GenericFunctions';
@ -34,7 +34,7 @@ export class CustomerIoTrigger implements INodeType {
description: 'Starts the workflow on a Customer.io update. (Beta)',
defaults: {
name: 'Customer.io Trigger',
color: '#7131ff',
color: '#ffcd00',
},
inputs: [],
outputs: ['main'],
@ -237,7 +237,7 @@ export class CustomerIoTrigger implements INodeType {
const endpoint = '/reporting_webhooks';
let { reporting_webhooks: webhooks } = await apiRequest.call(this, 'GET', endpoint, {});
let { reporting_webhooks: webhooks } = await customerIoApiRequest.call(this, 'GET', endpoint, {}, 'beta');
if (webhooks === null) {
webhooks = [];
@ -295,7 +295,7 @@ export class CustomerIoTrigger implements INodeType {
events: data,
};
webhook = await apiRequest.call(this, 'POST', endpoint, body);
webhook = await customerIoApiRequest.call(this, 'POST', endpoint, body, 'beta');
const webhookData = this.getWorkflowStaticData('node');
webhookData.webhookId = webhook.id as string;
@ -307,7 +307,7 @@ export class CustomerIoTrigger implements INodeType {
if (webhookData.webhookId !== undefined) {
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
try {
await apiRequest.call(this, 'DELETE', endpoint, {});
await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta');
} catch (e) {
return false;
}

View file

@ -0,0 +1,295 @@
import { INodeProperties } from 'n8n-workflow';
export const eventOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'event',
],
},
},
options: [
{
name: 'Track',
value: 'track',
description: 'Track a customer event.',
},
{
name: 'Track Anonymous',
value: 'trackAnonymous',
description: 'Track an anonymous event.',
},
],
default: 'track',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const eventFields = [
/* -------------------------------------------------------------------------- */
/* event:track */
/* -------------------------------------------------------------------------- */
{
displayName: 'Customer ID',
name: 'customerId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'track',
]
},
},
description: 'The unique identifier for the customer.',
},
{
displayName: 'Event Name',
name: 'eventName',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'track',
]
},
},
description: 'Name of the event to track.',
},
{
displayName: 'JSON Parameters',
name: 'jsonParameters',
type: 'boolean',
default: false,
description: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'track',
],
},
},
},
{
displayName: 'Additional Fields',
name: 'additionalFieldsJson',
type: 'json',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'track',
],
jsonParameters: [
true,
],
},
},
description: 'Object of values to set as described <a href="https://customer.io/docs/api-triggered-data-format#basic-data-formatting" target="_blank">here</a>.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'track',
],
jsonParameters: [
false,
]
},
},
options: [
{
displayName: 'Custom Attributes',
name: 'customAttributes',
type: 'fixedCollection',
description: 'Custom Properties',
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Attribute',
name: 'customAttribute',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
required: true,
default: '',
description: 'Attribute name.',
placeholder: 'Price',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
required: true,
default: '',
description: 'Attribute value.',
placeholder: '25.50',
},
],
},
]
},
{
displayName: 'Type',
name: 'type',
type: 'string',
default: '',
description: 'Used to change event type. For Page View events set to "page".',
},
],
},
/* -------------------------------------------------------------------------- */
/* event:track anonymous */
/* -------------------------------------------------------------------------- */
{
displayName: 'Event Name',
name: 'eventName',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'trackAnonymous',
]
},
},
description: 'The unique identifier for the customer.',
},
{
displayName: 'JSON Parameters',
name: 'jsonParameters',
type: 'boolean',
default: false,
description: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'trackAnonymous',
],
},
},
},
{
displayName: 'Additional Fields',
name: 'additionalFieldsJson',
type: 'json',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'trackAnonymous',
],
jsonParameters: [
true,
],
},
},
description: 'Object of values to set as described <a href="https://customer.io/docs/api-triggered-data-format#basic-data-formatting" target="_blank">here</a>.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'trackAnonymous',
],
jsonParameters: [
false,
]
},
},
options: [
{
displayName: 'Custom Attributes',
name: 'customAttributes',
type: 'fixedCollection',
description: 'Custom Properties',
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Attribute',
name: 'customAttribute',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
required: true,
default: '',
description: 'Attribute name.',
placeholder: 'Price',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
required: true,
default: '',
description: 'Attribute value.',
placeholder: '25.50',
},
],
},
]
},
],
},
] as INodeProperties[];

View file

@ -16,7 +16,7 @@ import {
get,
} from 'lodash';
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
export async function customerIoApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, baseApi?: string, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('customerIoApi');
if (credentials === undefined) {
@ -28,14 +28,26 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
const options: OptionsWithUri = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${credentials.apiKey}`,
},
method,
body,
qs: query,
uri: `https://beta-api.customer.io/v1/api${endpoint}`,
uri: '',
json: true,
};
if (baseApi === 'tracking') {
options.uri = `https://track.customer.io/api/v1${endpoint}`;
const basicAuthKey = Buffer.from(`${credentials.trackingSiteId}:${credentials.trackingApiKey}`).toString('base64');
Object.assign(options.headers, { 'Authorization': `Basic ${basicAuthKey}` });
} else if (baseApi === 'api') {
options.uri = `https://api.customer.io/v1/api${endpoint}`;
const basicAuthKey = Buffer.from(`${credentials.trackingSiteId}:${credentials.trackingApiKey}`).toString('base64');
Object.assign(options.headers, { 'Authorization': `Basic ${basicAuthKey}` });
} else if (baseApi === 'beta') {
options.uri = `https://beta-api.customer.io/v1/api${endpoint}`;
Object.assign(options.headers, { 'Authorization': `Bearer ${credentials.appApiKey as string}` });
}
try {
return await this.helpers.request!(options);
} catch (error) {
@ -63,3 +75,13 @@ export function eventExists(currentEvents: string[], webhookEvents: IDataObject)
}
return true;
}
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
let result;
try {
result = JSON.parse(json!);
} catch (exception) {
result = undefined;
}
return result;
}

View file

@ -0,0 +1,73 @@
import { INodeProperties } from 'n8n-workflow';
export const segmentOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'segment',
],
},
},
options: [
{
name: 'Add Customer',
value: 'add',
},
{
name: 'Remove Customer',
value: 'remove',
},
],
default: 'add',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const segmentFields = [
/* -------------------------------------------------------------------------- */
/* segment:add */
/* -------------------------------------------------------------------------- */
{
displayName: 'Segment ID',
name: 'segmentId',
type: 'number',
required: true,
default: 0,
displayOptions: {
show: {
resource: [
'segment',
],
operation: [
'add',
'remove',
]
},
},
description: 'The unique identifier of the segment.',
},
{
displayName: 'Customer IDs',
name: 'customerIds',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'segment',
],
operation: [
'add',
'remove',
]
},
},
description: 'A list of customer ids to add to the segment.',
},
] as INodeProperties[];

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -17,6 +17,24 @@ import {
import * as ftpClient from 'promise-ftp';
import * as sftpClient from 'ssh2-sftp-client';
interface ReturnFtpItem {
type: string;
name: string;
size: number;
accessTime: Date;
modifyTime: Date;
rights: {
user: string;
group: string;
other: string;
};
owner: string | number;
group: string | number;
target: string;
sticky?: boolean;
path: string;
}
export class Ftp implements INodeType {
description: INodeTypeDescription = {
displayName: 'FTP',
@ -220,6 +238,21 @@ export class Ftp implements INodeType {
description: 'Path of directory to list contents of.',
required: true,
},
{
displayName: 'Recursive',
displayOptions: {
show: {
operation: [
'list',
],
},
},
name: 'recursive',
type: 'boolean',
default: false,
description: 'Return object representing all directories / objects recursively found within SFTP server',
required: true,
},
],
};
@ -234,6 +267,7 @@ export class Ftp implements INodeType {
let credentials: ICredentialDataDecryptedObject | undefined = undefined;
const protocol = this.getNodeParameter('protocol', 0) as string;
if (protocol === 'sftp') {
credentials = this.getCredentials('sftp');
} else {
@ -244,11 +278,11 @@ export class Ftp implements INodeType {
throw new Error('Failed to get credentials!');
}
let ftp: ftpClient;
let sftp: sftpClient;
let ftp : ftpClient;
let sftp : sftpClient;
if (protocol === 'sftp') {
sftp = new sftpClient();
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
@ -258,7 +292,6 @@ export class Ftp implements INodeType {
} else {
ftp = new ftpClient();
await ftp.connect({
host: credentials.host as string,
port: credentials.port as number,
@ -286,8 +319,16 @@ export class Ftp implements INodeType {
const path = this.getNodeParameter('path', i) as string;
if (operation === 'list') {
responseData = await sftp!.list(path);
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
const recursive = this.getNodeParameter('recursive', i) as boolean;
if (recursive) {
responseData = await callRecursiveList(path, sftp!, normalizeSFtpItem);
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
} else {
responseData = await sftp!.list(path);
responseData.forEach(item => normalizeSFtpItem(item as sftpClient.FileInfo, path));
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
}
}
if (operation === 'download') {
@ -347,8 +388,16 @@ export class Ftp implements INodeType {
const path = this.getNodeParameter('path', i) as string;
if (operation === 'list') {
responseData = await ftp!.list(path);
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
const recursive = this.getNodeParameter('recursive', i) as boolean;
if (recursive) {
responseData = await callRecursiveList(path, ftp!, normalizeFtpItem);
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
} else {
responseData = await ftp!.list(path);
responseData.forEach(item => normalizeFtpItem(item as ftpClient.ListingElement, path));
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
}
}
if (operation === 'download') {
@ -432,3 +481,54 @@ export class Ftp implements INodeType {
return [returnItems];
}
}
function normalizeFtpItem(input: ftpClient.ListingElement, path: string) {
const item = input as unknown as ReturnFtpItem;
item.modifyTime = input.date;
item.path = `${path}${path.endsWith('/') ? '' : '/'}${item.name}`;
// @ts-ignore
item.date = undefined;
}
function normalizeSFtpItem(input: sftpClient.FileInfo, path: string) {
const item = input as unknown as ReturnFtpItem;
item.accessTime = new Date(input.accessTime);
item.modifyTime = new Date(input.modifyTime);
item.path = `${path}${path.endsWith('/') ? '' : '/'}${item.name}`;
}
async function callRecursiveList(path: string, client: sftpClient | ftpClient, normalizeFunction: (input: ftpClient.ListingElement & sftpClient.FileInfo, path: string) => void) {
const pathArray : string[] = [path];
let currentPath = path;
const directoryItems : sftpClient.FileInfo[] = [];
let index = 0;
do {
// tslint:disable-next-line: array-type
const returnData : sftpClient.FileInfo[] | (string | ftpClient.ListingElement)[] = await client.list(pathArray[index]);
// @ts-ignore
returnData.map((item : sftpClient.FileInfo) => {
if ((pathArray[index] as string).endsWith('/')) {
currentPath = `${pathArray[index]}${item.name}`;
} else {
currentPath = `${pathArray[index]}/${item.name}`;
}
// Is directory
if (item.type === 'd') {
pathArray.push(currentPath);
}
normalizeFunction(item as ftpClient.ListingElement & sftpClient.FileInfo, currentPath);
directoryItems.push(item);
});
index++;
} while (index <= pathArray.length - 1);
return directoryItems;
}

View file

@ -476,7 +476,7 @@ export class Gmail implements INodeType {
if (qs.labelIds == '') {
delete qs.labelIds;
} else {
qs.labelIds = (qs.labelIds as string[]).join(',');
qs.labelIds = qs.labelIds as string[];
}
}

View file

@ -442,7 +442,7 @@ export const messageFields = [
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: '',
default: [],
description: 'Only return messages with labels that match all of the specified label IDs.',
},
{

View file

@ -217,6 +217,7 @@
"dist/nodes/CrateDb/CrateDb.node.js",
"dist/nodes/Cron.node.js",
"dist/nodes/Crypto.node.js",
"dist/nodes/CustomerIo/CustomerIo.node.js",
"dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
"dist/nodes/DateTime.node.js",
"dist/nodes/Discord/Discord.node.js",