mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
🔀 Merge branch 'master' into CheckExists-option-Telegram-Trigger-node
This commit is contained in:
commit
60e31f6e8f
|
@ -10,11 +10,26 @@ export class CustomerIoApi implements ICredentialType {
|
||||||
documentationUrl = 'customerIo';
|
documentationUrl = 'customerIo';
|
||||||
properties = [
|
properties = [
|
||||||
{
|
{
|
||||||
displayName: 'App API Key',
|
displayName: 'Tracking API Key',
|
||||||
name: 'apiKey',
|
name: 'trackingApiKey',
|
||||||
type: 'string' as NodePropertyTypes,
|
type: 'string' as NodePropertyTypes,
|
||||||
default: '',
|
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.',
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
199
packages/nodes-base/nodes/CustomerIo/CampaignDescription.ts
Normal file
199
packages/nodes-base/nodes/CustomerIo/CampaignDescription.ts
Normal 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[];
|
189
packages/nodes-base/nodes/CustomerIo/CustomerDescription.ts
Normal file
189
packages/nodes-base/nodes/CustomerIo/CustomerDescription.ts
Normal 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[];
|
327
packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts
Normal file
327
packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts
Normal 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)];
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
apiRequest,
|
customerIoApiRequest,
|
||||||
eventExists,
|
eventExists,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export class CustomerIoTrigger implements INodeType {
|
||||||
description: 'Starts the workflow on a Customer.io update. (Beta)',
|
description: 'Starts the workflow on a Customer.io update. (Beta)',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Customer.io Trigger',
|
name: 'Customer.io Trigger',
|
||||||
color: '#7131ff',
|
color: '#ffcd00',
|
||||||
},
|
},
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: ['main'],
|
outputs: ['main'],
|
||||||
|
@ -237,7 +237,7 @@ export class CustomerIoTrigger implements INodeType {
|
||||||
|
|
||||||
const endpoint = '/reporting_webhooks';
|
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) {
|
if (webhooks === null) {
|
||||||
webhooks = [];
|
webhooks = [];
|
||||||
|
@ -295,7 +295,7 @@ export class CustomerIoTrigger implements INodeType {
|
||||||
events: data,
|
events: data,
|
||||||
};
|
};
|
||||||
|
|
||||||
webhook = await apiRequest.call(this, 'POST', endpoint, body);
|
webhook = await customerIoApiRequest.call(this, 'POST', endpoint, body, 'beta');
|
||||||
|
|
||||||
const webhookData = this.getWorkflowStaticData('node');
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
webhookData.webhookId = webhook.id as string;
|
webhookData.webhookId = webhook.id as string;
|
||||||
|
@ -307,7 +307,7 @@ export class CustomerIoTrigger implements INodeType {
|
||||||
if (webhookData.webhookId !== undefined) {
|
if (webhookData.webhookId !== undefined) {
|
||||||
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
|
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
|
||||||
try {
|
try {
|
||||||
await apiRequest.call(this, 'DELETE', endpoint, {});
|
await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
295
packages/nodes-base/nodes/CustomerIo/EventDescription.ts
Normal file
295
packages/nodes-base/nodes/CustomerIo/EventDescription.ts
Normal 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[];
|
|
@ -16,7 +16,7 @@ import {
|
||||||
get,
|
get,
|
||||||
} from 'lodash';
|
} 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');
|
const credentials = this.getCredentials('customerIoApi');
|
||||||
|
|
||||||
if (credentials === undefined) {
|
if (credentials === undefined) {
|
||||||
|
@ -28,14 +28,26 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
||||||
const options: OptionsWithUri = {
|
const options: OptionsWithUri = {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${credentials.apiKey}`,
|
|
||||||
},
|
},
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
qs: query,
|
uri: '',
|
||||||
uri: `https://beta-api.customer.io/v1/api${endpoint}`,
|
|
||||||
json: true,
|
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 {
|
try {
|
||||||
return await this.helpers.request!(options);
|
return await this.helpers.request!(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -63,3 +75,13 @@ export function eventExists(currentEvents: string[], webhookEvents: IDataObject)
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
|
73
packages/nodes-base/nodes/CustomerIo/SegmentDescription.ts
Normal file
73
packages/nodes-base/nodes/CustomerIo/SegmentDescription.ts
Normal 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[];
|
BIN
packages/nodes-base/nodes/CustomerIo/customerio.png
Normal file
BIN
packages/nodes-base/nodes/CustomerIo/customerio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -17,6 +17,24 @@ import {
|
||||||
import * as ftpClient from 'promise-ftp';
|
import * as ftpClient from 'promise-ftp';
|
||||||
import * as sftpClient from 'ssh2-sftp-client';
|
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 {
|
export class Ftp implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'FTP',
|
displayName: 'FTP',
|
||||||
|
@ -220,6 +238,21 @@ export class Ftp implements INodeType {
|
||||||
description: 'Path of directory to list contents of.',
|
description: 'Path of directory to list contents of.',
|
||||||
required: true,
|
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;
|
let credentials: ICredentialDataDecryptedObject | undefined = undefined;
|
||||||
const protocol = this.getNodeParameter('protocol', 0) as string;
|
const protocol = this.getNodeParameter('protocol', 0) as string;
|
||||||
|
|
||||||
if (protocol === 'sftp') {
|
if (protocol === 'sftp') {
|
||||||
credentials = this.getCredentials('sftp');
|
credentials = this.getCredentials('sftp');
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,11 +278,11 @@ export class Ftp implements INodeType {
|
||||||
throw new Error('Failed to get credentials!');
|
throw new Error('Failed to get credentials!');
|
||||||
}
|
}
|
||||||
|
|
||||||
let ftp: ftpClient;
|
let ftp : ftpClient;
|
||||||
let sftp: sftpClient;
|
let sftp : sftpClient;
|
||||||
|
|
||||||
if (protocol === 'sftp') {
|
if (protocol === 'sftp') {
|
||||||
sftp = new sftpClient();
|
sftp = new sftpClient();
|
||||||
|
|
||||||
await sftp.connect({
|
await sftp.connect({
|
||||||
host: credentials.host as string,
|
host: credentials.host as string,
|
||||||
port: credentials.port as number,
|
port: credentials.port as number,
|
||||||
|
@ -258,7 +292,6 @@ export class Ftp implements INodeType {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ftp = new ftpClient();
|
ftp = new ftpClient();
|
||||||
|
|
||||||
await ftp.connect({
|
await ftp.connect({
|
||||||
host: credentials.host as string,
|
host: credentials.host as string,
|
||||||
port: credentials.port as number,
|
port: credentials.port as number,
|
||||||
|
@ -286,8 +319,16 @@ export class Ftp implements INodeType {
|
||||||
const path = this.getNodeParameter('path', i) as string;
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
if (operation === 'list') {
|
if (operation === 'list') {
|
||||||
responseData = await sftp!.list(path);
|
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
|
||||||
|
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') {
|
if (operation === 'download') {
|
||||||
|
@ -347,8 +388,16 @@ export class Ftp implements INodeType {
|
||||||
const path = this.getNodeParameter('path', i) as string;
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
if (operation === 'list') {
|
if (operation === 'list') {
|
||||||
responseData = await ftp!.list(path);
|
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
|
||||||
|
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') {
|
if (operation === 'download') {
|
||||||
|
@ -432,3 +481,54 @@ export class Ftp implements INodeType {
|
||||||
return [returnItems];
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -476,7 +476,7 @@ export class Gmail implements INodeType {
|
||||||
if (qs.labelIds == '') {
|
if (qs.labelIds == '') {
|
||||||
delete qs.labelIds;
|
delete qs.labelIds;
|
||||||
} else {
|
} else {
|
||||||
qs.labelIds = (qs.labelIds as string[]).join(',');
|
qs.labelIds = qs.labelIds as string[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,7 @@ export const messageFields = [
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
loadOptionsMethod: 'getLabels',
|
loadOptionsMethod: 'getLabels',
|
||||||
},
|
},
|
||||||
default: '',
|
default: [],
|
||||||
description: 'Only return messages with labels that match all of the specified label IDs.',
|
description: 'Only return messages with labels that match all of the specified label IDs.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -217,6 +217,7 @@
|
||||||
"dist/nodes/CrateDb/CrateDb.node.js",
|
"dist/nodes/CrateDb/CrateDb.node.js",
|
||||||
"dist/nodes/Cron.node.js",
|
"dist/nodes/Cron.node.js",
|
||||||
"dist/nodes/Crypto.node.js",
|
"dist/nodes/Crypto.node.js",
|
||||||
|
"dist/nodes/CustomerIo/CustomerIo.node.js",
|
||||||
"dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
|
"dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
|
||||||
"dist/nodes/DateTime.node.js",
|
"dist/nodes/DateTime.node.js",
|
||||||
"dist/nodes/Discord/Discord.node.js",
|
"dist/nodes/Discord/Discord.node.js",
|
||||||
|
|
Loading…
Reference in a new issue