Add ProfitWell Node (#1204)

*  Improvements to ProfitWell Node

 Improvements to Pro

*  Improvements to ProfitWell-Node

*  improvements simplifying data

*  Small formatting improvement

Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
Jan 2020-11-25 11:44:50 +01:00 committed by GitHub
parent be828a3808
commit 9f4d6f44cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 716 additions and 0 deletions

View file

@ -0,0 +1,19 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class ProfitWellApi implements ICredentialType {
name = 'profitWellApi';
displayName = 'ProfitWell API';
documentationUrl = 'profitWell';
properties = [
{
displayName: 'API Token',
name: 'accessToken',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Your Private Token',
},
];
}

View file

@ -0,0 +1,27 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const companyOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'company',
],
},
},
options: [
{
name: 'Get Settings',
value: 'getSetting',
description: 'Get your companys ProfitWell account settings',
},
],
default: 'getSetting',
description: 'The operation to perform.',
},
] as INodeProperties[];

View file

@ -0,0 +1,74 @@
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
} from 'n8n-workflow';
export async function profitWellApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
try {
const credentials = this.getCredentials('profitWellApi');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
let options: OptionsWithUri = {
headers: {
'Authorization': credentials.accessToken,
},
method,
qs,
body,
uri: uri || `https://api.profitwell.com/v2${resource}`,
json: true,
};
options = Object.assign({}, options, option);
return await this.helpers.request!(options);
} catch (error) {
if (error.response && error.response.body && error.response.body.message) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`ProfitWell error response [${error.statusCode}]: ${errorBody.message}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
}
}
export function simplifyDailyMetrics(responseData: { [key: string]: [{ date: string, value: number | null }] }) {
const data: IDataObject[] = [];
const keys = Object.keys(responseData);
const dates = responseData[keys[0]].map(e => e.date);
for (const [index, date] of dates.entries()) {
const element: IDataObject = {
date,
};
for (const key of keys) {
element[key] = responseData[key][index].value;
}
data.push(element);
}
return data;
}
export function simplifyMontlyMetrics(responseData: { [key: string]: [{ date: string, value: number | null }] }) {
const data: IDataObject = {};
for (const key of Object.keys(responseData)) {
for (const [index] of responseData[key].entries()) {
data[key] = responseData[key][index].value;
data['date'] = responseData[key][index].date;
}
}
return data;
}

View file

@ -0,0 +1,439 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const metricOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'metric',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Retrieve financial metric broken down by day for either the current month or the last',
},
],
default: 'get',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const metricFields = [
/* -------------------------------------------------------------------------- */
/* metric:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'Daily',
value: 'daily',
description: 'Retrieve financial metric broken down by day for either the current month or the last',
},
{
name: 'Monthly',
value: 'monthly',
description: 'Retrieve all monthly financial metric for your company',
},
],
default: '',
required: true,
displayOptions: {
show: {
resource: [
'metric',
],
operation: [
'get',
],
},
},
},
{
displayName: 'Month',
name: 'month',
type: 'string',
default: '',
placeholder: 'YYYY-MM',
required: true,
displayOptions: {
show: {
resource: [
'metric',
],
operation: [
'get',
],
type: [
'daily',
],
},
},
description: 'Can only be the current or previous month. Format should be YYYY-MM',
},
{
displayName: 'Simple',
name: 'simple',
type: 'boolean',
default: true,
displayOptions: {
show: {
resource: [
'metric',
],
operation: [
'get',
],
},
},
description: 'When set to true a simplify version of the response will be used else the raw data.',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
displayOptions: {
show: {
resource: [
'metric',
],
operation: [
'get',
],
},
},
default: {},
options: [
{
displayName: 'Plan ID',
name: 'plan_id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPlanIds',
},
default: '',
description: 'Only return the metric for this Plan ID',
},
{
displayName: 'Metrics',
name: 'dailyMetrics',
type: 'multiOptions',
displayOptions: {
show: {
'/type': [
'daily',
],
},
},
options: [
{
name: 'Active Customers',
value: 'active_customers',
description: 'Number of paying customers',
},
{
name: 'Churned Customers',
value: 'churned_customers',
description: 'Number of paying customers who churned',
},
{
name: 'Churned Recurring Revenue',
value: 'churned_recurring_revenue',
description: 'MRR lost to churn (voluntary and delinquent)',
},
{
name: 'Cumulative Net New MRR',
value: 'cumulative_net_new_mrr',
description: 'New + Upgrades - Downgrades - Churn MRR, cumulative for the month up through the given day',
},
{
name: 'Cumulative New Trialing Customers',
value: 'cumulative_new_trialing_customers',
description: 'Number of new trialing customers, cumulative for the month up through the given day',
},
{
name: 'Downgraded Customers',
value: 'downgraded_customers',
description: 'Number of existing customers who net downgraded',
},
{
name: 'Downgraded Recurring Revenue',
value: 'downgraded_recurring_revenue',
description: 'How much downgrades and plan length decreases affect your MRR',
},
{
name: 'Future Churn MRR',
value: 'future_churn_mrr',
description: 'MRR that will be lost when users who are currently cancelled actually churn',
},
{
name: 'New Customers',
value: 'new_customers',
description: 'Number of new, paying customers you have',
},
{
name: 'New Recurring Revenue',
value: 'new_recurring_revenue',
description: 'MRR from new users',
},
{
name: 'Reactivated Customers',
value: 'reactivated_customers',
description: 'Number of customers who have reactivated',
},
{
name: 'Reactivated Recurring Revenue',
value: 'reactivated_recurring_revenue',
description: 'How much MRR comes from reactivated customers',
},
{
name: 'Recurring Revenue',
value: 'recurring_revenue',
description: `Your company's MRR`,
},
{
name: 'Upgraded Customers',
value: 'upgraded_customers',
description: `Number of existing customers who net upgraded`,
},
{
name: 'Upgraded Recurring Revenue',
value: 'upgraded_recurring_revenue',
description: `How much upgrades and plan length increases affect your MRR`,
},
],
default: '',
description: 'Comma-separated list of metric trends to return (the default is to return all metric)',
},
{
displayName: 'Metrics',
name: 'monthlyMetrics',
type: 'multiOptions',
displayOptions: {
show: {
'/type': [
'monthly',
],
},
},
options: [
{
name: 'Active Customers',
value: 'active_customers',
description: 'Number of paying customers',
},
{
name: 'Active Trialing Customers',
value: 'active_trialing_customers',
description: 'Number of trialing customers',
},
{
name: 'Average Revenue Per User',
value: 'average_revenue_per_user',
description: 'ARPU',
},
{
name: 'Churned Customers',
value: 'churned_customers',
description: 'Number of paying customers who churned',
},
{
name: 'Churned Customers Cancellations',
value: 'churned_customers_cancellations',
description: 'Number of customers who churned by cancelling their subscription(s)',
},
{
name: 'Churned Customers Delinquent',
value: 'churned_customers_delinquent',
description: 'Number of customers who churned because they failed to pay you',
},
{
name: 'Churned Recurring Revenue',
value: 'churned_recurring_revenue',
description: 'Revenue lost to churn (voluntary and delinquent)',
},
{
name: 'Churned Recurring Revenue Cancellations',
value: 'churned_recurring_revenue_cancellations',
description: 'Revenue lost to customers who churned by cancelling their subscription(s)',
},
{
name: 'Churned Recurring Revenue Delinquent',
value: 'churned_recurring_revenue_delinquent',
description: 'Revenue lost to customers who churned delinquent',
},
{
name: 'Churned Trialing Customers',
value: 'churned_trialing_customers',
description: 'Number of trialling customers who churned',
},
{
name: 'Converted Customers',
value: 'converted_customers',
description: 'Number of customers who converted from trialing to active',
},
{
name: 'Converted Recurring Revenue',
value: 'converted_recurring_revenue',
description: 'How much MRR comes from users who converted from trialing to active',
},
{
name: 'Customer Churn Cancellations Rate',
value: 'customers_churn_cancellations_rate',
description: `Percentage of paying customers who churned by cancelling their subscription(s)`,
},
{
name: 'Customer Churn Delinquent Rate',
value: 'customers_churn_delinquent_rate',
description: `Percentage of paying customers who churned because they failed to pay you`,
},
{
name: 'Customer Churn Rate',
value: 'customers_churn_rate',
description: `Percentage of paying customers who churned`,
},
{
name: 'Customer Conversion Rate',
value: 'customer_conversion_rate',
description: 'Percent of trialing customers who converted',
},
{
name: 'Customer Retention Rate',
value: 'customers_retention_rate',
description: 'Percent of customers active last month who are still active this month',
},
{
name: 'Downgrade Customers',
value: 'downgraded_customers',
description: 'Number of existing customers who net downgraded',
},
{
name: 'Downgrade Rate',
value: 'downgrade_rate',
description: 'Downgrade revenue as a percent of existing revenue',
},
{
name: 'Downgrade Recurring Revenue',
value: 'downgraded_recurring_revenue',
description: 'How much downgrades and plan length decreases affect your MRR ',
},
{
name: 'Existing Customers',
value: 'existing_customers',
description: 'Number of paying customers you had at the start of the given month',
},
{
name: 'Existing Recurring Revenue',
value: 'existing_recurring_revenue',
description: `Your company's MRR at the start of the given month`,
},
{
name: 'Existing Trialing Customers',
value: 'existing_trialing_customers',
description: `Number of trialing customers who existed at the start of the month`,
},
{
name: 'Growth_Rate',
value: 'growth_rate',
description: `Rate at which your company's MRR has grown over the previous month`,
},
{
name: 'Lifetime Value',
value: 'lifetime_value',
description: `Average LTV, as calculated at the end of the given period`,
},
{
name: 'New Customers',
value: 'new_customers',
description: `Number of new, paying customers you have`,
},
{
name: 'New Recurring Revenue',
value: 'new_recurring_revenue',
description: `MRR from new users`,
},
{
name: 'New Trailing Customers',
value: 'new_trialing_customers',
description: `Number of new trialing customers`,
},
{
name: 'Reactivated Customers',
value: 'reactivated_customers',
description: `Number of customers who have reactivated`,
},
{
name: 'Reactivated Recurring Revenue',
value: 'reactivated_recurring_revenue',
description: `How much MRR comes from reactivated customers`,
},
{
name: 'Recurring Revenue',
value: 'recurring_revenue',
description: `Your company's MRR`,
},
{
name: 'Revenue Churn Cancellations Rate',
value: 'revenue_churn_cancellations_rate',
description: `Voluntary churn revenue as a percent of the month's starting revenue`,
},
{
name: 'Revenue Churn Delinquent_ Rate',
value: 'revenue_churn_delinquent_rate',
description: `Delinquent churn revenue as a percent of the month's starting revenue `,
},
{
name: 'Revenue Churn Rate',
value: 'revenue_churn_rate',
description: `Revenue lost to churn as a percentage of existing revenue`,
},
{
name: 'Revenue Retention Rate',
value: 'revenue_retention_rate',
description: `Percent of revenue coming from existing customers that was retained by the end of the month`,
},
{
name: 'Upgrade Rate',
value: 'upgrade_rate',
description: `Upgrade revenue as a percent of existing revenue`,
},
{
name: 'Upgraded Customers',
value: 'upgraded_customers',
description: `Number of existing customers who net upgraded `,
},
{
name: 'Upgraded Recurring Revenue',
value: 'upgraded_recurring_revenue',
description: `How much upgrades and plan length increases affect your MRR`,
},
{
name: 'Plan Changed Rate',
value: 'plan_change_rate',
description: `Net change in revenue as a percentage of existing revenue`,
},
{
name: 'Plan Changed Recurring Revenue',
value: 'plan_changed_recurring_revenue',
description: `Net change in revenue for this plan`,
},
],
default: '',
description: 'Comma-separated list of metric trends to return (the default is to return all metric)',
},
],
},
] as INodeProperties[];

