From 497ffc9679bbb10b772ab92eabf0047dad2e2f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 1 Jun 2021 10:43:57 +0200 Subject: [PATCH] :zap: Add filters to all getAll operations --- .../nodes-base/nodes/Zoho/GenericFunctions.ts | 18 +- .../nodes-base/nodes/Zoho/ZohoCrm.node.ts | 133 +++++++++++-- .../Zoho/descriptions/LeadDescription.ts | 83 -------- .../nodes/Zoho/descriptions/SharedFields.ts | 178 ++++++++++++++---- packages/nodes-base/nodes/Zoho/types.d.ts | 18 ++ 5 files changed, 286 insertions(+), 144 deletions(-) diff --git a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts index 40a45f63d8..bf5ec81541 100644 --- a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts @@ -21,13 +21,16 @@ import { import { AllFields, + CamelCaseResource, DateType, + GetAllFilterOptions, IdType, LoadedFields, LocationType, NameType, ProductDetails, ResourceItems, + SnakeCaseResource, ZohoOAuth2ApiCredentials, } from './types'; @@ -62,6 +65,7 @@ export async function zohoApiRequest( } try { + console.log(options); const responseData = await this.helpers.requestOAuth2?.call(this, 'zohoOAuth2Api', options); if (responseData === undefined) return []; @@ -127,7 +131,7 @@ export async function handleListing( return responseData.slice(0, limit); } -export function throwOnEmptyUpdate(this: IExecuteFunctions, resource: string) { +export function throwOnEmptyUpdate(this: IExecuteFunctions, resource: CamelCaseResource) { throw new NodeOperationError( this.getNode(), `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. */ -export async function getFields(this: ILoadOptionsFunctions, resource: string) { +export async function getFields(this: ILoadOptionsFunctions, resource: SnakeCaseResource) { const { fields } = await zohoApiRequest.call( this, 'GET', '/settings/fields', {}, { module: `${resource}s` }, ) as LoadedFields; @@ -299,3 +303,13 @@ export async function getFields(this: ILoadOptionsFunctions, resource: string) { 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); + } +}; diff --git a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts index c7f0344fef..71654a9d0c 100644 --- a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts +++ b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts @@ -11,6 +11,7 @@ import { } from 'n8n-workflow'; import { + addGetAllFilterOptions, adjustAccountPayload, adjustContactPayload, adjustDealPayload, @@ -30,10 +31,11 @@ import { } from './GenericFunctions'; import { + CamelCaseResource, + GetAllFilterOptions, LoadedAccounts, LoadedContacts, LoadedDeals, - LoadedFields, LoadedProducts, LoadedVendors, ProductDetails, @@ -158,6 +160,10 @@ export class ZohoCrm implements INodeType { methods = { loadOptions: { + // ---------------------------------------- + // resources + // ---------------------------------------- + async getAccounts(this: ILoadOptionsFunctions) { const accounts = await zohoApiRequestAllItems.call(this, 'GET', '/accounts') as LoadedAccounts; return toLoadOptions(accounts, 'Account_Name'); @@ -173,10 +179,6 @@ export class ZohoCrm implements INodeType { return toLoadOptions(deals, 'Deal_Name'); }, - async getLeadFields(this: ILoadOptionsFunctions) { - return getFields.call(this, 'lead'); - }, - async getProducts(this: ILoadOptionsFunctions) { const products = await zohoApiRequestAllItems.call(this, 'GET', '/products') as LoadedProducts; return toLoadOptions(products, 'Product_Name'); @@ -186,6 +188,54 @@ export class ZohoCrm implements INodeType { const vendors = await zohoApiRequestAllItems.call(this, 'GET', '/vendors') as LoadedVendors; 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 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; 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/delete-specific-record.html - //try { + // try { if (resource === 'account') { @@ -264,7 +314,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -365,7 +420,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -465,7 +525,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -569,7 +634,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -672,11 +742,9 @@ export class ZohoCrm implements INodeType { // ---------------------------------------- const qs: IDataObject = {}; - const options = this.getNodeParameter('options', i) as IDataObject; + const options = this.getNodeParameter('options', i) as GetAllFilterOptions; - if (Object.keys(options).length) { - Object.assign(qs, options); - } + addGetAllFilterOptions(qs, options); responseData = await handleListing.call(this, 'GET', '/leads', {}, qs); @@ -779,7 +847,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -884,7 +957,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -990,7 +1068,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -1097,7 +1180,12 @@ export class ZohoCrm implements INodeType { // 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') { @@ -1202,7 +1290,12 @@ export class ZohoCrm implements INodeType { // 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') { diff --git a/packages/nodes-base/nodes/Zoho/descriptions/LeadDescription.ts b/packages/nodes-base/nodes/Zoho/descriptions/LeadDescription.ts index faeaea8467..e3d9371b2b 100644 --- a/packages/nodes-base/nodes/Zoho/descriptions/LeadDescription.ts +++ b/packages/nodes-base/nodes/Zoho/descriptions/LeadDescription.ts @@ -294,89 +294,6 @@ export const leadFields = [ // lead: getAll // ---------------------------------------- ...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 diff --git a/packages/nodes-base/nodes/Zoho/descriptions/SharedFields.ts b/packages/nodes-base/nodes/Zoho/descriptions/SharedFields.ts index 36f4f59bde..20e6ac303c 100644 --- a/packages/nodes-base/nodes/Zoho/descriptions/SharedFields.ts +++ b/packages/nodes-base/nodes/Zoho/descriptions/SharedFields.ts @@ -1,3 +1,5 @@ +import { CamelCaseResource } from '../types'; + export const billingAddress = { displayName: 'Billing Address', name: 'Billing_Address', @@ -310,46 +312,144 @@ export const makeProductDetails = (resource: string, operation: string, { hasUps ], }); -export const makeGetAllFields = (resource: string) => [ - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - default: false, - description: 'Return all results.', - displayOptions: { - show: { - resource: [ - resource, - ], - operation: [ - 'getAll', - ], +export const makeGetAllFields = (resource: CamelCaseResource) => { + const loadOptionsMethod = { + account: 'getAccountFields', + contact: 'getContactFields', + deal: 'getDealFields', + invoice: 'getInvoiceFields', + lead: 'getLeadFields', + product: 'getProductFields', + purchaseOrder: 'getPurchaseOrderFields', + quote: 'getQuoteFields', + salesOrder: 'getSalesOrderFields', + vendor: 'getVendorFields', + }[resource]; + + return [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + }, }, }, - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - default: 5, - description: 'The number of results to return.', - typeOptions: { - minValue: 1, - maxValue: 1000, - }, - displayOptions: { - show: { - resource: [ - resource, - ], - operation: [ - 'getAll', - ], - returnAll: [ - false, - ], + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + returnAll: [ + 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.', + }, + ], + }, + ]; +}; diff --git a/packages/nodes-base/nodes/Zoho/types.d.ts b/packages/nodes-base/nodes/Zoho/types.d.ts index e56790a772..461547111a 100644 --- a/packages/nodes-base/nodes/Zoho/types.d.ts +++ b/packages/nodes-base/nodes/Zoho/types.d.ts @@ -1,5 +1,23 @@ 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 + +type CamelToSnakeCase = + S extends `${infer S1}${infer S2}` + ? `${S1 extends Capitalize ? "_" : ""}${Lowercase}${CamelToSnakeCase}` + : S + +export type GetAllFilterOptions = { + fields: string[], + [otherOptions: string]: unknown; +}; + // ---------------------------------------- // for auth // ----------------------------------------