Improvements to ConvertKit-Trigger

This commit is contained in:
ricardo 2020-08-06 13:33:43 -04:00
parent 9b15cf50bc
commit 833001a9e4
3 changed files with 255 additions and 38 deletions

View file

@ -8,12 +8,17 @@ import {
INodeTypeDescription, INodeTypeDescription,
INodeType, INodeType,
IWebhookResponseData, IWebhookResponseData,
ILoadOptionsFunctions,
INodePropertyOptions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
convertKitApiRequest, convertKitApiRequest,
} from './GenericFunctions'; } from './GenericFunctions';
import {
snakeCase,
} from 'change-case';
export class ConvertKitTrigger implements INodeType { export class ConvertKitTrigger implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -34,7 +39,7 @@ export class ConvertKitTrigger implements INodeType {
{ {
name: 'convertKitApi', name: 'convertKitApi',
required: true, required: true,
} },
], ],
webhooks: [ webhooks: [
{ {
@ -50,21 +55,86 @@ export class ConvertKitTrigger implements INodeType {
name: 'event', name: 'event',
type: 'options', type: 'options',
required: true, required: true,
default: 'subscriberActivated', default: '',
description: 'The events that can trigger the webhook and whether they are enabled.', description: 'The events that can trigger the webhook and whether they are enabled.',
options: [ options: [
{ {
name: 'Subscriber Activated', name: 'Form Subscribe',
value: 'subscriberActivated', value: 'formSubscribe',
description: 'Whether the webhook is triggered when a subscriber is activated.',
}, },
{ {
name: 'Link Clicked', name: 'Link Click',
value: 'linkClicked', value: 'linkClick',
description: 'Whether the webhook is triggered when a link is clicked.', },
{
name: 'Product Purchase',
value: 'productPurchase',
},
{
name: 'Purchase Created',
value: 'purchaseCreate',
},
{
name: 'Sequence Complete',
value: 'courseComplete',
},
{
name: 'Sequence Subscribe',
value: 'courseSubscribe',
},
{
name: 'Subscriber Activated',
value: 'subscriberActivate',
},
{
name: 'Subscriber Unsubscribe',
value: 'subscriberUnsubscribe',
},
{
name: 'Tag Add',
value: 'tagAdd',
},
{
name: 'Tag Remove',
value: 'tagRemove',
}, },
], ],
}, },
{
displayName: 'Form ID',
name: 'formId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getForms',
},
required: true,
default: '',
displayOptions: {
show: {
event: [
'formSubscribe',
],
},
},
},
{
displayName: 'Sequence ID',
name: 'courseId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSequences',
},
required: true,
default: '',
displayOptions: {
show: {
event: [
'courseSubscribe',
'courseComplete',
],
},
},
},
{ {
displayName: 'Initiating Link', displayName: 'Initiating Link',
name: 'link', name: 'link',
@ -75,7 +145,39 @@ export class ConvertKitTrigger implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
event: [ event: [
'linkClicked', 'linkClick',
],
},
},
},
{
displayName: 'Product ID',
name: 'productId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
event: [
'productPurchase',
],
},
},
},
{
displayName: 'Tag ID',
name: 'tagId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTags',
},
required: true,
default: '',
displayOptions: {
show: {
event: [
'tagAdd',
'tagRemove',
], ],
}, },
}, },
@ -83,73 +185,181 @@ export class ConvertKitTrigger implements INodeType {
], ],
}; };
methods = {
loadOptions: {
// Get all the tags to display them to user so that he can
// select them easily
async getTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const { tags } = await convertKitApiRequest.call(this, 'GET', '/tags');
for (const tag of tags) {
const tagName = tag.name;
const tagId = tag.id;
returnData.push({
name: tagName,
value: tagId,
});
}
return returnData;
},
// Get all the forms to display them to user so that he can
// select them easily
async getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const { forms } = await convertKitApiRequest.call(this, 'GET', '/forms');
for (const form of forms) {
const formName = form.name;
const formId = form.id;
returnData.push({
name: formName,
value: formId,
});
}
return returnData;
},
// Get all the sequences to display them to user so that he can
// select them easily
async getSequences(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const { courses } = await convertKitApiRequest.call(this, 'GET', '/sequences');
for (const course of courses) {
const courseName = course.name;
const courseId = course.id;
returnData.push({
name: courseName,
value: courseId,
});
}
return returnData;
},
}
};
// @ts-ignore (because of request) // @ts-ignore (because of request)
webhookMethods = { webhookMethods = {
default: { default: {
async checkExists(this: IHookFunctions): Promise<boolean> { async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node'); const webhookData = this.getWorkflowStaticData('node');
// THe API does not have an endpoint to list all webhooks
if(webhookData.webhookId) { if(webhookData.webhookId) {
return true; return true;
} }
return false; return false;
}, },
async create(this: IHookFunctions): Promise<boolean> { async create(this: IHookFunctions): Promise<boolean> {
let webhook;
const webhookUrl = this.getNodeWebhookUrl('default'); const webhookUrl = this.getNodeWebhookUrl('default');
const event = this.getNodeParameter('event', 0);
let event = this.getNodeParameter('event', 0) as string;
const endpoint = '/automations/hooks'; const endpoint = '/automations/hooks';
const qs: IDataObject = {}; if (event === 'purchaseCreate') {
try { event = `purchase.${snakeCase(event)}`;
qs.target_url = webhookUrl;
if(event === 'subscriberActivated') { } else {
qs.event = {
name: 'subscriber.subscriber_activate', event = `subscriber.${snakeCase(event)}`;
};
} else if(event === 'linkClicked') {
const link = this.getNodeParameter('link', 0) as string;
qs.event = {
name: 'subscriber.link_click',
initiator_value: link,
};
}
webhook = await convertKitApiRequest.call(this, 'POST', endpoint, {}, qs);
} catch (error) {
throw error;
} }
const body: IDataObject = {
target_url: webhookUrl as string,
event: {
name: event
},
};
if (event === 'subscriber.form_subscribe') {
//@ts-ignore
body.event['form_id'] = this.getNodeParameter('formId', 0);
}
if (event === 'subscriber.course_subscribe' || event === 'subscriber.course_complete') {
//@ts-ignore
body.event['sequence_id'] = this.getNodeParameter('courseId', 0);
}
if (event === 'subscriber.link_click') {
//@ts-ignore
body.event['initiator_value'] = this.getNodeParameter('link', 0);
}
if (event === 'subscriber.product_purchase') {
//@ts-ignore
body.event['product_id'] = this.getNodeParameter('productId', 0);
}
if (event === 'subscriber.tag_add' || event === 'subscriber.tag_remove"') {
//@ts-ignore
body.event['tag_id'] = this.getNodeParameter('tagId', 0);
}
const webhook = await convertKitApiRequest.call(this, 'POST', endpoint, body);
if (webhook.rule.id === undefined) { if (webhook.rule.id === undefined) {
return false; return false;
} }
const webhookData = this.getWorkflowStaticData('node'); const webhookData = this.getWorkflowStaticData('node');
webhookData.webhookId = webhook.rule.id as string; webhookData.webhookId = webhook.rule.id as string;
webhookData.events = event;
return true; return true;
}, },
async delete(this: IHookFunctions): Promise<boolean> { async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node'); const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) { if (webhookData.webhookId !== undefined) {
const endpoint = `/automations/hooks/${webhookData.webhookId}`; const endpoint = `/automations/hooks/${webhookData.webhookId}`;
try { try {
await convertKitApiRequest.call(this, 'DELETE', endpoint, {}, {});
await convertKitApiRequest.call(this, 'DELETE', endpoint);
} catch (error) { } catch (error) {
return false; return false;
} }
delete webhookData.webhookId; delete webhookData.webhookId;
delete webhookData.events;
} }
return true; return true;
}, },
}, },
}; };
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> { async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const returnData: IDataObject[] = []; const returnData: IDataObject[] = [];
returnData.push(this.getBodyData()); returnData.push(this.getBodyData());

View file

@ -39,12 +39,19 @@ export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSin
delete options.body; delete options.body;
} }
console.log(options); // it's a webhook so include the api secret on the body
if ((options.uri as string).includes('/automations/hooks')) {
options.body['api_secret'] = credentials.apiSecret;
} else {
qs.api_secret = credentials.apiSecret;
}
if (Object.keys(options.qs).length === 0) {
delete options.qs;
}
try { try {
qs.api_secret = credentials.apiSecret;
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {

View file

@ -18,17 +18,17 @@ export const sequenceOperations = [
{ {
name: 'Add Subscriber', name: 'Add Subscriber',
value: 'addSubscriber', value: 'addSubscriber',
description: 'Add a subscriber.', description: 'Add a subscriber',
}, },
{ {
name: 'Get All', name: 'Get All',
value: 'getAll', value: 'getAll',
description: 'Get all sequences.', description: 'Get all sequences',
}, },
{ {
name: 'Get Subscriptions', name: 'Get Subscriptions',
value: 'getSubscriptions', value: 'getSubscriptions',
description: 'List subscriptions to a sequence including subscriber data.', description: 'Get all subscriptions to a sequence including subscriber data',
}, },
], ],
default: 'addSubscriber', default: 'addSubscriber',