View file

@ -0,0 +1,155 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {
profitWellApiRequest,
simplifyDailyMetrics,
simplifyMontlyMetrics,
} from './GenericFunctions';
import {
companyOperations,
} from './CompanyDescription';
import {
metricFields,
metricOperations,
} from './MetricDescription';
export class ProfitWell implements INodeType {
description: INodeTypeDescription = {
displayName: 'ProfitWell',
name: 'profitWell',
icon: 'file:profitwell.png',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume ProfitWell API',
defaults: {
name: 'ProfitWell',
color: '#1e333d',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'profitWellApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Company',
value: 'company',
},
{
name: 'Metric',
value: 'metric',
},
],
default: 'metric',
description: 'Resource to consume.',
},
// COMPANY
...companyOperations,
// METRICS
...metricOperations,
...metricFields,
],
};
methods = {
loadOptions: {
async getPlanIds(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const planIds = await profitWellApiRequest.call(
this,
'GET',
'/metrics/plans',
);
for (const planId of planIds.plan_ids) {
returnData.push({
name: planId,
value: planId,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
if (resource === 'company') {
if (operation === 'getSetting') {
responseData = await profitWellApiRequest.call(this, 'GET', `/company/settings/`);
}
}
if (resource === 'metric') {
if (operation === 'get') {
const type = this.getNodeParameter('type', i) as string;
const simple = this.getNodeParameter('simple', 0) as boolean;
if (type === 'daily') {
qs.month = this.getNodeParameter('month', i) as string;
}
const options = this.getNodeParameter('options', i) as IDataObject;
Object.assign(qs, options);
if (qs.dailyMetrics) {
qs.metrics = (qs.dailyMetrics as string[]).join(',');
delete qs.dailyMetrics;
}
if (qs.monthlyMetrics) {
qs.metrics = (qs.monthlyMetrics as string[]).join(',');
delete qs.monthlyMetrics;
}
responseData = await profitWellApiRequest.call(this, 'GET', `/metrics/${type}`, {}, qs);
responseData = responseData.data;
if (simple === true) {
if (type === 'daily') {
responseData = simplifyDailyMetrics(responseData);
} else {
responseData = simplifyMontlyMetrics(responseData);
}
}
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -158,6 +158,7 @@
"dist/credentials/PhilipsHueOAuth2Api.credentials.js",
"dist/credentials/Postgres.credentials.js",
"dist/credentials/PostmarkApi.credentials.js",
"dist/credentials/ProfitWellApi.credentials.js",
"dist/credentials/PushbulletOAuth2Api.credentials.js",
"dist/credentials/PushoverApi.credentials.js",
"dist/credentials/QuestDb.credentials.js",
@ -376,6 +377,7 @@
"dist/nodes/PhilipsHue/PhilipsHue.node.js",
"dist/nodes/Postgres/Postgres.node.js",
"dist/nodes/Postmark/PostmarkTrigger.node.js",
"dist/nodes/ProfitWell/ProfitWell.node.js",
"dist/nodes/Pushbullet/Pushbullet.node.js",
"dist/nodes/Pushover/Pushover.node.js",
"dist/nodes/QuestDb/QuestDb.node.js",