Add filters to all getAll operations

This commit is contained in:
Iván Ovejero 2021-06-01 10:43:57 +02:00
parent 2863b04fff
commit 497ffc9679
5 changed files with 286 additions and 144 deletions

View file

@ -21,13 +21,16 @@ import {
import { import {
AllFields, AllFields,
CamelCaseResource,
DateType, DateType,
GetAllFilterOptions,
IdType, IdType,
LoadedFields, LoadedFields,
LocationType, LocationType,
NameType, NameType,
ProductDetails, ProductDetails,
ResourceItems, ResourceItems,
SnakeCaseResource,
ZohoOAuth2ApiCredentials, ZohoOAuth2ApiCredentials,
} from './types'; } from './types';
@ -62,6 +65,7 @@ export async function zohoApiRequest(
} }
try { try {
console.log(options);
const responseData = await this.helpers.requestOAuth2?.call(this, 'zohoOAuth2Api', options); const responseData = await this.helpers.requestOAuth2?.call(this, 'zohoOAuth2Api', options);
if (responseData === undefined) return []; if (responseData === undefined) return [];
@ -127,7 +131,7 @@ export async function handleListing(
return responseData.slice(0, limit); return responseData.slice(0, limit);
} }
export function throwOnEmptyUpdate(this: IExecuteFunctions, resource: string) { export function throwOnEmptyUpdate(this: IExecuteFunctions, resource: CamelCaseResource) {
throw new NodeOperationError( throw new NodeOperationError(
this.getNode(), this.getNode(),
`Please enter at least one field to update for the ${resource}.`, `Please enter at least one field to update for the ${resource}.`,
@ -291,7 +295,7 @@ export const toLoadOptions = (items: ResourceItems, nameProperty: NameType) =>
/** /**
* Retrieve all fields for a resource, sorted alphabetically. * Retrieve all fields for a resource, sorted alphabetically.
*/ */
export async function getFields(this: ILoadOptionsFunctions, resource: string) { export async function getFields(this: ILoadOptionsFunctions, resource: SnakeCaseResource) {
const { fields } = await zohoApiRequest.call( const { fields } = await zohoApiRequest.call(
this, 'GET', '/settings/fields', {}, { module: `${resource}s` }, this, 'GET', '/settings/fields', {}, { module: `${resource}s` },
) as LoadedFields; ) as LoadedFields;
@ -299,3 +303,13 @@ export async function getFields(this: ILoadOptionsFunctions, resource: string) {
return sortBy(options, o => o.name); return sortBy(options, o => o.name);
} }
/**
* Add filter options to a query string object.
*/
export const addGetAllFilterOptions = (qs: IDataObject, options: GetAllFilterOptions) => {
if (Object.keys(options).length) {
const { fields, ...rest } = options;
Object.assign(qs, fields && { fields: fields.join(',') }, rest);
}
};

View file

@ -11,6 +11,7 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
addGetAllFilterOptions,
adjustAccountPayload, adjustAccountPayload,
adjustContactPayload, adjustContactPayload,
adjustDealPayload, adjustDealPayload,
@ -30,10 +31,11 @@ import {
} from './GenericFunctions'; } from './GenericFunctions';
import { import {
CamelCaseResource,
GetAllFilterOptions,
LoadedAccounts, LoadedAccounts,
LoadedContacts, LoadedContacts,
LoadedDeals, LoadedDeals,
LoadedFields,
LoadedProducts, LoadedProducts,
LoadedVendors, LoadedVendors,
ProductDetails, ProductDetails,
@ -158,6 +160,10 @@ export class ZohoCrm implements INodeType {
methods = { methods = {
loadOptions: { loadOptions: {
// ----------------------------------------
// resources
// ----------------------------------------
async getAccounts(this: ILoadOptionsFunctions) { async getAccounts(this: ILoadOptionsFunctions) {
const accounts = await zohoApiRequestAllItems.call(this, 'GET', '/accounts') as LoadedAccounts; const accounts = await zohoApiRequestAllItems.call(this, 'GET', '/accounts') as LoadedAccounts;
return toLoadOptions(accounts, 'Account_Name'); return toLoadOptions(accounts, 'Account_Name');
@ -173,10 +179,6 @@ export class ZohoCrm implements INodeType {
return toLoadOptions(deals, 'Deal_Name'); return toLoadOptions(deals, 'Deal_Name');
}, },
async getLeadFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'lead');
},
async getProducts(this: ILoadOptionsFunctions) { async getProducts(this: ILoadOptionsFunctions) {
const products = await zohoApiRequestAllItems.call(this, 'GET', '/products') as LoadedProducts; const products = await zohoApiRequestAllItems.call(this, 'GET', '/products') as LoadedProducts;
return toLoadOptions(products, 'Product_Name'); return toLoadOptions(products, 'Product_Name');
@ -186,6 +188,54 @@ export class ZohoCrm implements INodeType {
const vendors = await zohoApiRequestAllItems.call(this, 'GET', '/vendors') as LoadedVendors; const vendors = await zohoApiRequestAllItems.call(this, 'GET', '/vendors') as LoadedVendors;
return toLoadOptions(vendors, 'Vendor_Name'); return toLoadOptions(vendors, 'Vendor_Name');
}, },
// ----------------------------------------
// resource fields
// ----------------------------------------
async getAccountFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'account');
},
async getContactFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'contact');
},
async getDealFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'deal');
},
async getInvoiceFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'invoice');
},
async getLeadFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'lead');
},
async getProductFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'product');
},
async getPurchaseOrderFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'purchase_order');
},
async getVendorOrderFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'vendor');
},
async getQuoteFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'quote');
},
async getSalesOrderFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'sales_order');
},
async getVendorFields(this: ILoadOptionsFunctions) {
return getFields.call(this, 'vendor');
},
}, },
}; };
@ -193,7 +243,7 @@ export class ZohoCrm implements INodeType {
const items = this.getInputData(); const items = this.getInputData();
const returnData: IDataObject[] = []; const returnData: IDataObject[] = [];
const resource = this.getNodeParameter('resource', 0) as string; const resource = this.getNodeParameter('resource', 0) as CamelCaseResource;
const operation = this.getNodeParameter('operation', 0) as string; const operation = this.getNodeParameter('operation', 0) as string;
let responseData; let responseData;
@ -205,7 +255,7 @@ export class ZohoCrm implements INodeType {
// https://www.zoho.com/crm/developer/docs/api/update-specific-record.html // https://www.zoho.com/crm/developer/docs/api/update-specific-record.html
// https://www.zoho.com/crm/developer/docs/api/delete-specific-record.html // https://www.zoho.com/crm/developer/docs/api/delete-specific-record.html
//try { // try {
if (resource === 'account') { if (resource === 'account') {
@ -264,7 +314,12 @@ export class ZohoCrm implements INodeType {
// account: getAll // account: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/accounts'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/accounts', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -365,7 +420,12 @@ export class ZohoCrm implements INodeType {
// contact: getAll // contact: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/contacts'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/contacts', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -465,7 +525,12 @@ export class ZohoCrm implements INodeType {
// deal: getAll // deal: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/deals'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/deals', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -569,7 +634,12 @@ export class ZohoCrm implements INodeType {
// invoice: getAll // invoice: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/invoices'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/invoices', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -672,11 +742,9 @@ export class ZohoCrm implements INodeType {
// ---------------------------------------- // ----------------------------------------
const qs: IDataObject = {}; const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
if (Object.keys(options).length) { addGetAllFilterOptions(qs, options);
Object.assign(qs, options);
}
responseData = await handleListing.call(this, 'GET', '/leads', {}, qs); responseData = await handleListing.call(this, 'GET', '/leads', {}, qs);
@ -779,7 +847,12 @@ export class ZohoCrm implements INodeType {
// product: getAll // product: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/products'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/products', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -884,7 +957,12 @@ export class ZohoCrm implements INodeType {
// purchaseOrder: getAll // purchaseOrder: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/purchase_orders'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/purchase_orders', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -990,7 +1068,12 @@ export class ZohoCrm implements INodeType {
// quote: getAll // quote: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/quotes'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/quotes', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -1097,7 +1180,12 @@ export class ZohoCrm implements INodeType {
// salesOrder: getAll // salesOrder: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/sales_orders'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/sales_orders', {}, qs);
} else if (operation === 'update') { } else if (operation === 'update') {
@ -1202,7 +1290,12 @@ export class ZohoCrm implements INodeType {
// vendor: getAll // vendor: getAll
// ---------------------------------------- // ----------------------------------------
responseData = await handleListing.call(this, 'GET', '/vendors'); const qs: IDataObject = {};
const options = this.getNodeParameter('options', i) as GetAllFilterOptions;
addGetAllFilterOptions(qs, options);
responseData = await handleListing.call(this, 'GET', '/sales_orders');
} else if (operation === 'update') { } else if (operation === 'update') {

View file

@ -294,89 +294,6 @@ export const leadFields = [
// lead: getAll // lead: getAll
// ---------------------------------------- // ----------------------------------------
...makeGetAllFields('lead'), ...makeGetAllFields('lead'),
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
displayOptions: {
show: {
resource: [
'lead',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Approved',
name: 'approved',
type: 'boolean',
default: true,
description: 'Retrieve only approved leads. Defaults to true.',
},
{
displayName: 'Converted',
name: 'converted',
type: 'boolean',
default: false,
description: 'Retrieve only converted leads. Defaults to false.',
},
{
displayName: 'Fields',
name: 'fields',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLeadFields',
},
default: [],
},
{
displayName: 'Include Child',
name: 'include_child',
type: 'boolean',
default: false,
description: 'Retrieve only leads from child territories.',
},
{
displayName: 'Sort By',
name: 'sort_by',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLeadFields',
},
default: [],
description: 'Field to sort leads by.',
},
{
displayName: 'Sort Order',
name: 'sort_order',
type: 'options',
options: [
{
name: 'Ascending',
value: 'asc',
},
{
name: 'Descending',
value: 'desc',
},
],
default: 'desc',
description: 'Ascending or descending order sort order.',
},
{
displayName: 'Territory ID',
name: 'territory_id',
type: 'string',
default: '',
description: 'Retrieve only leads from this territory.',
},
],
},
// ---------------------------------------- // ----------------------------------------
// lead: update // lead: update

View file

@ -1,3 +1,5 @@
import { CamelCaseResource } from '../types';
export const billingAddress = { export const billingAddress = {
displayName: 'Billing Address', displayName: 'Billing Address',
name: 'Billing_Address', name: 'Billing_Address',
@ -310,46 +312,144 @@ export const makeProductDetails = (resource: string, operation: string, { hasUps
], ],
}); });
export const makeGetAllFields = (resource: string) => [ export const makeGetAllFields = (resource: CamelCaseResource) => {
{ const loadOptionsMethod = {
displayName: 'Return All', account: 'getAccountFields',
name: 'returnAll', contact: 'getContactFields',
type: 'boolean', deal: 'getDealFields',
default: false, invoice: 'getInvoiceFields',
description: 'Return all results.', lead: 'getLeadFields',
displayOptions: { product: 'getProductFields',
show: { purchaseOrder: 'getPurchaseOrderFields',
resource: [ quote: 'getQuoteFields',
resource, salesOrder: 'getSalesOrderFields',
], vendor: 'getVendorFields',
operation: [ }[resource];
'getAll',
], return [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
resource,
],
operation: [
'getAll',
],
},
}, },
}, },
}, {
{ displayName: 'Limit',
displayName: 'Limit', name: 'limit',
name: 'limit', type: 'number',
type: 'number', default: 5,
default: 5, description: 'The number of results to return.',
description: 'The number of results to return.', typeOptions: {
typeOptions: { minValue: 1,
minValue: 1, maxValue: 1000,
maxValue: 1000, },
}, displayOptions: {
displayOptions: { show: {
show: { resource: [
resource: [ resource,
resource, ],
], operation: [
operation: [ 'getAll',
'getAll', ],
], returnAll: [
returnAll: [ false,
false, ],
], },
}, },
}, },
}, {
]; displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
displayOptions: {
show: {
resource: [
resource,,
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Approved',
name: 'approved',
type: 'boolean',
default: true,
description: 'Retrieve only approved leads. Defaults to true.',
},
{
displayName: 'Converted',
name: 'converted',
type: 'boolean',
default: false,
description: 'Retrieve only converted leads. Defaults to false.',
},
{
displayName: 'Fields',
name: 'fields',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod,
},
default: [],
},
{
displayName: 'Include Child',
name: 'include_child',
type: 'boolean',
default: false,
description: 'Retrieve only leads from child territories.',
},
{
displayName: 'Sort By',
name: 'sort_by',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod,
},
default: [],
description: 'Field to sort leads by.',
},
{
displayName: 'Sort Order',
name: 'sort_order',
type: 'options',
options: [
{
name: 'Ascending',
value: 'asc',
},
{
name: 'Descending',
value: 'desc',
},
],
default: 'desc',
description: 'Ascending or descending order sort order.',
},
{
displayName: 'Territory ID',
name: 'territory_id',
type: 'string',
default: '',
description: 'Retrieve only leads from this territory.',
},
],
},
];
};

View file

@ -1,5 +1,23 @@
import { IDataObject } from "n8n-workflow"; import { IDataObject } from "n8n-workflow";
// ----------------------------------------
// for generic functions
// ----------------------------------------
export type CamelCaseResource = 'account' | 'contact' | 'deal' | 'invoice' | 'lead' | 'product' | 'purchaseOrder' | 'quote' | 'salesOrder' | 'vendor';
export type SnakeCaseResource = CamelToSnakeCase<CamelCaseResource>
type CamelToSnakeCase<S extends string> =
S extends `${infer S1}${infer S2}`
? `${S1 extends Capitalize<S1> ? "_" : ""}${Lowercase<S1>}${CamelToSnakeCase<S2>}`
: S
export type GetAllFilterOptions = {
fields: string[],
[otherOptions: string]: unknown;
};
// ---------------------------------------- // ----------------------------------------
// for auth // for auth
// ---------------------------------------- // ----------------------------------------