From c630028531b90abdc4fb18854b2c1eccd16a63d5 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 15 Feb 2020 19:23:22 -0500 Subject: [PATCH 01/20] :sparkles: Added lead resource --- .../credentials/ZohoOAuth2Api.credentials.ts | 2 +- .../nodes-base/nodes/Zoho/GenericFunctions.ts | 10 +- .../nodes-base/nodes/Zoho/LeadDescription.ts | 637 ++++++++++++++++-- .../nodes-base/nodes/Zoho/LeadInterface.ts | 37 + .../nodes-base/nodes/Zoho/ZohoCrm.node.ts | 363 +++++++++- 5 files changed, 984 insertions(+), 65 deletions(-) create mode 100644 packages/nodes-base/nodes/Zoho/LeadInterface.ts diff --git a/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts b/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts index 801b164ffd..12a83493a4 100644 --- a/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts @@ -62,7 +62,7 @@ export class ZohoOAuth2Api implements ICredentialType { displayName: 'Scope', name: 'scope', type: 'hidden' as NodePropertyTypes, - default: 'ZohoCRM.modules.ALL', + default: 'ZohoCRM.modules.ALL,ZohoCRM.settings.all,ZohoCRM.users.all', }, { displayName: 'Auth URI Query Parameters', diff --git a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts index 04fe204858..3d20a3ec02 100644 --- a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts @@ -7,6 +7,7 @@ import { import { IDataObject } from 'n8n-workflow'; +import { queryResult } from 'pg-promise'; export async function zohoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const options: OptionsWithUri = { @@ -41,14 +42,17 @@ export async function zohoApiRequestAllItems(this: IExecuteFunctions | ILoadOpti let responseData; let uri: string | undefined; + query.per_page = 200; + query.page = 0; do { responseData = await zohoApiRequest.call(this, method, endpoint, body, query, uri); - uri = responseData.nextRecordsUrl; + uri = responseData.info.more_records; returnData.push.apply(returnData, responseData[propertyName]); + query.page++; } while ( - responseData.nextRecordsUrl !== undefined && - responseData.nextRecordsUrl !== null + responseData.info.more_records !== undefined && + responseData.info.more_records === true ); return returnData; diff --git a/packages/nodes-base/nodes/Zoho/LeadDescription.ts b/packages/nodes-base/nodes/Zoho/LeadDescription.ts index 198abbe472..577fee4aec 100644 --- a/packages/nodes-base/nodes/Zoho/LeadDescription.ts +++ b/packages/nodes-base/nodes/Zoho/LeadDescription.ts @@ -28,10 +28,15 @@ export const leadOperations = [ value: 'getAll', description: 'Get data of all leads', }, + { + name: 'Get Fields', + value: 'getFields', + description: `Get the fields' metadata`, + }, { name: 'Update', value: 'update', - description: 'Update new lead', + description: 'Update a lead', }, { name: 'Delete', @@ -49,7 +54,6 @@ export const leadFields = [ /* -------------------------------------------------------------------------- */ /* lead:create */ /* -------------------------------------------------------------------------- */ - { displayName: 'Last Name', name: 'lastName', @@ -86,86 +90,617 @@ export const leadFields = [ }, options: [ { - displayName: 'Avatar', - name: 'avatar', - type: 'string', - default: '', - description: 'An avatar image URL. note: the image url needs to be https.', + displayName: 'Annual Revenue', + name: 'annualRevenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, }, { - displayName: 'Name', - name: 'name', + displayName: 'Company', + name: 'company', type: 'string', default: '', - description: 'Name of the user', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Email Opt Out', + name: 'emailOptOut', + type: 'boolean', + default: false, + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getIndustries', + }, + default: '', + }, + { + displayName: 'Is Record Duplicate', + name: 'isRecordDuplicate', + type: 'boolean', + default: false, + }, + { + displayName: 'Lead Source', + name: 'leadSource', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadSources', + }, + default: '', + }, + { + displayName: 'Lead Status', + name: 'leadStatus', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadStatuses' + }, + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'No. of Employees', + name: 'numberOfEmployees', + type: 'number', + default: 1, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, }, { displayName: 'Phone', name: 'phone', type: 'string', default: '', - description: 'The phone number of the user', }, { - displayName: 'Unsubscribed From Emails', - name: 'unsubscribedFromEmails', - type: 'boolean', - default: false, - description: 'Whether the Lead is unsubscribed from emails', - }, - { - displayName: 'Update Last Request At', - name: 'updateLastRequestAt', - type: 'boolean', - default: false, - description: 'A boolean value, which if true, instructs Intercom to update the
users last_request_at value to the current API service time in
UTC. default value if not sent is false.', - }, - { - displayName: 'Companies', - name: 'companies', - type: 'multiOptions', - typeOptions: { - loadOptionsMethod: 'getCompanies', - }, - default: [], - description: 'Identifies the companies this user belongs to.', - }, - { - displayName: 'UTM Source', - name: 'utmSource', + displayName: 'Salutation', + name: 'salutation', type: 'string', default: '', - description: 'An avatar image URL. note: the image url needs to be https.', }, { - displayName: 'UTM Medium', - name: 'utmMedium', + displayName: 'Secondary Email', + name: 'secondaryEmail', type: 'string', default: '', - description: 'Identifies what type of link was used', }, { - displayName: 'UTM Campaign', - name: 'utmCampaign', + displayName: 'Skype ID', + name: 'SkypeId', type: 'string', default: '', - description: 'Identifies a specific product promotion or strategic campaign', }, { - displayName: 'UTM Term', - name: 'utmTerm', + displayName: 'Title', + name: 'title', type: 'string', default: '', - description: 'Identifies search terms', }, { - displayName: 'UTM Content', - name: 'utmContent', + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', type: 'string', default: '', - description: 'Identifies what specifically was clicked to bring the user to the site', }, ] }, - + { + displayName: 'Address', + name: 'addressUi', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + } + ], + }, +/* -------------------------------------------------------------------------- */ +/* lead:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'lead', + ], + }, + }, + options: [ + { + displayName: 'Annual Revenue', + name: 'annualRevenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Email Opt Out', + name: 'emailOptOut', + type: 'boolean', + default: false, + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getIndustries', + }, + default: '', + }, + { + displayName: 'Is Record Duplicate', + name: 'isRecordDuplicate', + type: 'boolean', + default: false, + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: `User's last name`, + }, + { + displayName: 'Lead Source', + name: 'leadSource', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadSources', + }, + default: '', + }, + { + displayName: 'Lead Status', + name: 'leadStatus', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadStatuses' + }, + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'No. of Employees', + name: 'numberOfEmployees', + type: 'number', + default: 1, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Salutation', + name: 'salutation', + type: 'string', + default: '', + }, + { + displayName: 'Secondary Email', + name: 'secondaryEmail', + type: 'string', + default: '', + }, + { + displayName: 'Skype ID', + name: 'SkypeId', + type: 'string', + default: '', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ] + }, + { + displayName: 'Address', + name: 'addressUi', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + } + ], + }, +/* -------------------------------------------------------------------------- */ +/* lead:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'get', + ], + }, + }, + }, +/* -------------------------------------------------------------------------- */ +/* lead:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 200, + }, + default: 100, + description: 'How many results to return.', + }, + { + 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: 'To get the list of approved records. Default value is true.', + }, + { + displayName: 'Converted', + name: 'converted', + type: 'boolean', + default: false, + description: 'To get the list of converted records. Default value is false', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadFields', + }, + default: [], + }, + { + displayName: 'Include Child', + name: 'includeChild', + type: 'boolean', + default: false, + description: 'To include records from the child territories. True includes child territory records', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + }, + ], + default: 'desc', + description: 'Order sort attribute ascending or descending.', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadFields', + }, + default: [], + }, + { + displayName: 'Territory ID', + name: 'territoryId', + type: 'string', + default: '', + description: 'To get the list of records based on the territory ', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* lead:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'delete', + ], + }, + }, + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zoho/LeadInterface.ts b/packages/nodes-base/nodes/Zoho/LeadInterface.ts new file mode 100644 index 0000000000..957f816cb6 --- /dev/null +++ b/packages/nodes-base/nodes/Zoho/LeadInterface.ts @@ -0,0 +1,37 @@ +export interface ILead { + Annual_Revenue?: number; + City?: string; + Company?: string; + Country?: string; + Description?: string; + Designation?: string; + Email?: string; + Email_Opt_Out?: boolean; + Fax?: string; + First_Name?: string; + Industry?: string; + Is_Record_Duplicate?: boolean; + Last_Name?: string; + Lead_Owner?: string; + Lead_Source?: string; + Lead_Status?: string; + Mobile?: string; + No_of_Employees?: number; + Phone?: string; + Salutation?: string; + Secondary_Email?: string; + Skype_ID?: string; + State?: string; + Street?: string; + Twitter?: string; + Website?: string; + Zip_Code?: string; +} + +export interface IAddress { + street?: string; + city?: string; + state?: string; + country?: string; + zipCode?: string; +} diff --git a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts index f1d397c2dc..20a04295e0 100644 --- a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts +++ b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts @@ -7,6 +7,8 @@ import { INodeExecutionData, INodeTypeDescription, INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, } from 'n8n-workflow'; import { @@ -19,6 +21,11 @@ import { leadFields, } from './LeadDescription'; +import { + ILead, + IAddress, +} from './LeadInterface'; + export class ZohoCrm implements INodeType { description: INodeTypeDescription = { displayName: 'Zoho CRM', @@ -59,34 +66,370 @@ export class ZohoCrm implements INodeType { ], }; + methods = { + loadOptions: { + // Get all the available users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { users } = await zohoApiRequest.call(this, 'GET', '/users', {}, { type: 'AllUsers' }); + for (const user of users) { + const userName = `${user.first_name} ${user.last_name}`; + const userId = user.profile.id; + returnData.push({ + name: userName, + value: userId, + }); + } + return returnData; + }, + // Get all the available accounts to display them to user so that he can + // select them easily + async getAccounts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.sort_by = 'Created_Time'; + qs.sort_order = 'desc'; + const { data } = await zohoApiRequest.call(this, 'GET', '/accounts', {}, qs); + for (const account of data) { + const accountName = account.Account_Name + const accountId = account.id; + returnData.push({ + name: accountName, + value: accountId, + }); + } + return returnData; + }, + // Get all the available lead statuses to display them to user so that he can + // select them easily + async getLeadStatuses(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Lead_Status') { + for (const value of field.pick_list_values) { + const valueName = value.display_value + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available lead sources to display them to user so that he can + // select them easily + async getLeadSources(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Lead_Source') { + for (const value of field.pick_list_values) { + const valueName = value.display_value + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available industries to display them to user so that he can + // select them easily + async getIndustries(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Industry') { + for (const value of field.pick_list_values) { + const valueName = value.display_value + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available lead fields to display them to user so that he can + // select them easily + async getLeadFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + returnData.push({ + name: field.field_label, + value: field.api_name, + }); + } + return returnData; + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; const length = items.length as unknown as number; + const qs: IDataObject = {}; let responseData; for (let i = 0; i < length; i++) { const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; if (resource === 'lead') { + //https://www.zoho.com/crm/developer/docs/api/insert-records.html if (operation === 'create') { const lastName = this.getNodeParameter('lastName', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const body = { + const body: ILead = { Last_Name: lastName, }; - // if (additionalFields.email) { - // // @ts-ignore - // body.email = additionalFields.email as string; - // } + if (additionalFields.owner) { + body.Lead_Owner = additionalFields.owner as string; + } + if (additionalFields.company) { + body.Company = additionalFields.company as string; + } + if (additionalFields.firstName) { + body.First_Name = additionalFields.firstName as string; + } + if (additionalFields.email) { + body.Email = additionalFields.email as string; + } + if (additionalFields.title) { + body.Designation = additionalFields.title as string; + } + if (additionalFields.phone) { + body.Phone = additionalFields.phone as string; + } + if (additionalFields.mobile) { + body.Mobile = additionalFields.mobile as string; + } + if (additionalFields.leadStatus) { + body.Lead_Status = additionalFields.leadStatus as string; + } + if (additionalFields.fax) { + body.Fax = additionalFields.fax as string; + } + if (additionalFields.website) { + body.Website = additionalFields.website as string; + } + if (additionalFields.leadSource) { + body.Lead_Source = additionalFields.leadSource as string; + } + if (additionalFields.industry) { + body.Industry = additionalFields.industry as string; + } + if (additionalFields.numberOfEmployees) { + body.No_of_Employees = additionalFields.numberOfEmployees as number; + } + if (additionalFields.annualRevenue) { + body.Annual_Revenue = additionalFields.annualRevenue as number; + } + if (additionalFields.emailOptOut) { + body.Email_Opt_Out = additionalFields.emailOptOut as boolean; + } + if (additionalFields.skypeId) { + body.Skype_ID = additionalFields.skypeId as string; + } + if (additionalFields.salutation) { + body.Salutation = additionalFields.salutation as string; + } + if (additionalFields.secondaryEmail) { + body.Secondary_Email = additionalFields.secondaryEmail as string; + } + if (additionalFields.twitter) { + body.Twitter = additionalFields.twitter as string; + } + if (additionalFields.isRecordDuplicate) { + body.Is_Record_Duplicate = additionalFields.isRecordDuplicate as boolean; + } + if (additionalFields.description) { + body.Description = additionalFields.description as string; + } + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValues as IAddress; + if (address) { + if (address.country) { + body.Country = address.country as string; + } + if (address.city) { + body.City = address.city as string; + } + if (address.state) { + body.State = address.state as string; + } + if (address.street) { + body.Street = address.street as string; + } + if (address.zipCode) { + body.Zip_Code = address.zipCode as string; + } + } responseData = await zohoApiRequest.call(this, 'POST', '/leads', body); responseData = responseData.data; - } else { - throw new Error(`The operation "${operation}" is not known!`); } - } else { - throw new Error(`The resource "${resource}" is not known!`); + //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html + if (operation === 'update') { + const leadId = this.getNodeParameter('leadId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: ILead = {}; + if (additionalFields.lastName) { + body.Last_Name = additionalFields.lastName as string; + } + if (additionalFields.owner) { + body.Lead_Owner = additionalFields.owner as string; + } + if (additionalFields.company) { + body.Company = additionalFields.company as string; + } + if (additionalFields.firstName) { + body.First_Name = additionalFields.firstName as string; + } + if (additionalFields.email) { + body.Email = additionalFields.email as string; + } + if (additionalFields.title) { + body.Designation = additionalFields.title as string; + } + if (additionalFields.phone) { + body.Phone = additionalFields.phone as string; + } + if (additionalFields.mobile) { + body.Mobile = additionalFields.mobile as string; + } + if (additionalFields.leadStatus) { + body.Lead_Status = additionalFields.leadStatus as string; + } + if (additionalFields.fax) { + body.Fax = additionalFields.fax as string; + } + if (additionalFields.website) { + body.Website = additionalFields.website as string; + } + if (additionalFields.leadSource) { + body.Lead_Source = additionalFields.leadSource as string; + } + if (additionalFields.industry) { + body.Industry = additionalFields.industry as string; + } + if (additionalFields.numberOfEmployees) { + body.No_of_Employees = additionalFields.numberOfEmployees as number; + } + if (additionalFields.annualRevenue) { + body.Annual_Revenue = additionalFields.annualRevenue as number; + } + if (additionalFields.emailOptOut) { + body.Email_Opt_Out = additionalFields.emailOptOut as boolean; + } + if (additionalFields.skypeId) { + body.Skype_ID = additionalFields.skypeId as string; + } + if (additionalFields.salutation) { + body.Salutation = additionalFields.salutation as string; + } + if (additionalFields.secondaryEmail) { + body.Secondary_Email = additionalFields.secondaryEmail as string; + } + if (additionalFields.twitter) { + body.Twitter = additionalFields.twitter as string; + } + if (additionalFields.isRecordDuplicate) { + body.Is_Record_Duplicate = additionalFields.isRecordDuplicate as boolean; + } + if (additionalFields.description) { + body.Description = additionalFields.description as string; + } + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValues as IAddress; + if (address) { + if (address.country) { + body.Country = address.country as string; + } + if (address.city) { + body.City = address.city as string; + } + if (address.state) { + body.State = address.state as string; + } + if (address.street) { + body.Street = address.street as string; + } + if (address.zipCode) { + body.Zip_Code = address.zipCode as string; + } + } + responseData = await zohoApiRequest.call(this, 'PUT', `/leads/${leadId}`, body); + responseData = responseData.data; + } + //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html + if (operation === 'get') { + const leadId = this.getNodeParameter('leadId', i) as string; + responseData = await zohoApiRequest.call(this, 'GET', `/leads/${leadId}`); + responseData = responseData.data; + } + //https://www.zoho.com/crm/developer/docs/api/get-records.html + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.fields) { + qs.fields = (options.fields as string[]).join(','); + } + if (options.approved) { + qs.approved = options.approved as boolean; + } + if (options.converted) { + qs.converted = options.converted as boolean; + } + if (options.includeChild) { + qs.include_child = options.includeChild as boolean; + } + if (options.sortOrder) { + qs.sort_order = options.sortOrder as string; + } + if (options.sortBy) { + qs.sort_by = options.sortBy as string; + } + if (options.territoryId) { + qs.territory_id = options.territoryId as string; + } + if (returnAll) { + responseData = await zohoApiRequestAllItems.call(this, 'data', 'GET', '/leads', {}, qs); + } else { + qs.per_page = this.getNodeParameter('limit', i) as number; + responseData = await zohoApiRequest.call(this, 'GET', '/leads', {}, qs); + responseData = responseData.data; + } + } + //https://www.zoho.com/crm/developer/docs/api/delete-specific-record.html + if (operation === 'delete') { + const leadId = this.getNodeParameter('leadId', i) as string; + responseData = await zohoApiRequest.call(this, 'DELETE', `/leads/${leadId}`); + responseData = responseData.data; + } + //https://www.zoho.com/crm/developer/docs/api/field-meta.html + if (operation === 'getFields') { + qs.module = 'leads'; + responseData = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + responseData = responseData.fields; + } } - if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else if (responseData !== undefined) { From b3a9eb08c1aabace44d3beea46d53f9e1cc2a8a9 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 15 Feb 2020 19:25:14 -0500 Subject: [PATCH 02/20] :zap: small fix --- packages/nodes-base/nodes/Zoho/GenericFunctions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts index 3d20a3ec02..4deea5f6f5 100644 --- a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts @@ -7,7 +7,6 @@ import { import { IDataObject } from 'n8n-workflow'; -import { queryResult } from 'pg-promise'; export async function zohoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const options: OptionsWithUri = { From 3fd4884667b167d4773001efb8cc2406328a6113 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 15 Mar 2020 19:51:31 -0400 Subject: [PATCH 03/20] :sparkles: Microsoft Excel node --- packages/cli/src/Server.ts | 4 +- .../MicrosoftOAuth2Api.credentials.ts | 45 ++ .../nodes/Microsoft/GenericFunctions.ts | 73 +++ .../nodes/Microsoft/MicrosoftExcel.node.ts | 335 +++++++++++++ .../nodes/Microsoft/TableDescription.ts | 447 ++++++++++++++++++ .../nodes/Microsoft/WorkbookDescription.ts | 154 ++++++ .../nodes/Microsoft/WorksheetDescription.ts | 283 +++++++++++ packages/nodes-base/nodes/Microsoft/excel.png | Bin 0 -> 5984 bytes packages/nodes-base/package.json | 6 +- 9 files changed, 1344 insertions(+), 3 deletions(-) create mode 100644 packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Microsoft/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts create mode 100644 packages/nodes-base/nodes/Microsoft/TableDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/excel.png diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 4fe1ef70d7..5a1c0703bf 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -203,7 +203,9 @@ class App { }); } - jwt.verify(token, getKey, {}, (err: Error, decoded: string) => { + jwt.verify(token, getKey, {}, (err: Error, + //decoded: string + ) => { if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token"); next(); diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts new file mode 100644 index 0000000000..2519ba583b --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts @@ -0,0 +1,45 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOAuth2Api implements ICredentialType { + name = 'microsoftOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', + }, + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access Files.ReadWrite', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'response_mode=query', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts new file mode 100644 index 0000000000..b4f68be343 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts @@ -0,0 +1,73 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { + IDataObject +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'microsoftOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts new file mode 100644 index 0000000000..c62730449d --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts @@ -0,0 +1,335 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, + microsoftApiRequestAllItemsSkip, +} from './GenericFunctions'; + +import { + workbookOperations, + workbookFields, +} from './WorkbookDescription'; + +import { + worksheetOperations, + worksheetFields, +} from './WorksheetDescription'; + +import { + tableOperations, + tableFields, +} from './TableDescription'; + +export class MicrosoftExcel implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft Excel', + name: 'microsoftExcel', + icon: 'file:excel.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft Excel API.', + defaults: { + name: 'Microsoft Excel', + color: '#1c6d40', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Table', + value: 'table', + description: 'Represents an Excel table.', + }, + { + name: 'Workbook', + value: 'workbook', + description: 'Workbook is the top level object which contains related workbook objects such as worksheets, tables, ranges, etc.', + }, + { + name: 'Worksheet', + value: 'worksheet', + description: 'An Excel worksheet is a grid of cells. It can contain data, tables, charts, etc.', + }, + ], + default: 'workbook', + description: 'The resource to operate on.', + }, + ...workbookOperations, + ...workbookFields, + ...worksheetOperations, + ...worksheetFields, + ...tableOperations, + ...tableFields, + ], + }; + + methods = { + loadOptions: { + // Get all the workbooks to display them to user so that he can + // select them easily + async getWorkbooks(this: ILoadOptionsFunctions): Promise { + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const workbooks = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + for (const workbook of workbooks) { + const workbookName = workbook.name; + const workbookId = workbook.id; + returnData.push({ + name: workbookName, + value: workbookId, + }); + } + return returnData; + }, + // Get all the worksheets to display them to user so that he can + // select them easily + async getworksheets(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const worksheets = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + for (const worksheet of worksheets) { + const worksheetName = worksheet.name; + const worksheetId = worksheet.id; + returnData.push({ + name: worksheetName, + value: worksheetId, + }); + } + return returnData; + }, + // Get all the tables to display them to user so that he can + // select them easily + async getTables(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const worksheetId = this.getCurrentNodeParameter('worksheet'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const tables = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables`, {}, qs); + for (const table of tables) { + const tableName = table.name; + const tableId = table.id; + returnData.push({ + name: tableName, + value: tableId, + }); + } + return returnData; + }, + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + const result: IDataObject[] = []; + const object: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'table') { + //https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http + if (operation === 'addRow') { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject; + const body: IDataObject = {}; + if (Object.keys(items[0].json).length === 0) { + throw new Error('Input cannot be empty'); + } + if (additionalFields.index) { + body.index = additionalFields.index as number; + } + const values: any[][] = []; + for (const item of items) { + values.push(Object.values(item.json)); + } + body.values = values; + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + } + //https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http + if (operation === 'getColumns') { + for (let i = 0; i < length; i++) { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + responseData = responseData.value; + } + if (!rawData) { + //@ts-ignore + responseData = responseData.map(column => ({ name: column.name })); + } + } + } + //https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http + if (operation === 'getRows') { + for (let i = 0; i < length; i++) { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + responseData = responseData.value; + } + if (!rawData) { + qs['$select'] = 'name'; + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + //@ts-ignore + columns = columns.map(column => column.name); + for (let i = 0; i < responseData.length; i++) { + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + result.push({ ...object }); + } + responseData = result; + } + } + } + } + if (resource === 'workbook') { + for (let i = 0; i < length; i++) { + //https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http + if (operation === 'addWorksheet') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = {}; + if (additionalFields.name) { + body.name = additionalFields.name; + } + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + responseData = responseData.value; + } + } + } + } + if (resource === 'worksheet') { + for (let i = 0; i < length; i++) { + //https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const workbookId = this.getNodeParameter('workbook', i) as string; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + responseData = responseData.value; + } + } + //https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http + if (operation === 'getContent') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const range = this.getNodeParameter('range', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`, {}, qs); + if (!rawData) { + const keyRow = this.getNodeParameter('keyRow', i) as number; + const dataStartRow = this.getNodeParameter('dataStartRow', i) as number; + if (responseData.values === null) { + throw new Error('Range did not return data'); + } + const keyValues = responseData.values[keyRow]; + for (let i = dataStartRow; i < responseData.values.length; i++) { + for (let y = 0; y < keyValues.length; y++) { + object[keyValues[y]] = responseData.values[i][y]; + } + result.push({ ...object }); + } + responseData = result; + } + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Microsoft/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/TableDescription.ts new file mode 100644 index 0000000000..6dc6de780b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/TableDescription.ts @@ -0,0 +1,447 @@ +import { INodeProperties } from "n8n-workflow"; + +export const tableOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'table', + ], + }, + }, + options: [ + { + name: 'Add Row', + value: 'addRow', + description: 'Adds rows to the end of the table' + }, + { + name: 'Get Columns', + value: 'getColumns', + description: 'Retrieve a list of tablecolumns', + }, + { + name: 'Get Rows', + value: 'getRows', + description: 'Retrieve a list of tablerows', + }, + ], + default: 'addRow', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const tableFields = [ + +/* -------------------------------------------------------------------------- */ +/* table:addRow */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + options: [ + { + displayName: 'Index', + name: 'index', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: `Specifies the relative position of the new row. If not defined,
+ the addition happens at the end. Any rows below the inserted row are shifted downwards. Zero-indexed`, + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* table:getRows */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* table:getColumns */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + rawData: [ + true + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts b/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts new file mode 100644 index 0000000000..526a8ceb43 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts @@ -0,0 +1,154 @@ +import { INodeProperties } from "n8n-workflow"; + +export const workbookOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + name: 'Add Worksheet', + value: 'addWorksheet', + description: 'Adds a new worksheet to the workbook.', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get data of all workbooks', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const workbookFields = [ + +/* -------------------------------------------------------------------------- */ +/* workbook:addWorksheet */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `The name of the worksheet to be added. If specified, name should be unqiue.
+ If not specified, Excel determines the name of the new worksheet.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* workbook:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts new file mode 100644 index 0000000000..204e5f0e41 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts @@ -0,0 +1,283 @@ +import { INodeProperties } from "n8n-workflow"; + +export const worksheetOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all worksheets', + }, + { + name: 'Get Content', + value: 'getContent', + description: 'Get worksheet content', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const worksheetFields = [ + +/* -------------------------------------------------------------------------- */ +/* worksheet:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* worksheet:getContent */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Range', + name: 'range', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: 'A1:C3', + required: true, + description: 'The address or the name of the range. If not specified, the entire worksheet range is returned.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Data Start Row', + name: 'dataStartRow', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + description: 'Index of the first row which contains
the actual data and not the keys. Starts with 0.', + }, + { + displayName: 'Key Row', + name: 'keyRow', + type: 'number', + typeOptions: { + minValue: 0, + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + default: 0, + description: 'Index of the row which contains the keys. Starts at 0.
The incoming node data is matched to the keys for assignment. The matching is case sensitve.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/excel.png b/packages/nodes-base/nodes/Microsoft/excel.png new file mode 100644 index 0000000000000000000000000000000000000000..40631413a5f656ad5819f9faada8eecc78509bd4 GIT binary patch literal 5984 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 bdqb4Qmz%rb(0TCZ=~zQWN4Z?lGU$H*2Ppz* literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 746f91f723..47f21e8396 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -54,7 +54,8 @@ "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -132,7 +133,8 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Merge.node.js", + "dist/nodes/Merge.node.js", + "dist/nodes/Microsoft/MicrosoftExcel.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js", From 07246a0b156806083c2876b4508ddbfe457265c5 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 16 Mar 2020 22:02:48 -0400 Subject: [PATCH 04/20] :sparkles: HelpScout Integration --- packages/cli/src/Server.ts | 2 +- .../HelpScoutOAuth2Api.credentials.ts | 46 + .../HelpScout/ConversationDescription.ts | 598 +++++++ .../nodes/HelpScout/ConversationInterface.ts | 18 + .../nodes/HelpScout/CountriesCodes.ts | 1579 +++++++++++++++++ .../nodes/HelpScout/CustomerDescription.ts | 811 +++++++++ .../nodes/HelpScout/CustomerInterface.ts | 20 + .../nodes/HelpScout/GenericFunctions.ts | 69 + .../nodes/HelpScout/HelpScout.node.ts | 410 +++++ .../nodes/HelpScout/HelpScoutTrigger.node.ts | 202 +++ .../nodes/HelpScout/MailboxDescription.ts | 54 + .../nodes/HelpScout/ThreadDescription.ts | 257 +++ .../nodes/HelpScout/ThreadInterface.ts | 15 + .../nodes-base/nodes/HelpScout/helpScout.png | Bin 0 -> 4862 bytes packages/nodes-base/package.json | 7 +- 15 files changed, 4085 insertions(+), 3 deletions(-) create mode 100644 packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/HelpScout/ConversationDescription.ts create mode 100644 packages/nodes-base/nodes/HelpScout/ConversationInterface.ts create mode 100644 packages/nodes-base/nodes/HelpScout/CountriesCodes.ts create mode 100644 packages/nodes-base/nodes/HelpScout/CustomerDescription.ts create mode 100644 packages/nodes-base/nodes/HelpScout/CustomerInterface.ts create mode 100644 packages/nodes-base/nodes/HelpScout/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/HelpScout/HelpScout.node.ts create mode 100644 packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts create mode 100644 packages/nodes-base/nodes/HelpScout/MailboxDescription.ts create mode 100644 packages/nodes-base/nodes/HelpScout/ThreadDescription.ts create mode 100644 packages/nodes-base/nodes/HelpScout/ThreadInterface.ts create mode 100644 packages/nodes-base/nodes/HelpScout/helpScout.png diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 4fe1ef70d7..99cda09412 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -203,7 +203,7 @@ class App { }); } - jwt.verify(token, getKey, {}, (err: Error, decoded: string) => { + jwt.verify(token, getKey, {}, (err: Error, decoded: object) => { if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token"); next(); diff --git a/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts b/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts new file mode 100644 index 0000000000..0301bf2c51 --- /dev/null +++ b/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts @@ -0,0 +1,46 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HelpScoutOAuth2Api implements ICredentialType { + name = 'helpScoutOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'HelpScout OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://secure.helpscout.net/authentication/authorizeClientApplication', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.helpscout.net/v2/oauth2/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts new file mode 100644 index 0000000000..cce35b096a --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts @@ -0,0 +1,598 @@ +import { INodeProperties } from "n8n-workflow"; + +export const conversationOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'conversation', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new conversation', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a conversation', + }, + { + name: 'Get', + value: 'get', + description: 'Get a conversation', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all conversations', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const conversationFields = [ +/* -------------------------------------------------------------------------- */ +/* conversation:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Mailbox', + name: 'mailboxId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMailboxes', + }, + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'ID of a mailbox where the conversation is being created', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + required: true, + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'Closed', + value: 'closed', + }, + { + name: 'Pending', + value: 'pending', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'Conversation status', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + required: true, + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: `Conversation’s subject`, + }, + { + displayName: 'Type', + name: 'type', + required: true, + type: 'options', + options: [ + { + name: 'Chat', + value: 'chat', + }, + { + name: 'Email', + value: 'email', + }, + { + name: 'Phone', + value: 'phone', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'Conversation type', + }, + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + description: 'By default the response only contain the ID to resource
. If this option gets activated it
will resolve the data automatically.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + options: [ + { + displayName: 'Assign To', + name: 'assignTo', + type: 'number', + default: 0, + description: 'The Help Scout user assigned to the conversation.', + }, + { + displayName: 'Auto Reply', + name: 'autoReply', + type: 'boolean', + default: false, + description: `When autoReply is set to true, an auto reply will be sent
+ as long as there is at least one customer thread in the conversation.`, + }, + { + displayName: 'Closed At', + name: 'closedAt', + type: 'dateTime', + default: '', + description: `When the conversation was closed, only applicable for imported conversations`, + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: `When this conversation was created - ISO 8601 date time`, + }, + { + displayName: 'Customer Email', + name: 'customerEmail', + type: 'string', + default: '', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'number', + default: 0, + }, + { + displayName: 'Imported', + name: 'imported', + type: 'boolean', + default: false, + description: `When imported is set to true, no outgoing emails or notifications will be generated.`, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'List of tags to be be added to the conversation', + }, + { + displayName: 'User ID', + name: 'user', + type: 'number', + default: 0, + description: 'ID of the user who is adding the conversation and threads.', + }, + ] + }, + { + displayName: 'Threads', + name: 'threadsUi', + placeholder: 'Add Thread', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Thread', + name: 'threadsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Customer', + value: 'customer' + }, + { + name: 'Note', + value: 'note' + }, + { + name: 'Phone', + value: 'phone' + }, + { + name: 'Reply', + value: 'reply' + }, + ], + default: '', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true + }, + default: '', + description: 'The message text, ' + }, + { + displayName: 'Bcc', + name: 'bcc', + displayOptions: { + show: { + type: [ + 'customer' + ], + }, + }, + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: [], + description: 'Email addresses.' + }, + { + displayName: 'Cc', + name: 'cc', + displayOptions: { + show: { + type: [ + 'customer' + ], + }, + }, + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: [], + description: 'Email addresses.' + }, + { + displayName: 'Draft', + name: 'draft', + displayOptions: { + show: { + type: [ + 'reply' + ], + }, + }, + type: 'boolean', + default: false, + description: 'If set to true, a draft reply is created', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* conversation:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'get', + ], + }, + }, + description: 'conversation ID', + }, +/* -------------------------------------------------------------------------- */ +/* conversation:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'conversation ID', + }, +/* -------------------------------------------------------------------------- */ +/* conversation:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Embed', + name: 'embed', + type: 'options', + options: [ + { + name: 'Threads', + value: 'threads', + }, + ], + default: '', + description: 'Allows embedding/loading of sub-entities', + }, + { + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters conversations from a specific mailbox', + }, + { + displayName: 'Folder ID', + name: 'folder', + type: 'string', + default: '', + description: 'Filters conversations from a specific folder id', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'All', + value: 'all', + }, + { + name: 'Closed', + value: 'closed', + }, + { + name: 'Open', + value: 'open', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Spam', + value: 'spam', + }, + ], + default: 'active', + description: 'Filter conversation by status', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'Filter conversation by tags', + }, + { + displayName: 'Assign To', + name: 'assignTo', + type: 'number', + default: 0, + description: 'Filters conversations by assignee id', + }, + { + displayName: 'Modified Since', + name: 'modifiedSince', + type: 'dateTime', + default: '', + description: 'Returns only conversations that were modified after this date', + }, + { + displayName: 'Number', + name: 'number', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: 'Looks up conversation by conversation number', + }, + { + displayName: 'Sort Field', + name: 'sortField', + type: 'options', + options: [ + { + name: 'Created At', + value: 'createdAt', + }, + { + name: 'customer Email', + value: 'customerEmail', + }, + { + name: 'customer Name', + value: 'customerName', + }, + { + name: 'Mailbox ID', + value: 'mailboxid', + }, + { + name: 'Modified At', + value: 'modifiedAt', + }, + { + name: 'Number', + value: 'number', + }, + { + name: 'Score', + value: 'score', + }, + { + name: 'Status', + value: 'status', + }, + { + name: 'Subject', + value: 'subject', + }, + ], + default: '', + description: 'Sorts the result by specified field', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'Desc', + value: 'desc', + }, + ], + default: 'desc', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Advanced search Examples' + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts new file mode 100644 index 0000000000..4140fda218 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts @@ -0,0 +1,18 @@ +import { IDataObject } from "n8n-workflow"; + +export interface IConversation { + assignTo?: number; + autoReply?: boolean; + closedAt?: string; + createdAt?: string; + customer?: IDataObject; + fields?: IDataObject[]; + imported?: boolean; + mailboxId?: number; // + status?: string; // + subject?: string; // + tags?: IDataObject[]; + threads?: IDataObject[]; + type?: string; // + user?: number; +} diff --git a/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts new file mode 100644 index 0000000000..653e876beb --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts @@ -0,0 +1,1579 @@ +export const countriesCodes = [ + { + "name": "Afghanistan", + "alpha2": "AF", + "alpha3": "AFG", + "numeric": "004" + }, + { + "name": "Åland Islands", + "alpha2": "AX", + "alpha3": "ALA", + "numeric": "248", + "altName": "Aland Islands" + }, + { + "name": "Albania", + "alpha2": "AL", + "alpha3": "ALB", + "numeric": "008" + }, + { + "name": "Algeria", + "alpha2": "DZ", + "alpha3": "DZA", + "numeric": "012" + }, + { + "name": "American Samoa", + "alpha2": "AS", + "alpha3": "ASM", + "numeric": "016" + }, + { + "name": "Andorra", + "alpha2": "AD", + "alpha3": "AND", + "numeric": "020" + }, + { + "name": "Angola", + "alpha2": "AO", + "alpha3": "AGO", + "numeric": "024" + }, + { + "name": "Anguilla", + "alpha2": "AI", + "alpha3": "AIA", + "numeric": "660" + }, + { + "name": "Antarctica", + "alpha2": "AQ", + "alpha3": "ATA", + "numeric": "010" + }, + { + "name": "Antigua and Barbuda", + "alpha2": "AG", + "alpha3": "ATG", + "numeric": "028" + }, + { + "name": "Argentina", + "alpha2": "AR", + "alpha3": "ARG", + "numeric": "032" + }, + { + "name": "Armenia", + "alpha2": "AM", + "alpha3": "ARM", + "numeric": "051" + }, + { + "name": "Aruba", + "alpha2": "AW", + "alpha3": "ABW", + "numeric": "533" + }, + { + "name": "Australia", + "alpha2": "AU", + "alpha3": "AUS", + "numeric": "036" + }, + { + "name": "Austria", + "alpha2": "AT", + "alpha3": "AUT", + "numeric": "040" + }, + { + "name": "Azerbaijan", + "alpha2": "AZ", + "alpha3": "AZE", + "numeric": "031" + }, + { + "name": "Bahamas (the)", + "alpha2": "BS", + "alpha3": "BHS", + "numeric": "044", + "altName": "Bahamas" + }, + { + "name": "Bahrain", + "alpha2": "BH", + "alpha3": "BHR", + "numeric": "048" + }, + { + "name": "Bangladesh", + "alpha2": "BD", + "alpha3": "BGD", + "numeric": "050" + }, + { + "name": "Barbados", + "alpha2": "BB", + "alpha3": "BRB", + "numeric": "052" + }, + { + "name": "Belarus", + "alpha2": "BY", + "alpha3": "BLR", + "numeric": "112" + }, + { + "name": "Belgium", + "alpha2": "BE", + "alpha3": "BEL", + "numeric": "056" + }, + { + "name": "Belize", + "alpha2": "BZ", + "alpha3": "BLZ", + "numeric": "084" + }, + { + "name": "Benin", + "alpha2": "BJ", + "alpha3": "BEN", + "numeric": "204" + }, + { + "name": "Bermuda", + "alpha2": "BM", + "alpha3": "BMU", + "numeric": "060" + }, + { + "name": "Bhutan", + "alpha2": "BT", + "alpha3": "BTN", + "numeric": "064" + }, + { + "name": "Bolivia (Plurinational State of)", + "alpha2": "BO", + "alpha3": "BOL", + "numeric": "068", + "altName": "Bolivia" + }, + { + "name": "Bonaire, Sint Eustatius and Saba", + "alpha2": "BQ", + "alpha3": "BES", + "numeric": "535" + }, + { + "name": "Bosnia and Herzegovina", + "alpha2": "BA", + "alpha3": "BIH", + "numeric": "070" + }, + { + "name": "Botswana", + "alpha2": "BW", + "alpha3": "BWA", + "numeric": "072" + }, + { + "name": "Bouvet Island", + "alpha2": "BV", + "alpha3": "BVT", + "numeric": "074" + }, + { + "name": "Brazil", + "alpha2": "BR", + "alpha3": "BRA", + "numeric": "076" + }, + { + "name": "British Indian Ocean Territory (the)", + "alpha2": "IO", + "alpha3": "IOT", + "numeric": "086", + "altName": "British Indian Ocean Territory" + }, + { + "name": "Brunei Darussalam", + "alpha2": "BN", + "alpha3": "BRN", + "numeric": "096", + "shortName": "Brunei" + }, + { + "name": "Bulgaria", + "alpha2": "BG", + "alpha3": "BGR", + "numeric": "100" + }, + { + "name": "Burkina Faso", + "alpha2": "BF", + "alpha3": "BFA", + "numeric": "854" + }, + { + "name": "Burundi", + "alpha2": "BI", + "alpha3": "BDI", + "numeric": "108" + }, + { + "name": "Cabo Verde", + "alpha2": "CV", + "alpha3": "CPV", + "numeric": "132", + "altName": "Cape Verde" + }, + { + "name": "Cambodia", + "alpha2": "KH", + "alpha3": "KHM", + "numeric": "116" + }, + { + "name": "Cameroon", + "alpha2": "CM", + "alpha3": "CMR", + "numeric": "120" + }, + { + "name": "Canada", + "alpha2": "CA", + "alpha3": "CAN", + "numeric": "124" + }, + { + "name": "Cayman Islands (the)", + "alpha2": "KY", + "alpha3": "CYM", + "numeric": "136", + "altName": "Cayman Islands" + }, + { + "name": "Central African Republic (the)", + "alpha2": "CF", + "alpha3": "CAF", + "numeric": "140", + "altName": "Central African Republic" + }, + { + "name": "Chad", + "alpha2": "TD", + "alpha3": "TCD", + "numeric": "148" + }, + { + "name": "Chile", + "alpha2": "CL", + "alpha3": "CHL", + "numeric": "152" + }, + { + "name": "China", + "alpha2": "CN", + "alpha3": "CHN", + "numeric": "156" + }, + { + "name": "Christmas Island", + "alpha2": "CX", + "alpha3": "CXR", + "numeric": "162" + }, + { + "name": "Cocos (Keeling) Islands (the)", + "alpha2": "CC", + "alpha3": "CCK", + "numeric": "166", + "altName": "Cocos (Keeling) Islands", + "shortName": "Cocos Islands" + }, + { + "name": "Colombia", + "alpha2": "CO", + "alpha3": "COL", + "numeric": "170" + }, + { + "name": "Comoros (the)", + "alpha2": "KM", + "alpha3": "COM", + "numeric": "174", + "altName": "Comoros" + }, + { + "name": "Congo (the Democratic Republic of the)", + "alpha2": "CD", + "alpha3": "COD", + "numeric": "180", + "altName": "Congo, (Kinshasa)", + "shortName": "Democratic Republic of the Congo" + }, + { + "name": "Congo (the)", + "alpha2": "CG", + "alpha3": "COG", + "numeric": "178", + "altName": "Congo (Brazzaville)", + "shortName": "Republic of the Congo" + }, + { + "name": "Cook Islands (the)", + "alpha2": "CK", + "alpha3": "COK", + "numeric": "184", + "altName": "Cook Islands" + }, + { + "name": "Costa Rica", + "alpha2": "CR", + "alpha3": "CRI", + "numeric": "188" + }, + { + "name": "Côte d'Ivoire", + "alpha2": "CI", + "alpha3": "CIV", + "numeric": "384", + "shortName": "Ivory Coast" + }, + { + "name": "Croatia", + "alpha2": "HR", + "alpha3": "HRV", + "numeric": "191" + }, + { + "name": "Cuba", + "alpha2": "CU", + "alpha3": "CUB", + "numeric": "192" + }, + { + "name": "Curaçao", + "alpha2": "CW", + "alpha3": "CUW", + "numeric": "531", + "shortName": "Curacao" + }, + { + "name": "Cyprus", + "alpha2": "CY", + "alpha3": "CYP", + "numeric": "196" + }, + { + "name": "Czechia", + "alpha2": "CZ", + "alpha3": "CZE", + "numeric": "203", + "altName": "Czech Republic" + }, + { + "name": "Denmark", + "alpha2": "DK", + "alpha3": "DNK", + "numeric": "208" + }, + { + "name": "Djibouti", + "alpha2": "DJ", + "alpha3": "DJI", + "numeric": "262" + }, + { + "name": "Dominica", + "alpha2": "DM", + "alpha3": "DMA", + "numeric": "212" + }, + { + "name": "Dominican Republic (the)", + "alpha2": "DO", + "alpha3": "DOM", + "numeric": "214", + "altName": "Dominican Republic" + }, + { + "name": "Ecuador", + "alpha2": "EC", + "alpha3": "ECU", + "numeric": "218" + }, + { + "name": "Egypt", + "alpha2": "EG", + "alpha3": "EGY", + "numeric": "818" + }, + { + "name": "El Salvador", + "alpha2": "SV", + "alpha3": "SLV", + "numeric": "222" + }, + { + "name": "Equatorial Guinea", + "alpha2": "GQ", + "alpha3": "GNQ", + "numeric": "226" + }, + { + "name": "Eritrea", + "alpha2": "ER", + "alpha3": "ERI", + "numeric": "232" + }, + { + "name": "Estonia", + "alpha2": "EE", + "alpha3": "EST", + "numeric": "233" + }, + { + "name": "Ethiopia", + "alpha2": "ET", + "alpha3": "ETH", + "numeric": "231" + }, + { + "name": "Falkland Islands (the) [Malvinas]", + "alpha2": "FK", + "alpha3": "FLK", + "numeric": "238", + "altName": "Falkland Islands (Malvinas)", + "shortName": "Falkland Islands" + }, + { + "name": "Faroe Islands (the)", + "alpha2": "FO", + "alpha3": "FRO", + "numeric": "234", + "altName": "Faroe Islands" + }, + { + "name": "Fiji", + "alpha2": "FJ", + "alpha3": "FJI", + "numeric": "242" + }, + { + "name": "Finland", + "alpha2": "FI", + "alpha3": "FIN", + "numeric": "246" + }, + { + "name": "France", + "alpha2": "FR", + "alpha3": "FRA", + "numeric": "250" + }, + { + "name": "French Guiana", + "alpha2": "GF", + "alpha3": "GUF", + "numeric": "254" + }, + { + "name": "French Polynesia", + "alpha2": "PF", + "alpha3": "PYF", + "numeric": "258" + }, + { + "name": "French Southern Territories (the)", + "alpha2": "TF", + "alpha3": "ATF", + "numeric": "260", + "altName": "French Southern Territories" + }, + { + "name": "Gabon", + "alpha2": "GA", + "alpha3": "GAB", + "numeric": "266" + }, + { + "name": "Gambia (the)", + "alpha2": "GM", + "alpha3": "GMB", + "numeric": "270", + "altName": "Gambia" + }, + { + "name": "Georgia", + "alpha2": "GE", + "alpha3": "GEO", + "numeric": "268" + }, + { + "name": "Germany", + "alpha2": "DE", + "alpha3": "DEU", + "numeric": "276" + }, + { + "name": "Ghana", + "alpha2": "GH", + "alpha3": "GHA", + "numeric": "288" + }, + { + "name": "Gibraltar", + "alpha2": "GI", + "alpha3": "GIB", + "numeric": "292" + }, + { + "name": "Greece", + "alpha2": "GR", + "alpha3": "GRC", + "numeric": "300" + }, + { + "name": "Greenland", + "alpha2": "GL", + "alpha3": "GRL", + "numeric": "304" + }, + { + "name": "Grenada", + "alpha2": "GD", + "alpha3": "GRD", + "numeric": "308" + }, + { + "name": "Guadeloupe", + "alpha2": "GP", + "alpha3": "GLP", + "numeric": "312" + }, + { + "name": "Guam", + "alpha2": "GU", + "alpha3": "GUM", + "numeric": "316" + }, + { + "name": "Guatemala", + "alpha2": "GT", + "alpha3": "GTM", + "numeric": "320" + }, + { + "name": "Guernsey", + "alpha2": "GG", + "alpha3": "GGY", + "numeric": "831" + }, + { + "name": "Guinea", + "alpha2": "GN", + "alpha3": "GIN", + "numeric": "324" + }, + { + "name": "Guinea-Bissau", + "alpha2": "GW", + "alpha3": "GNB", + "numeric": "624" + }, + { + "name": "Guyana", + "alpha2": "GY", + "alpha3": "GUY", + "numeric": "328" + }, + { + "name": "Haiti", + "alpha2": "HT", + "alpha3": "HTI", + "numeric": "332" + }, + { + "name": "Heard Island and McDonald Islands", + "alpha2": "HM", + "alpha3": "HMD", + "numeric": "334", + "altName": "Heard and Mcdonald Islands" + }, + { + "name": "Holy See (the)", + "alpha2": "VA", + "alpha3": "VAT", + "numeric": "336", + "altName": "Holy See (Vatican City State)", + "shortName": "Vatican" + }, + { + "name": "Honduras", + "alpha2": "HN", + "alpha3": "HND", + "numeric": "340" + }, + { + "name": "Hong Kong", + "alpha2": "HK", + "alpha3": "HKG", + "numeric": "344", + "altName": "Hong Kong, SAR China" + }, + { + "name": "Hungary", + "alpha2": "HU", + "alpha3": "HUN", + "numeric": "348" + }, + { + "name": "Iceland", + "alpha2": "IS", + "alpha3": "ISL", + "numeric": "352" + }, + { + "name": "India", + "alpha2": "IN", + "alpha3": "IND", + "numeric": "356" + }, + { + "name": "Indonesia", + "alpha2": "ID", + "alpha3": "IDN", + "numeric": "360" + }, + { + "name": "Iran (Islamic Republic of)", + "alpha2": "IR", + "alpha3": "IRN", + "numeric": "364", + "altName": "Iran, Islamic Republic of", + "shortName": "Iran" + }, + { + "name": "Iraq", + "alpha2": "IQ", + "alpha3": "IRQ", + "numeric": "368" + }, + { + "name": "Ireland", + "alpha2": "IE", + "alpha3": "IRL", + "numeric": "372" + }, + { + "name": "Isle of Man", + "alpha2": "IM", + "alpha3": "IMN", + "numeric": "833" + }, + { + "name": "Israel", + "alpha2": "IL", + "alpha3": "ISR", + "numeric": "376" + }, + { + "name": "Italy", + "alpha2": "IT", + "alpha3": "ITA", + "numeric": "380" + }, + { + "name": "Jamaica", + "alpha2": "JM", + "alpha3": "JAM", + "numeric": "388" + }, + { + "name": "Japan", + "alpha2": "JP", + "alpha3": "JPN", + "numeric": "392" + }, + { + "name": "Jersey", + "alpha2": "JE", + "alpha3": "JEY", + "numeric": "832" + }, + { + "name": "Jordan", + "alpha2": "JO", + "alpha3": "JOR", + "numeric": "400" + }, + { + "name": "Kazakhstan", + "alpha2": "KZ", + "alpha3": "KAZ", + "numeric": "398" + }, + { + "name": "Kenya", + "alpha2": "KE", + "alpha3": "KEN", + "numeric": "404" + }, + { + "name": "Kiribati", + "alpha2": "KI", + "alpha3": "KIR", + "numeric": "296" + }, + { + "name": "Korea (the Democratic People's Republic of)", + "alpha2": "KP", + "alpha3": "PRK", + "numeric": "408", + "altName": "Korea (North)", + "shortName": "North Korea" + }, + { + "name": "Korea (the Republic of)", + "alpha2": "KR", + "alpha3": "KOR", + "numeric": "410", + "altName": "Korea (South)", + "shortName": "South Korea" + }, + { + "name": "Kuwait", + "alpha2": "KW", + "alpha3": "KWT", + "numeric": "414" + }, + { + "name": "Kyrgyzstan", + "alpha2": "KG", + "alpha3": "KGZ", + "numeric": "417" + }, + { + "name": "Lao People's Democratic Republic (the)", + "alpha2": "LA", + "alpha3": "LAO", + "numeric": "418", + "altName": "Lao PDR", + "shortName": "Laos" + }, + { + "name": "Latvia", + "alpha2": "LV", + "alpha3": "LVA", + "numeric": "428" + }, + { + "name": "Lebanon", + "alpha2": "LB", + "alpha3": "LBN", + "numeric": "422" + }, + { + "name": "Lesotho", + "alpha2": "LS", + "alpha3": "LSO", + "numeric": "426" + }, + { + "name": "Liberia", + "alpha2": "LR", + "alpha3": "LBR", + "numeric": "430" + }, + { + "name": "Libya", + "alpha2": "LY", + "alpha3": "LBY", + "numeric": "434" + }, + { + "name": "Liechtenstein", + "alpha2": "LI", + "alpha3": "LIE", + "numeric": "438" + }, + { + "name": "Lithuania", + "alpha2": "LT", + "alpha3": "LTU", + "numeric": "440" + }, + { + "name": "Luxembourg", + "alpha2": "LU", + "alpha3": "LUX", + "numeric": "442" + }, + { + "name": "Macao", + "alpha2": "MO", + "alpha3": "MAC", + "numeric": "446", + "altName": "Macao, SAR China", + "shortName": "Macau" + }, + { + "name": "Macedonia (the former Yugoslav Republic of)", + "alpha2": "MK", + "alpha3": "MKD", + "numeric": "807", + "altName": "Macedonia, Republic of", + "shortName": "Macedonia" + }, + { + "name": "Madagascar", + "alpha2": "MG", + "alpha3": "MDG", + "numeric": "450" + }, + { + "name": "Malawi", + "alpha2": "MW", + "alpha3": "MWI", + "numeric": "454" + }, + { + "name": "Malaysia", + "alpha2": "MY", + "alpha3": "MYS", + "numeric": "458" + }, + { + "name": "Maldives", + "alpha2": "MV", + "alpha3": "MDV", + "numeric": "462" + }, + { + "name": "Mali", + "alpha2": "ML", + "alpha3": "MLI", + "numeric": "466" + }, + { + "name": "Malta", + "alpha2": "MT", + "alpha3": "MLT", + "numeric": "470" + }, + { + "name": "Marshall Islands (the)", + "alpha2": "MH", + "alpha3": "MHL", + "numeric": "584", + "altName": "Marshall Islands" + }, + { + "name": "Martinique", + "alpha2": "MQ", + "alpha3": "MTQ", + "numeric": "474" + }, + { + "name": "Mauritania", + "alpha2": "MR", + "alpha3": "MRT", + "numeric": "478" + }, + { + "name": "Mauritius", + "alpha2": "MU", + "alpha3": "MUS", + "numeric": "480" + }, + { + "name": "Mayotte", + "alpha2": "YT", + "alpha3": "MYT", + "numeric": "175" + }, + { + "name": "Mexico", + "alpha2": "MX", + "alpha3": "MEX", + "numeric": "484" + }, + { + "name": "Micronesia (Federated States of)", + "alpha2": "FM", + "alpha3": "FSM", + "numeric": "583", + "altName": "Micronesia, Federated States of", + "shortName": "Micronesia" + }, + { + "name": "Moldova (the Republic of)", + "alpha2": "MD", + "alpha3": "MDA", + "numeric": "498", + "altName": "Moldova" + }, + { + "name": "Monaco", + "alpha2": "MC", + "alpha3": "MCO", + "numeric": "492" + }, + { + "name": "Mongolia", + "alpha2": "MN", + "alpha3": "MNG", + "numeric": "496" + }, + { + "name": "Montenegro", + "alpha2": "ME", + "alpha3": "MNE", + "numeric": "499" + }, + { + "name": "Montserrat", + "alpha2": "MS", + "alpha3": "MSR", + "numeric": "500" + }, + { + "name": "Morocco", + "alpha2": "MA", + "alpha3": "MAR", + "numeric": "504" + }, + { + "name": "Mozambique", + "alpha2": "MZ", + "alpha3": "MOZ", + "numeric": "508" + }, + { + "name": "Myanmar", + "alpha2": "MM", + "alpha3": "MMR", + "numeric": "104" + }, + { + "name": "Namibia", + "alpha2": "NA", + "alpha3": "NAM", + "numeric": "516" + }, + { + "name": "Nauru", + "alpha2": "NR", + "alpha3": "NRU", + "numeric": "520" + }, + { + "name": "Nepal", + "alpha2": "NP", + "alpha3": "NPL", + "numeric": "524" + }, + { + "name": "Netherlands (the)", + "alpha2": "NL", + "alpha3": "NLD", + "numeric": "528", + "altName": "Netherlands" + }, + { + "name": "New Caledonia", + "alpha2": "NC", + "alpha3": "NCL", + "numeric": "540" + }, + { + "name": "New Zealand", + "alpha2": "NZ", + "alpha3": "NZL", + "numeric": "554" + }, + { + "name": "Nicaragua", + "alpha2": "NI", + "alpha3": "NIC", + "numeric": "558" + }, + { + "name": "Niger (the)", + "alpha2": "NE", + "alpha3": "NER", + "numeric": "562", + "altName": "Niger" + }, + { + "name": "Nigeria", + "alpha2": "NG", + "alpha3": "NGA", + "numeric": "566" + }, + { + "name": "Niue", + "alpha2": "NU", + "alpha3": "NIU", + "numeric": "570" + }, + { + "name": "Norfolk Island", + "alpha2": "NF", + "alpha3": "NFK", + "numeric": "574" + }, + { + "name": "Northern Mariana Islands (the)", + "alpha2": "MP", + "alpha3": "MNP", + "numeric": "580", + "altName": "Northern Mariana Islands" + }, + { + "name": "Norway", + "alpha2": "NO", + "alpha3": "NOR", + "numeric": "578" + }, + { + "name": "Oman", + "alpha2": "OM", + "alpha3": "OMN", + "numeric": "512" + }, + { + "name": "Pakistan", + "alpha2": "PK", + "alpha3": "PAK", + "numeric": "586" + }, + { + "name": "Palau", + "alpha2": "PW", + "alpha3": "PLW", + "numeric": "585" + }, + { + "name": "Palestine, State of", + "alpha2": "PS", + "alpha3": "PSE", + "numeric": "275", + "altName": "Palestinian Territory", + "shortName": "Palestine" + }, + { + "name": "Panama", + "alpha2": "PA", + "alpha3": "PAN", + "numeric": "591" + }, + { + "name": "Papua New Guinea", + "alpha2": "PG", + "alpha3": "PNG", + "numeric": "598" + }, + { + "name": "Paraguay", + "alpha2": "PY", + "alpha3": "PRY", + "numeric": "600" + }, + { + "name": "Peru", + "alpha2": "PE", + "alpha3": "PER", + "numeric": "604" + }, + { + "name": "Philippines (the)", + "alpha2": "PH", + "alpha3": "PHL", + "numeric": "608", + "altName": "Philippines" + }, + { + "name": "Pitcairn", + "alpha2": "PN", + "alpha3": "PCN", + "numeric": "612" + }, + { + "name": "Poland", + "alpha2": "PL", + "alpha3": "POL", + "numeric": "616" + }, + { + "name": "Portugal", + "alpha2": "PT", + "alpha3": "PRT", + "numeric": "620" + }, + { + "name": "Puerto Rico", + "alpha2": "PR", + "alpha3": "PRI", + "numeric": "630" + }, + { + "name": "Qatar", + "alpha2": "QA", + "alpha3": "QAT", + "numeric": "634" + }, + { + "name": "Réunion", + "alpha2": "RE", + "alpha3": "REU", + "numeric": "638", + "shortName": "Reunion" + }, + { + "name": "Romania", + "alpha2": "RO", + "alpha3": "ROU", + "numeric": "642" + }, + { + "name": "Russian Federation (the)", + "alpha2": "RU", + "alpha3": "RUS", + "numeric": "643", + "altName": "Russian Federation", + "shortName": "Russia" + }, + { + "name": "Rwanda", + "alpha2": "RW", + "alpha3": "RWA", + "numeric": "646" + }, + { + "name": "Saint Barthélemy", + "alpha2": "BL", + "alpha3": "BLM", + "numeric": "652", + "altName": "Saint-Barthélemy", + "shortName": "Saint Barthelemy" + }, + { + "name": "Saint Helena, Ascension and Tristan da Cunha", + "alpha2": "SH", + "alpha3": "SHN", + "numeric": "654", + "altName": "Saint Helena" + }, + { + "name": "Saint Kitts and Nevis", + "alpha2": "KN", + "alpha3": "KNA", + "numeric": "659" + }, + { + "name": "Saint Lucia", + "alpha2": "LC", + "alpha3": "LCA", + "numeric": "662" + }, + { + "name": "Saint Martin (French part)", + "alpha2": "MF", + "alpha3": "MAF", + "numeric": "663", + "altName": "Saint-Martin (French part)", + "shortName": "Saint Martin" + }, + { + "name": "Saint Pierre and Miquelon", + "alpha2": "PM", + "alpha3": "SPM", + "numeric": "666" + }, + { + "name": "Saint Vincent and the Grenadines", + "alpha2": "VC", + "alpha3": "VCT", + "numeric": "670", + "altName": "Saint Vincent and Grenadines" + }, + { + "name": "Samoa", + "alpha2": "WS", + "alpha3": "WSM", + "numeric": "882" + }, + { + "name": "San Marino", + "alpha2": "SM", + "alpha3": "SMR", + "numeric": "674" + }, + { + "name": "Sao Tome and Principe", + "alpha2": "ST", + "alpha3": "STP", + "numeric": "678" + }, + { + "name": "Saudi Arabia", + "alpha2": "SA", + "alpha3": "SAU", + "numeric": "682" + }, + { + "name": "Senegal", + "alpha2": "SN", + "alpha3": "SEN", + "numeric": "686" + }, + { + "name": "Serbia", + "alpha2": "RS", + "alpha3": "SRB", + "numeric": "688" + }, + { + "name": "Seychelles", + "alpha2": "SC", + "alpha3": "SYC", + "numeric": "690" + }, + { + "name": "Sierra Leone", + "alpha2": "SL", + "alpha3": "SLE", + "numeric": "694" + }, + { + "name": "Singapore", + "alpha2": "SG", + "alpha3": "SGP", + "numeric": "702" + }, + { + "name": "Sint Maarten (Dutch part)", + "alpha2": "SX", + "alpha3": "SXM", + "numeric": "534", + "shortName": "Sint Maarten" + }, + { + "name": "Slovakia", + "alpha2": "SK", + "alpha3": "SVK", + "numeric": "703" + }, + { + "name": "Slovenia", + "alpha2": "SI", + "alpha3": "SVN", + "numeric": "705" + }, + { + "name": "Solomon Islands", + "alpha2": "SB", + "alpha3": "SLB", + "numeric": "090" + }, + { + "name": "Somalia", + "alpha2": "SO", + "alpha3": "SOM", + "numeric": "706" + }, + { + "name": "South Africa", + "alpha2": "ZA", + "alpha3": "ZAF", + "numeric": "710" + }, + { + "name": "South Georgia and the South Sandwich Islands", + "alpha2": "GS", + "alpha3": "SGS", + "numeric": "239" + }, + { + "name": "South Sudan", + "alpha2": "SS", + "alpha3": "SSD", + "numeric": "728" + }, + { + "name": "Spain", + "alpha2": "ES", + "alpha3": "ESP", + "numeric": "724" + }, + { + "name": "Sri Lanka", + "alpha2": "LK", + "alpha3": "LKA", + "numeric": "144" + }, + { + "name": "Sudan (the)", + "alpha2": "SD", + "alpha3": "SDN", + "numeric": "729", + "altName": "Sudan" + }, + { + "name": "Suriname", + "alpha2": "SR", + "alpha3": "SUR", + "numeric": "740" + }, + { + "name": "Svalbard and Jan Mayen", + "alpha2": "SJ", + "alpha3": "SJM", + "numeric": "744", + "altName": "Svalbard and Jan Mayen Islands" + }, + { + "name": "Swaziland", + "alpha2": "SZ", + "alpha3": "SWZ", + "numeric": "748" + }, + { + "name": "Sweden", + "alpha2": "SE", + "alpha3": "SWE", + "numeric": "752" + }, + { + "name": "Switzerland", + "alpha2": "CH", + "alpha3": "CHE", + "numeric": "756" + }, + { + "name": "Syrian Arab Republic", + "alpha2": "SY", + "alpha3": "SYR", + "numeric": "760", + "altName": "Syrian Arab Republic (Syria)", + "shortName": "Syria" + }, + { + "name": "Taiwan (Province of China)", + "alpha2": "TW", + "alpha3": "TWN", + "numeric": "158", + "altName": "Taiwan, Republic of China", + "shortName": "Taiwan" + }, + { + "name": "Tajikistan", + "alpha2": "TJ", + "alpha3": "TJK", + "numeric": "762" + }, + { + "name": "Tanzania, United Republic of", + "alpha2": "TZ", + "alpha3": "TZA", + "numeric": "834", + "shortName": "Tanzania" + }, + { + "name": "Thailand", + "alpha2": "TH", + "alpha3": "THA", + "numeric": "764" + }, + { + "name": "Timor-Leste", + "alpha2": "TL", + "alpha3": "TLS", + "numeric": "626", + "shortName": "East Timor" + }, + { + "name": "Togo", + "alpha2": "TG", + "alpha3": "TGO", + "numeric": "768" + }, + { + "name": "Tokelau", + "alpha2": "TK", + "alpha3": "TKL", + "numeric": "772" + }, + { + "name": "Tonga", + "alpha2": "TO", + "alpha3": "TON", + "numeric": "776" + }, + { + "name": "Trinidad and Tobago", + "alpha2": "TT", + "alpha3": "TTO", + "numeric": "780" + }, + { + "name": "Tunisia", + "alpha2": "TN", + "alpha3": "TUN", + "numeric": "788" + }, + { + "name": "Turkey", + "alpha2": "TR", + "alpha3": "TUR", + "numeric": "792" + }, + { + "name": "Turkmenistan", + "alpha2": "TM", + "alpha3": "TKM", + "numeric": "795" + }, + { + "name": "Turks and Caicos Islands (the)", + "alpha2": "TC", + "alpha3": "TCA", + "numeric": "796", + "altName": "Turks and Caicos Islands" + }, + { + "name": "Tuvalu", + "alpha2": "TV", + "alpha3": "TUV", + "numeric": "798" + }, + { + "name": "Uganda", + "alpha2": "UG", + "alpha3": "UGA", + "numeric": "800" + }, + { + "name": "Ukraine", + "alpha2": "UA", + "alpha3": "UKR", + "numeric": "804" + }, + { + "name": "United Arab Emirates (the)", + "alpha2": "AE", + "alpha3": "ARE", + "numeric": "784", + "altName": "United Arab Emirates" + }, + { + "name": "United Kingdom of Great Britain and Northern Ireland (the)", + "alpha2": "GB", + "alpha3": "GBR", + "numeric": "826", + "altName": "United Kingdom" + }, + { + "name": "United States Minor Outlying Islands (the)", + "alpha2": "UM", + "alpha3": "UMI", + "numeric": "581", + "altName": "US Minor Outlying Islands" + }, + { + "name": "United States of America (the)", + "alpha2": "US", + "alpha3": "USA", + "numeric": "840", + "altName": "United States of America", + "shortName": "United States" + }, + { + "name": "Uruguay", + "alpha2": "UY", + "alpha3": "URY", + "numeric": "858" + }, + { + "name": "Uzbekistan", + "alpha2": "UZ", + "alpha3": "UZB", + "numeric": "860" + }, + { + "name": "Vanuatu", + "alpha2": "VU", + "alpha3": "VUT", + "numeric": "548" + }, + { + "name": "Venezuela (Bolivarian Republic of)", + "alpha2": "VE", + "alpha3": "VEN", + "numeric": "862", + "altName": "Venezuela (Bolivarian Republic)", + "shortName": "Venezuela" + }, + { + "name": "Viet Nam", + "alpha2": "VN", + "alpha3": "VNM", + "numeric": "704", + "shortName": "Vietnam" + }, + { + "name": "Virgin Islands (British)", + "alpha2": "VG", + "alpha3": "VGB", + "numeric": "092", + "altName": "British Virgin Islands" + }, + { + "name": "Virgin Islands (U.S.)", + "alpha2": "VI", + "alpha3": "VIR", + "numeric": "850", + "altName": "Virgin Islands, US", + "shortName": "U.S. Virgin Islands" + }, + { + "name": "Wallis and Futuna", + "alpha2": "WF", + "alpha3": "WLF", + "numeric": "876", + "altName": "Wallis and Futuna Islands" + }, + { + "name": "Western Sahara*", + "alpha2": "EH", + "alpha3": "ESH", + "numeric": "732", + "altName": "Western Sahara" + }, + { + "name": "Yemen", + "alpha2": "YE", + "alpha3": "YEM", + "numeric": "887" + }, + { + "name": "Zambia", + "alpha2": "ZM", + "alpha3": "ZMB", + "numeric": "894" + }, + { + "name": "Zimbabwe", + "alpha2": "ZW", + "alpha3": "ZWE", + "numeric": "716" + } + ]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts new file mode 100644 index 0000000000..9f909a0b6b --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts @@ -0,0 +1,811 @@ +import { INodeProperties } from "n8n-workflow"; + +export const customerOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new customer', + }, + { + name: 'Properties', + value: 'properties', + description: 'Get customer property definitions', + }, + { + name: 'Get', + value: 'get', + description: 'Get a customer', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all customers', + }, + { + name: 'Update', + value: 'update', + description: 'Update a customer', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const customerFields = [ +/* -------------------------------------------------------------------------- */ +/* customer:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + description: 'By default the response only contain the ID to resource
. If this option gets activated it
will resolve the data automatically.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + options: [ + { + displayName: 'Age', + name: 'age', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: `Customer’s age`, + }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: `First name of the customer. When defined it must be between 1 and 40 characters.`, + }, + { + displayName: 'Gender', + name: 'gender', + type: 'options', + options: [ + { + name: 'Female', + value: 'female', + }, + { + name: 'Male', + value: 'male', + }, + { + name: 'Unknown', + value: 'unknown', + }, + ], + default: '', + description: 'Gender of this customer.', + }, + { + displayName: 'Job Title', + name: 'jobTitle', + type: 'string', + default: '', + description: 'Job title. Max length 60 characters.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name of the customer', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the customer.', + }, + { + displayName: 'Organization', + name: 'organization', + type: 'string', + default: '', + description: 'Organization', + }, + { + displayName: 'Photo Url', + name: 'photoUrl', + type: 'string', + default: '', + description: 'URL of the customer’s photo', + }, + ] + }, + { + displayName: 'Address', + name: 'addressUi', + placeholder: 'Add Address', + type: 'fixedCollection', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Address', + name: 'addressValue', + values: [ + { + displayName: 'Line 1', + name: 'line1', + type: 'string', + default: '', + description: 'line1', + }, + { + displayName: 'Line 2', + name: 'line2', + type: 'string', + default: '', + description: 'line2', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State', + }, + { + displayName: 'Country', + name: 'country', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCountriesCodes', + }, + default: '', + description: 'Country', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + description: 'Postal code', + }, + ], + }, + ], + }, + { + displayName: 'Chat Handles', + name: 'chatsUi', + placeholder: 'Add Chat Handle', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Chat Handle', + name: 'chatsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'aim', + value: 'aim', + }, + { + name: 'gtalk', + value: 'gtalk', + }, + { + name: 'icq', + value: 'icq', + }, + { + name: 'msn', + value: 'msn', + }, + { + name: 'other', + value: 'other', + }, + { + name: 'qq', + value: 'qq', + }, + { + name: 'skype', + value: 'skype', + }, + { + name: 'xmpp', + value: 'xmpp', + }, + { + name: 'yahoo', + value: 'yahoo', + }, + ], + description: 'Chat type', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Chat handle', + }, + ], + }, + ], + }, + { + displayName: 'Emails', + name: 'emailsUi', + placeholder: 'Add Email', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'emailsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Home', + value: 'home', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Work', + value: 'work', + }, + ], + description: 'Location for this email address', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Email', + }, + ], + }, + ], + }, + { + displayName: 'Phones', + name: 'phonesUi', + placeholder: 'Add Phone', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'phonesValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Fax', + value: 'fax', + }, + { + name: 'Home', + value: 'home', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Pager', + value: 'pager', + }, + { + name: 'Work', + value: 'work', + }, + ], + description: 'Location for this phone', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Phone', + }, + ], + }, + ], + }, + { + displayName: 'Social Profiles', + name: 'socialProfilesUi', + placeholder: 'Add Social Profile', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Social Profile', + name: 'socialProfilesValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'About Me', + value: 'aboutMe', + }, + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Forsquare', + value: 'forsquare', + }, + { + name: 'Google', + value: 'google', + }, + { + name: 'Google Plus', + value: 'googleplus', + }, + { + name: 'Linkedin', + value: 'linkedin', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Quora', + value: 'quora', + }, + { + name: 'Tungleme', + value: 'tungleme', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'Youtube', + value: 'youtube', + }, + ], + description: 'Type of social profile', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Social Profile handle (url for example)', + }, + ], + }, + ], + }, + { + displayName: 'Websites', + name: 'websitesUi', + placeholder: 'Add Website', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Website', + name: 'websitesValues', + values: [ + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* customer:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters customers from a specific mailbox', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: 'Filters customers by first name', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Filters customers by last name', + }, + { + displayName: 'Modified Since', + name: 'modifiedSince', + type: 'dateTime', + default: '', + description: 'Returns only customers that were modified after this date', + }, + { + displayName: 'Sort Field', + name: 'sortField', + type: 'options', + options: [ + { + name: 'Score', + value: 'score', + }, + { + name: 'First Name', + value: 'firstName', + }, + { + name: 'Last Name', + value: 'lastName', + }, + { + name: 'Modified At', + value: 'modifiedAt', + }, + ], + default: 'score', + description: 'Sorts the result by specified field', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'Desc', + value: 'desc', + }, + ], + default: 'desc', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Advanced search Examples' + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* customer:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'get', + ], + }, + }, + description: 'Customer ID', + }, +/* -------------------------------------------------------------------------- */ +/* customer:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'customer', + ], + }, + }, + description: 'Customer ID', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'customer', + ], + }, + }, + options: [ + { + displayName: 'Age', + name: 'age', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: `Customer’s age`, + }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: `First name of the customer. When defined it must be between 1 and 40 characters.`, + }, + { + displayName: 'Gender', + name: 'gender', + type: 'options', + options: [ + { + name: 'Female', + value: 'female', + }, + { + name: 'Male', + value: 'male', + }, + { + name: 'Unknown', + value: 'unknown', + }, + ], + default: '', + description: 'Gender of this customer.', + }, + { + displayName: 'Job Title', + name: 'jobTitle', + type: 'string', + default: '', + description: 'Job title. Max length 60 characters.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name of the customer', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the customer.', + }, + { + displayName: 'Organization', + name: 'organization', + type: 'string', + default: '', + description: 'Organization', + }, + { + displayName: 'Photo Url', + name: 'photoUrl', + type: 'string', + default: '', + description: 'URL of the customer’s photo', + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts new file mode 100644 index 0000000000..569a92c9aa --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts @@ -0,0 +1,20 @@ +import { IDataObject } from "n8n-workflow"; + +export interface ICustomer { + address?: IDataObject; + age?: string; + background?: string; + chats?: IDataObject[]; + emails?: IDataObject[]; + firstName?: string; + gender?: string; + jobTitle?: string; + lastName?: string; + location?: string; + organization?: string; + phones?: IDataObject[]; + photoUrl?: string; + properties?: IDataObject; + socialProfiles?: IDataObject[]; + websites?: IDataObject[]; +} diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts new file mode 100644 index 0000000000..c7cda4eac6 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -0,0 +1,69 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, + IHookFunctions, +} from 'n8n-core'; +import { + IDataObject, +} from 'n8n-workflow'; + +import { + get, +} from 'lodash'; + +export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://api.helpscout.net${resource}`, + json: true + }; + try { + if (Object.keys(option).length !== 0) { + options = Object.assign({}, options, option); + } + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'helpScoutOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body + && error.response.body._embedded + && error.response.body._embedded.errors) { + // Try to return the error prettier + //@ts-ignore + throw new Error(`HelpScout error response [${error.statusCode}]: ${error.response.body.message} - ${error.response.body._embedded.errors.map(error => { + return `${error.path} ${error.message}`; + }).join('-')}`); + } + throw error; + } +} + +export async function helpscoutApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.size = 50; + let uri; + + do { + responseData = await helpscoutApiRequest.call(this, method, endpoint, body, query, uri); + uri = get(responseData, '_links.next.href'); + returnData.push.apply(returnData, get(responseData, propertyName)); + } while ( + responseData['_links'] !== undefined && + responseData['_links'].next !== undefined && + responseData['_links'].next.href !== undefined + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts new file mode 100644 index 0000000000..27534b2426 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -0,0 +1,410 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, + IBinaryKeyData, +} from 'n8n-workflow'; + +import { + helpscoutApiRequest, + helpscoutApiRequestAllItems, +} from './GenericFunctions'; + +import { + conversationOperations, + conversationFields, +} from './ConversationDescription'; + +import { + customerOperations, + customerFields, +} from './CustomerDescription'; + +import { + mailboxOperations, + mailboxFields, +} from './MailboxDescription'; + +import { + threadOperations, + threadFields, +} from './ThreadDescription'; + +import { + ICustomer, +} from './CustomerInterface'; + +import { + IConversation, + } from './ConversationInterface'; + + import { + IThread, + IAttachment, + } from './ThreadInterface'; + + import { + countriesCodes +} from './CountriesCodes'; + +export class HelpScout implements INodeType { + description: INodeTypeDescription = { + displayName: 'HelpScout', + name: 'helpScout', + icon: 'file:helpScout.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Help Scout API.', + defaults: { + name: 'HelpScout', + color: '#1392ee', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'helpScoutOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Conversation', + value: 'conversation', + }, + { + name: 'Customer', + value: 'customer', + }, + { + name: 'Mailbox', + value: 'mailbox', + }, + { + name: 'Thread', + value: 'thread', + }, + ], + default: 'conversation', + description: 'The resource to operate on.', + }, + ...conversationOperations, + ...conversationFields, + ...customerOperations, + ...customerFields, + ...mailboxOperations, + ...mailboxFields, + ...threadOperations, + ...threadFields, + ], + }; + + methods = { + loadOptions: { + // Get all the countries codes to display them to user so that he can + // select them easily + async getCountriesCodes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + for (const countryCode of countriesCodes) { + const countryCodeName = `${countryCode.name} - ${countryCode.alpha2}`; + const countryCodeId = countryCode.alpha2; + returnData.push({ + name: countryCodeName, + value: countryCodeId, + }); + } + return returnData; + }, + // Get all the tags to display them to user so that he can + // select them easily + async getTags(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const tags = await helpscoutApiRequestAllItems.call(this, '_embedded.tags', 'GET', '/v2/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 mailboxes to display them to user so that he can + // select them easily + async getMailboxes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const mailboxes = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes'); + for (const mailbox of mailboxes) { + const mailboxName = mailbox.name; + const mailboxId = mailbox.id; + returnData.push({ + name: mailboxName, + value: mailboxId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + 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 === 'conversation') { + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/create + if (operation === 'create') { + const mailboxId = this.getNodeParameter('mailboxId', i) as number; + const status = this.getNodeParameter('status', i) as string; + const subject = this.getNodeParameter('subject', i) as string; + const type = this.getNodeParameter('type', i) as string; + const resolveData = this.getNodeParameter('resolveData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const threads = (this.getNodeParameter('threadsUi', i) as IDataObject).threadsValues as IDataObject[]; + const body: IConversation = { + mailboxId, + status, + subject, + type, + }; + Object.assign(body, additionalFields); + if (additionalFields.customerId) { + body.customer = { + id: additionalFields.customerId, + }; + //@ts-ignore + delete body.customerId; + } + if (additionalFields.customerEmail) { + body.customer = { + email: additionalFields.customerEmail, + }; + //@ts-ignore + delete body.customerEmail; + } + if (body.customer === undefined) { + throw new Error('Either customer email or customer ID must be set'); + } + if (threads) { + for (let i = 0; i < threads.length; i++) { + if (threads[i].type === '' || threads[i].text === '') { + throw new Error('Chat Threads cannot be empty'); + } + if (threads[i].type !== 'note') { + threads[i].customer = body.customer; + } + } + body.threads = threads; + } + responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/conversations', body, qs, undefined, { resolveWithFullResponse: true }); + const id = responseData.headers['resource-id']; + const uri = responseData.headers.location; + if (resolveData) { + responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri); + } else { + responseData = { + id, + uri, + }; + } + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/delete + if (operation === 'delete') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'DELETE', `/v2/conversations/${conversationId}`); + responseData = { success: true }; + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/get + if (operation === 'get') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/conversations/${conversationId}`); + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/list + if (operation === 'getAll') { + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(qs, options); + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + } + } + if (resource === 'customer') { + //https://developer.helpscout.com/mailbox-api/endpoints/customers/create + if (operation === 'create') { + const resolveData = this.getNodeParameter('resolveData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const chats = (this.getNodeParameter('chatsUi', i) as IDataObject).chatsValues as IDataObject[]; + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValue as IDataObject; + const emails = (this.getNodeParameter('emailsUi', i) as IDataObject).emailsValues as IDataObject[]; + const phones = (this.getNodeParameter('phonesUi', i) as IDataObject).phonesValues as IDataObject[]; + const socialProfiles = (this.getNodeParameter('socialProfilesUi', i) as IDataObject).socialProfilesValues as IDataObject[]; + const websites = (this.getNodeParameter('websitesUi', i) as IDataObject).websitesValues as IDataObject[]; + let body: ICustomer = {}; + body = Object.assign({}, additionalFields); + if (body.age) { + body.age = body.age.toString(); + } + if (chats) { + body.chats = chats; + } + if (address) { + body.address = address; + body.address.lines = [address.line1, address.line2]; + } + if (emails) { + body.emails = emails; + } + if (phones) { + body.phones = phones; + } + if (socialProfiles) { + body.socialProfiles = socialProfiles; + } + if (websites) { + body.websites = websites; + } + if (Object.keys(body).length === 0) { + throw new Error('You have to set at least one field'); + } + responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/customers', body, qs, undefined, { resolveWithFullResponse: true }); + const id = responseData.headers['resource-id']; + const uri = responseData.headers.location; + if (resolveData) { + responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri); + } else { + responseData = { + id, + uri, + }; + } + } + //https://developer.helpscout.com/mailbox-api/endpoints/customer_properties/list + if (operation === 'properties') { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customer-properties', 'GET', '/v2/customer-properties', {}, qs); + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/get + if (operation === 'get') { + const customerId = this.getNodeParameter('customerId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/customers/${customerId}`); + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/list + if (operation === 'getAll') { + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(qs, options); + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/overwrite/ + if (operation === 'update') { + const customerId = this.getNodeParameter('customerId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + let body: ICustomer = {}; + body = Object.assign({}, updateFields); + if (body.age) { + body.age = body.age.toString(); + } + if (Object.keys(body).length === 0) { + throw new Error('You have to set at least one field'); + } + responseData = await helpscoutApiRequest.call(this, 'PUT', `/v2/customers/${customerId}`, body, qs, undefined, { resolveWithFullResponse: true }); + responseData = { success: true }; + } + } + if (resource === 'mailbox') { + //https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/get + if (operation === 'get') { + const mailboxId = this.getNodeParameter('mailboxId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/mailboxes/${mailboxId}`, {}, qs); + } + //https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/list + if (operation === 'getAll') { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + } + } + if (resource === 'thread') { + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/chat + if (operation === 'create') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + const type = this.getNodeParameter('type', i) as string; + const text = this.getNodeParameter('text', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const attachments = this.getNodeParameter('attachmentsUi', i) as IDataObject; + const body: IThread = { + text, + attachments: [], + }; + Object.assign(body, additionalFields); + if (additionalFields.customerId) { + body.customer = { + id: additionalFields.customerId, + }; + //@ts-ignore + delete body.customerId; + } + if (additionalFields.customerEmail) { + body.customer = { + email: additionalFields.customerEmail, + }; + //@ts-ignore + delete body.customerEmail; + } + if (body.customer === undefined) { + throw new Error('Either customer email or customer ID must be set'); + } + if (attachments) { + if (attachments.attachmentsValues + && (attachments.attachmentsValues as IDataObject[]).length !== 0) { + body.attachments?.push.apply(body.attachments, attachments.attachmentsValues as IAttachment[]); + } + if (attachments.attachmentsBinary + && (attachments.attachmentsBinary as IDataObject[]).length !== 0 + && items[i].binary) { + const mapFunction = (value: IDataObject): IAttachment => { + const binaryProperty = (items[i].binary as IBinaryKeyData)[value.property as string]; + if (binaryProperty) { + return { + fileName: binaryProperty.fileName || 'unknown', + data: binaryProperty.data, + mimeType: binaryProperty.mimeType, + }; + } else { + throw new Error(`Binary property ${value.property} does not exist on input`); + } + }; + body.attachments?.push.apply(body.attachments, (attachments.attachmentsBinary as IDataObject[]).map(mapFunction) as IAttachment[]); + } + } + responseData = await helpscoutApiRequest.call(this, 'POST', `/v2/conversations/${conversationId}/chats`, body); + responseData = { success: true }; + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list + if (operation === 'getAll') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts new file mode 100644 index 0000000000..ecb7e2a12a --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts @@ -0,0 +1,202 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + INodeTypeDescription, + INodeType, + IWebhookResponseData, + IDataObject, +} from 'n8n-workflow'; + +import { + helpscoutApiRequest, + helpscoutApiRequestAllItems, +} from './GenericFunctions'; + +import { createHmac } from 'crypto'; + +export class HelpScoutTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'HelpScout Trigger', + name: 'helpScoutTrigger', + icon: 'file:helpScout.png', + group: ['trigger'], + version: 1, + description: 'Starts the workflow when HelpScout events occure.', + defaults: { + name: 'HelpScout Trigger', + color: '#1392ee', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'helpScoutOAuth2Api', + required: true, + }, + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + options: [ + { + name: 'convo.agent.reply.created', + value: 'convo.agent.reply.created', + }, + { + name: 'convo.assigned', + value: 'convo.assigned', + }, + { + name: 'convo.created', + value: 'convo.created', + }, + { + name: 'convo.customer.reply.created', + value: 'convo.customer.reply.created', + }, + { + name: 'convo.deleted', + value: 'convo.deleted', + }, + { + name: 'convo.merged', + value: 'convo.merged', + }, + { + name: 'convo.moved', + value: 'convo.moved', + }, + { + name: 'convo.note.created', + value: 'convo.note.created', + }, + { + name: 'convo.status', + value: 'convo.status', + }, + { + name: 'convo.tags', + value: 'convo.tags', + }, + { + name: 'customer.created', + value: 'customer.created', + }, + { + name: 'satisfaction.ratings', + value: 'satisfaction.ratings', + }, + ], + default: [], + required: true, + }, + ], + + }; + + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const events = this.getNodeParameter('events') as string; + + // Check all the webhooks which exist already if it is identical to the + // one that is supposed to get created. + const endpoint = '/v2/webhooks'; + const data = await helpscoutApiRequestAllItems.call(this, '_embedded.webhooks', 'GET', endpoint, {}); + + for (const webhook of data) { + if (webhook.url === webhookUrl) { + for (const event of events) { + if (!webhook.events.includes(event) + && webhook.state === 'enabled') { + return false; + } + } + } + // Set webhook-id to be sure that it can be deleted + webhookData.webhookId = webhook.id as string; + return true; + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const webhookUrl = this.getNodeWebhookUrl('default'); + const events = this.getNodeParameter('events') as string; + + const endpoint = '/v2/webhooks'; + + const body = { + url: webhookUrl, + events, + secret: Math.random().toString(36).substring(2, 15), + }; + + const responseData = await helpscoutApiRequest.call(this, 'POST', endpoint, body, {}, undefined, { resolveWithFullResponse: true }); + + if (responseData.headers['resource-id'] === undefined) { + // Required data is missing so was not successful + return false; + } + + webhookData.webhookId = responseData.headers['resource-id'] as string; + webhookData.secret = body.secret; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId !== undefined) { + + const endpoint = `/v2/webhooks/${webhookData.webhookId}`; + try { + await helpscoutApiRequest.call(this, 'DELETE', endpoint); + } catch (e) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + delete webhookData.secret; + } + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + const bodyData = this.getBodyData(); + const headerData = this.getHeaderData() as IDataObject; + const webhookData = this.getWorkflowStaticData('node'); + if (headerData['x-helpscout-signature'] === undefined) { + return {}; + } + //@ts-ignore + const computedSignature = createHmac('sha1', webhookData.secret as string).update(req.rawBody).digest('base64'); + if (headerData['x-helpscout-signature'] !== computedSignature) { + return {}; + } + return { + workflowData: [ + this.helpers.returnJsonArray(bodyData), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts new file mode 100644 index 0000000000..923b01677f --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts @@ -0,0 +1,54 @@ +import { INodeProperties } from "n8n-workflow"; + +export const mailboxOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'mailbox', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get data of a mailbox', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all mailboxes', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const mailboxFields = [ + +/* -------------------------------------------------------------------------- */ +/* mailbox:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Mailbox ID', + name: 'mailboxId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'mailbox', + ], + operation: [ + 'get', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts new file mode 100644 index 0000000000..230b99ce56 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts @@ -0,0 +1,257 @@ +import { INodeProperties } from "n8n-workflow"; + +export const threadOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'thread', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new chat thread', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all chat threads', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const threadFields = [ +/* -------------------------------------------------------------------------- */ +/* thread:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + description: 'conversation ID', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Customer', + value: 'customer' + }, + { + name: 'Note', + value: 'note' + }, + { + name: 'Phone', + value: 'phone' + }, + { + name: 'Reply', + value: 'reply' + }, + ], + default: '', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The chat text', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'thread', + ], + }, + }, + options: [ + { + displayName: 'Customer Email', + name: 'customerEmail', + type: 'string', + default: '', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'number', + default: 0, + }, + { + displayName: 'Draft', + name: 'draft', + type: 'boolean', + default: false, + displayOptions: { + show: { + '/type': [ + 'note', + ], + }, + }, + description: 'If set to true, a draft reply is created', + }, + { + displayName: 'Imported', + name: 'imported', + type: 'boolean', + default: false, + description: 'When imported is set to true, no outgoing emails or notifications will be generated.', + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + }, + ] + }, + { + displayName: 'Attachments', + name: 'attachmentsUi', + placeholder: 'Add Attachments', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'thread', + ], + }, + }, + options: [ + { + name: 'attachmentsValues', + displayName: 'Attachments Values', + values: [ + { + displayName: 'FileName', + name: 'fileName', + type: 'string', + default: '', + description: 'Attachment’s file name', + }, + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'string', + default: '', + description: 'Attachment’s mime type', + }, + { + displayName: 'Data', + name: 'data', + type: 'string', + default: '', + placeholder: 'ZXhhbXBsZSBmaWxl', + description: 'Base64-encoded stream of data.', + }, + ], + }, + { + name: 'attachmentsBinary', + displayName: 'Attachments Binary', + values: [ + { + displayName: 'Property', + name: 'property', + type: 'string', + default: 'data', + description: 'Name of the binary properties which contain data which should be added to email as attachment', + }, + ], + }, + ], + default: '', + description: 'Array of supported attachments to add to the message.', + }, +/* -------------------------------------------------------------------------- */ +/* thread:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'getAll', + ], + }, + }, + description: 'conversation ID', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts new file mode 100644 index 0000000000..b6cbbda213 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts @@ -0,0 +1,15 @@ +import { IDataObject } from "n8n-workflow"; + +export interface IAttachment { + fileName?: string; + mimeType?: string; + data?: string; +} + +export interface IThread { + createdAt?: string; + customer?: IDataObject; + imported?: boolean; + text?: string; + attachments?: IAttachment[]; +} diff --git a/packages/nodes-base/nodes/HelpScout/helpScout.png b/packages/nodes-base/nodes/HelpScout/helpScout.png new file mode 100644 index 0000000000000000000000000000000000000000..a2be95ba7f734e4f120cc2b6624a660b4ddec7b2 GIT binary patch literal 4862 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 Vf)A~m9w$G3I@b&|pQ+p4{~u?LqWk~= literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 746f91f723..282af6371b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,7 +42,8 @@ "dist/credentials/GithubApi.credentials.js", "dist/credentials/GithubOAuth2Api.credentials.js", "dist/credentials/GitlabApi.credentials.js", - "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/HelpScoutOAuth2Api.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", @@ -118,7 +119,9 @@ "dist/nodes/Gitlab/GitlabTrigger.node.js", "dist/nodes/Google/GoogleDrive.node.js", "dist/nodes/Google/GoogleSheets.node.js", - "dist/nodes/GraphQL/GraphQL.node.js", + "dist/nodes/GraphQL/GraphQL.node.js", + "dist/nodes/HelpScout/HelpScout.node.js", + "dist/nodes/HelpScout/HelpScoutTrigger.node.js", "dist/nodes/HtmlExtract/HtmlExtract.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", From 8fcd8839dd6cc8d3bc890ed620591b7b19806446 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 16 Mar 2020 22:07:15 -0400 Subject: [PATCH 05/20] :zap: small fix --- packages/nodes-base/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 207acd0351..20e30fe8c1 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,9 +42,9 @@ "dist/credentials/GithubApi.credentials.js", "dist/credentials/GithubOAuth2Api.credentials.js", "dist/credentials/GitlabApi.credentials.js", - "dist/credentials/GoogleApi.credentials.js", - "dist/credentials/HelpScoutOAuth2Api.credentials.js", - "dist/credentials/GoogleOAuth2Api.credentials.js", + "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/GoogleOAuth2Api.credentials.js", + "dist/credentials/HelpScoutOAuth2Api.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", From 79ba4dd43d5d992ec966ffaeda4a98913868a435 Mon Sep 17 00:00:00 2001 From: ricardo Date: Tue, 17 Mar 2020 14:39:20 -0400 Subject: [PATCH 06/20] :zap: small improvements --- .../nodes/Microsoft/MicrosoftExcel.node.ts | 9 +++++ .../nodes/Microsoft/TableDescription.ts | 40 +++++++++++++++++++ .../nodes/Microsoft/WorksheetDescription.ts | 20 ++++++++++ 3 files changed, 69 insertions(+) diff --git a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts index c62730449d..fd3de90659 100644 --- a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts +++ b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts @@ -206,6 +206,9 @@ export class MicrosoftExcel implements INodeType { if (!rawData) { //@ts-ignore responseData = responseData.map(column => ({ name: column.name })); + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + responseData = { [dataProperty] : responseData }; } } } @@ -242,6 +245,9 @@ export class MicrosoftExcel implements INodeType { result.push({ ...object }); } responseData = result; + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + responseData = { [dataProperty] : responseData }; } } } @@ -321,6 +327,9 @@ export class MicrosoftExcel implements INodeType { result.push({ ...object }); } responseData = result; + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + responseData = { [dataProperty] : responseData }; } } } diff --git a/packages/nodes-base/nodes/Microsoft/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/TableDescription.ts index 6dc6de780b..8c491a16e1 100644 --- a/packages/nodes-base/nodes/Microsoft/TableDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/TableDescription.ts @@ -260,6 +260,26 @@ export const tableFields = [ default: false, description: 'If the data should be returned RAW instead of parsed into keys according to their header.', }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getRows' + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, { displayName: 'Filters', name: 'filters', @@ -415,6 +435,26 @@ export const tableFields = [ default: false, description: 'If the data should be returned RAW instead of parsed into keys according to their header.', }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getColumns' + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, { displayName: 'Filters', name: 'filters', diff --git a/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts index 204e5f0e41..f50fbb3ece 100644 --- a/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts @@ -201,6 +201,26 @@ export const worksheetFields = [ default: false, description: 'If the data should be returned RAW instead of parsed into keys according to their header.', }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getContent' + ], + resource: [ + 'worksheet', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, { displayName: 'Data Start Row', name: 'dataStartRow', From b60c9be282c8ea603acb8e63da961c38e8bdb510 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 22 Mar 2020 13:03:56 -0400 Subject: [PATCH 07/20] :zap: added folder and changed name to credentials --- ...ls.ts => MicrosoftExcelOAuth2Api.credentials.ts} | 4 ++-- .../nodes/Microsoft/{ => Excel}/GenericFunctions.ts | 2 +- .../Microsoft/{ => Excel}/MicrosoftExcel.node.ts | 2 +- .../nodes/Microsoft/{ => Excel}/TableDescription.ts | 0 .../Microsoft/{ => Excel}/WorkbookDescription.ts | 0 .../Microsoft/{ => Excel}/WorksheetDescription.ts | 0 .../nodes/Microsoft/{ => Excel}/excel.png | Bin packages/nodes-base/package.json | 4 ++-- 8 files changed, 6 insertions(+), 6 deletions(-) rename packages/nodes-base/credentials/{MicrosoftOAuth2Api.credentials.ts => MicrosoftExcelOAuth2Api.credentials.ts} (91%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/GenericFunctions.ts (96%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/MicrosoftExcel.node.ts (99%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/TableDescription.ts (100%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/WorkbookDescription.ts (100%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/WorksheetDescription.ts (100%) rename packages/nodes-base/nodes/Microsoft/{ => Excel}/excel.png (100%) diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts similarity index 91% rename from packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts rename to packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts index 2519ba583b..d6e8e0f452 100644 --- a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts @@ -3,8 +3,8 @@ import { NodePropertyTypes, } from 'n8n-workflow'; -export class MicrosoftOAuth2Api implements ICredentialType { - name = 'microsoftOAuth2Api'; +export class MicrosoftExcelOAuth2Api implements ICredentialType { + name = 'microsoftExcelOAuth2Api'; extends = [ 'oAuth2Api', ]; diff --git a/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts similarity index 96% rename from packages/nodes-base/nodes/Microsoft/GenericFunctions.ts rename to packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts index b4f68be343..33090090b3 100644 --- a/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts @@ -24,7 +24,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing options.headers = Object.assign({}, options.headers, headers); } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'microsoftOAuth2Api', options); + return await this.helpers.requestOAuth.call(this, 'microsoftExcelOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts similarity index 99% rename from packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts rename to packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts index fd3de90659..d7c372c2db 100644 --- a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts @@ -49,7 +49,7 @@ export class MicrosoftExcel implements INodeType { outputs: ['main'], credentials: [ { - name: 'microsoftOAuth2Api', + name: 'microsoftExcelOAuth2Api', required: true, }, ], diff --git a/packages/nodes-base/nodes/Microsoft/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts similarity index 100% rename from packages/nodes-base/nodes/Microsoft/TableDescription.ts rename to packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts diff --git a/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts similarity index 100% rename from packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts rename to packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts diff --git a/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts similarity index 100% rename from packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts rename to packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts diff --git a/packages/nodes-base/nodes/Microsoft/excel.png b/packages/nodes-base/nodes/Microsoft/Excel/excel.png similarity index 100% rename from packages/nodes-base/nodes/Microsoft/excel.png rename to packages/nodes-base/nodes/Microsoft/Excel/excel.png diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3bd0b4a6fd..b8ef81cded 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -55,7 +55,7 @@ "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", - "dist/credentials/MicrosoftOAuth2Api.credentials.js", + "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -135,7 +135,7 @@ "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", - "dist/nodes/Microsoft/MicrosoftExcel.node.js", + "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js", From eaa84827c786fb17ce6a1e63dc8e1c427e33c08b Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 22 Mar 2020 15:14:45 -0400 Subject: [PATCH 08/20] :zap: Small improvements --- .../MicrosoftExcelOAuth2Api.credentials.ts | 26 +------------ .../MicrosoftOAuth2Api.credentials.ts | 38 +++++++++++++++++++ packages/nodes-base/package.json | 1 + 3 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts index d6e8e0f452..3dc1f4b960 100644 --- a/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts @@ -6,22 +6,10 @@ import { export class MicrosoftExcelOAuth2Api implements ICredentialType { name = 'microsoftExcelOAuth2Api'; extends = [ - 'oAuth2Api', + 'microsoftOAuth2Api', ]; displayName = 'Microsoft OAuth2 API'; properties = [ - { - displayName: 'Authorization URL', - name: 'authUrl', - type: 'string' as NodePropertyTypes, - default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', - }, - { - displayName: 'Access Token URL', - name: 'accessTokenUrl', - type: 'string' as NodePropertyTypes, - default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', - }, //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent { displayName: 'Scope', @@ -29,17 +17,5 @@ export class MicrosoftExcelOAuth2Api implements ICredentialType { type: 'hidden' as NodePropertyTypes, default: 'openid offline_access Files.ReadWrite', }, - { - displayName: 'Auth URI Query Parameters', - name: 'authQueryParameters', - type: 'hidden' as NodePropertyTypes, - default: 'response_mode=query', - }, - { - displayName: 'Authentication', - name: 'authentication', - type: 'hidden' as NodePropertyTypes, - default: 'body', - }, ]; } diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts new file mode 100644 index 0000000000..aa98141e3f --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts @@ -0,0 +1,38 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOAuth2Api implements ICredentialType { + name = 'microsoftOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'response_mode=query', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 462c97bd71..c5fa0e2e47 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -56,6 +56,7 @@ "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", From 882afed0585ae3917a306eb8b2cdb8de8e11fa9a Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 22 Mar 2020 15:43:35 -0400 Subject: [PATCH 09/20] :sparkles: Microsoft One Drive --- .../MicrosoftOneDriveOAuth2Api.credentials.ts | 21 + .../Microsoft/OneDrive/FileDescription.ts | 377 ++++++++++++++++++ .../Microsoft/OneDrive/FolderDescriptiont.ts | 75 ++++ .../Microsoft/OneDrive/GenericFunctions.ts | 83 ++++ .../OneDrive/MicrosoftOneDrive.node.ts | 215 ++++++++++ .../nodes/Microsoft/OneDrive/oneDrive.png | Bin 0 -> 4123 bytes packages/nodes-base/package.json | 2 + 7 files changed, 773 insertions(+) create mode 100644 packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png diff --git a/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts new file mode 100644 index 0000000000..adeeb79670 --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts @@ -0,0 +1,21 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOneDriveOAuth2Api implements ICredentialType { + name = 'microsoftOneDriveOAuth2Api'; + extends = [ + 'microsoftOAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access Files.ReadWrite.All', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts new file mode 100644 index 0000000000..32fe70a948 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts @@ -0,0 +1,377 @@ +import { INodeProperties } from "n8n-workflow"; + +export const fileOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'file', + ], + }, + }, + options: [ + { + name: 'Copy', + value: 'copy', + description: 'Copy a file', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a file', + }, + { + name: 'Download', + value: 'download', + description: 'Download a file', + }, + { + name: 'Get', + value: 'get', + description: 'Get a file', + }, + { + name: 'Search', + value: 'search', + description: 'Search a file', + }, + { + name: 'Upload', + value: 'upload', + description: 'Upload a file up to 4MB in size', + }, + ], + default: 'upload', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fileFields = [ + +/* -------------------------------------------------------------------------- */ +/* file:copy */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'File ID', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `The new name for the copy. If this isn't provided, the same name will be used as the original.`, + }, + ], + }, + { + displayName: 'Parent Reference', + name: 'parentReference', + type: 'collection', + placeholder: 'Add Parent Reference', + description: 'Reference to the parent item the copy will be created in Details ', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + description: 'Identifier of the drive instance that contains the item.', + }, + { + displayName: 'Drive Type', + name: 'driveType', + type: 'string', + default: '', + description: 'Identifies the type of drive.', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Identifier of the item in the drive.', + }, + { + displayName: 'List ID', + name: 'listId', + type: 'string', + default: '', + description: 'Identifier of the list.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the item being referenced', + }, + { + displayName: 'Path', + name: 'path', + type: 'string', + default: '', + description: 'Path that can be used to navigate to the item', + }, + { + displayName: 'Share ID', + name: 'shareId', + type: 'string', + default: '', + description: 'Identifier for a shared resource that can be accessed via the Shares API.', + }, + { + displayName: 'Site ID', + name: 'siteId', + type: 'string', + default: '', + description: 'Identifier of the site.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* file:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'Field ID', + }, +/* -------------------------------------------------------------------------- */ +/* file:download */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'download', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'File ID', + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + operation: [ + 'download' + ], + resource: [ + 'file', + ], + }, + }, + description: 'Name of the binary property to which to
write the data of the read file.', + }, +/* -------------------------------------------------------------------------- */ +/* file:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'Field ID', + }, +/* -------------------------------------------------------------------------- */ +/* file:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +/* -------------------------------------------------------------------------- */ +/* file:upload */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'The name the file should be saved as.', + }, + { + displayName: 'Parent ID', + name: 'parentId', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'ID of the parent folder that will contain the file.', + }, + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + required: true, + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + description: 'If the data to upload should be taken from binary field.', + }, + { + displayName: 'File Content', + name: 'fileContent', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + binaryData: [ + false, + ], + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + + }, + placeholder: '', + description: 'The text content of the file.', + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + binaryData: [ + true, + ], + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + + }, + placeholder: '', + description: 'Name of the binary property which contains
the data for the file.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts new file mode 100644 index 0000000000..83bd871143 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts @@ -0,0 +1,75 @@ +import { INodeProperties } from "n8n-workflow"; + +export const folderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'Get Children', + value: 'getChildren', + description: 'Get items inside a folder', + }, + { + name: 'Search', + value: 'search', + description: 'Search a folder', + }, + ], + default: 'getChildren', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const folderFields = [ + +/* -------------------------------------------------------------------------- */ +/* folder:getChildren */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Folder ID', + name: 'folderId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getChildren', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: 'Folder ID', + }, +/* -------------------------------------------------------------------------- */ +/* folder:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts new file mode 100644 index 0000000000..9330437f77 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts @@ -0,0 +1,83 @@ +import { + OptionsWithUri + } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = { json: true }): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`, + }; + try { + Object.assign(options, option); + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + if (Object.keys(qs).length === 0) { + delete options.qs; + } + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'microsoftOneDriveOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft OneDrive response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts new file mode 100644 index 0000000000..cd263d854f --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -0,0 +1,215 @@ +import { + IExecuteFunctions, + BINARY_ENCODING, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, + IBinaryKeyData, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, +} from './GenericFunctions'; + +import { + fileOperations, + fileFields, +} from './FileDescription'; + +import { + folderOperations, + folderFields +} from './FolderDescriptiont'; + +export class MicrosoftOneDrive implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft OneDrive', + name: 'microsoftOneDrive', + icon: 'file:oneDrive.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft OneDrive API.', + defaults: { + name: 'Microsoft OneDrive', + color: '#1d4bab', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftOneDriveOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'File', + value: 'file', + }, + { + name: 'Folder', + value: 'folder', + }, + ], + default: 'file', + description: 'The resource to operate on.', + }, + ...fileOperations, + ...fileFields, + ...folderOperations, + ...folderFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + 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 === 'file') { + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_copy?view=odsp-graph-online + if (operation === 'copy') { + const fileId = this.getNodeParameter('fileId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const parentReference = this.getNodeParameter('parentReference', i) as IDataObject; + const body: IDataObject = {}; + if (parentReference) { + body.parentReference = { ...parentReference }; + } + if (additionalFields.name) { + body.name = additionalFields.name as string; + } + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${fileId}/copy`, body, {}, undefined, {}, { json: true, resolveWithFullResponse: true }); + responseData = { location : responseData.headers.location }; + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online + if (operation === 'delete') { + const fileId = this.getNodeParameter('fileId', i) as string; + responseData = await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${fileId}`); + responseData = { success: true }; + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online + if (operation === 'download') { + const fileId = this.getNodeParameter('fileId', i) as string; + const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + + if (responseData.file === undefined) { + throw new Error('The ID you provided does not belong to a file.'); + } + + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}/content`, {}, {}, undefined, {}, { encoding: null, resolveWithFullResponse: true }); + + const newItem: INodeExecutionData = { + json: items[i].json, + binary: {}, + }; + + let mimeType: string | undefined; + if (responseData.headers['content-type']) { + mimeType = responseData.headers['content-type']; + } + + if (items[i].binary !== undefined) { + // Create a shallow copy of the binary data so that the old + // data references which do not get changed still stay behind + // but the incoming data does not get changed. + Object.assign(newItem.binary, items[i].binary); + } + + items[i] = newItem; + + const data = Buffer.from(responseData.body); + + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, undefined, mimeType); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online + if (operation === 'get') { + const fileId = this.getNodeParameter('fileId', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online + if (operation === 'search') { + const query = this.getNodeParameter('query', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); + responseData = responseData.filter((item: IDataObject) => item.file); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file + if (operation === 'upload') { + const parentId = this.getNodeParameter('parentId', i) as string; + const binaryData = this.getNodeParameter('binaryData', 0) as boolean; + let fileName = this.getNodeParameter('fileName', 0) as string; + + if (binaryData) { + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; + + if (items[i].binary === undefined) { + throw new Error('No binary data exists on item!'); + } + //@ts-ignore + if (items[i].binary[binaryPropertyName] === undefined) { + throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); + } + + const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; + + if (fileName !== '') { + fileName = `${fileName}.${binaryData.fileExtension}`; + } + + const body = Buffer.from(binaryData.data, BINARY_ENCODING); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body , {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length } ); + + } else { + const body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); + if (fileName === '') { + throw new Error('File name must be defined'); + } + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}.txt:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); + } + } + } + if (resource === 'folder') { + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online + if (operation === 'getChildren') { + const folderId = this.getNodeParameter('folderId', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${folderId}/children`); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online + if (operation === 'search') { + const query = this.getNodeParameter('query', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); + responseData = responseData.filter((item: IDataObject) => item.folder); + } + } + } + if (resource === 'file' && operation === 'download') { + // For file downloads the files get attached to the existing items + return this.prepareOutputData(items); + } else { + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } + } +} diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png b/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png new file mode 100644 index 0000000000000000000000000000000000000000..b33d98a8bb25e5b516df50c7769df1e617a38509 GIT binary patch literal 4123 zcma)9XIN8PvrdfkUZe*?RcdIFUJN~i-U5OY0S&$PE*L=slqS6i7z7lhiF86ox)C&HyrI4p6*(I@005xU(N;IUh=!L=N__E5 zqT8>!2tZ$BEjXZhm}BE&6YGT1an{oV2wh-O01=QL0J@}H7y!ryAo?2v000?eEB~?$3qk+Emt~!^+{lYS=A~`v3jk0s zTsjbtoy&5eW{EaM`JwcX@^?Mm#q1qD9h}61+`TTV0E$8K7tr0w&mI!w?&jeuAEd3AJ%`{c-sadXildbJz z4t@P&7y0q|k%)h80U+QSeMp!Z-efJ?s~c}G7*`z1J^gYFvRj&x<->M% zG&qrn8NofSz^9jwtT`yWP_m@kbd~coapHGiv9ro|*75Q3+&%-er>BJYjGkaX;j?1j z*JOEX_5L*ZSnEAszz-)@-QM`e#>x?1_x(u%N^y}dHFMJ+U#p&<9$UY6WVj>c1~pf7 zh@bxUJjKpN2BUJFRW+*ovFS#2SSI~upk%(B+Ix){&B>ri!4z3EOQ$wKx3Ab_sF%w3 z`wDmqLv*-WlIl`O9c_ut=LGH#o%QrCravSCE=5|b-&l;@8 zo8K}#Vqsq|SyaV>TrnwoL}e9;oV)1_Ws0?XOV`P9c7}(jTpgu*kw2rB44DZ&4`)Jc zI0oy}hSp@*&DY2XiF7o~Dm7WnOyO>5C*#8J%6*mf7>{Gs(Hpm5Ln(Jm7My8RW8Owu zRyvdRh4H|AlI(k!gbli>_!9=6k$kFYy$nVWQ6YkH%3yab6z7(6wa(lX&jXWN)x$OI>38Gl*^MQ zG#~WD%AQgKi{{|8bv4){q9ERSWNw7nemWtrq$8XM!=$+OlB4Y^eRBoxAmK)OlR?ki z$mgguEe>%za)w*tQSoG4tSF%ZPKwQNW-2}b^4v2N!Pjhumk zfU;a(b&N5bYz0`*WzzY*aCOj)23rJ$`@w=_DHsN!@q{lcH|(4P$@rI|UYt)SboWr& zhUjPAIJisY?}JF zm+Qun-xxT}=y~JFY^Eq_suSzF(-h8IjrUzki@t@|>GvPbDR9?Kcj6UDyc+KQ%s2I+ z^xfyK{(Z-fAr$v5rw89E`oaA@G_eYGqFb}gsDC?fk2&dIoPnyCP9P9%3tDch&G}S9!C*GcjhrL8Rl|<8EqHG4gu8KGo^_>WCSx zc>iPD8HFY;7N}boNaFjad^YxH{Yis$ywqkzx!&vyDFQZp;Fk#J7NpN7@(q{9$qcad zRW)&87WPkt^U4!(oni!dwi3IpqF107@y8MY z%})88*_kxV50-%wq3e+M0+H2kCKtpVJ%Lq9s~(%GdWf1tkc_Z)oY9wIL>6n*r*@UR zk1Ar7^`fYojxR0I)ojc&rnZ$L-?k&1?04$p2nqB24EHKc2?lmn{-5pSLvp1p1egR0 zLziNsZKd*ec$KUQXkGU4o3$16sJXEM zdd2!17DFU82j)VjsS>2@Vb2Y8tXa4b&1Ac7tSVnABAon4jl)ta)52st79gdOf=s{Z zwLPwwz`h9|ipfS|oB#3+rMsS~LHuo+ky&J$=;LIaSTvHI`?%cma-9?<# zXY4O|p8$6El||Fc#C@yE9`KHtX>#~A=pp|Oo^Eqs<{qt8Y?*bg#g?DQFCli!(lG%J z3-+Y#zNLI%SI&nhFMq_eO>(%B{?Y;(Yi~7tUqh~h`Wb~22Y+}#&-&-~Eu;-u!$#I* zQwK&m*qsvgq--(3tz-?jJw!CvvMEnGVFfI>HY{Yu)+_EVXb;MwGBwQ$Cn zcDtiRvymmzFb$Ev+@%rJmGXS7^jNT8^c$^PjKRcx&jnHIlCvGQ>pyQg>|tPe^DR>0 z_oNcIK%XgmgP!!= zwqoDl5XPo5&b)Z03G31LY76Zgczf4_nwWCG*#=1b>9)0j%E5}dW;18>n-erkne@7V zb`Z5&`-120I6vid3IK2=f_LEy{Ii3fb5ZEar2q=2v&G?=VBBzRZC}q{OAq;=8aTtc zA$yTw%RJo{L8a3whoV5sT_&YFN!X3?ATpBTD#+&2wY?)nzg~Lqm+>9ftNAyXyoaRU zil^_D>y?VVqflbsE}|MiMl}T3@j5Rn#*#Y*WXHROC{{_oSj6S_KoJ3iwigNkRNS=p ztFDHKR?<|Nc%)|Dk(m*7$g=H1yuHSUHLAVEk&=+WznU`Ih1y)g-RXX}J>%U)L4oi- zC3Y*b-*^m#%Uhu5+4Qa|3|o#(-PY-nOu;k~zdl|?-Z9g~tl zGA8MCDYPGgW~N`Kd(V1#b_tB&M~z_pt}ni)U?}LxyqIpzjd%uSA;)b}%ufV4GOwVx zuod(#vgDdmi+(5*Jt?L#?Xe6KZ@xES|MY{_#;-JkI?C6iByXn7k1e(=$MJ@uK~G+E zYp6%<4h6qOt#k6ln@%tP@?)kIo=9o;y3cmt6)YVQ_yi-Tq;}SxleQVMe(a9juD{)$ zIj#^gv?y)VN#bpXN@hGKnc%*`_~RiFlT1cb@|P31az_T%AN1;J^f@G?j$TtH*ZjJ8P`8( zeZpth00i$Ao&BmipmTxRFH*O|BD6qj`pdl94sU#kWS%I5f**PvA{^#vlsBu}cRTW? zixeQB#E-<`c-622UfHldGF{$Bo_jSkJn2Jc*P7m7$IP=H=&5~`9e25%hf?rL^}sj4 zIIb&Jo<@Pg2|ENQ zsA&*c0~20yv>qK2n&(8ablrDLpZY6Hil~mMiqR0+a)^^KOHaefr;=l4i!w`b@3Z%Y z^~F|4>8T+^71`GxFh-PSAjBsw_+Z}a_PCW zRU8Fz=9dbm-sAfST4HYG Date: Thu, 26 Mar 2020 15:44:48 -0400 Subject: [PATCH 10/20] :zap: added lookup and fixed issue with add columns operation --- .../Microsoft/Excel/MicrosoftExcel.node.ts | 72 ++++++++- .../nodes/Microsoft/Excel/TableDescription.ts | 138 ++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts index d7c372c2db..4edab92d35 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts @@ -173,10 +173,35 @@ export class MicrosoftExcel implements INodeType { if (additionalFields.index) { body.index = additionalFields.index as number; } - const values: any[][] = []; + + // Get table columns to eliminate any columns not needed on the input + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + const columns = responseData.value.map((column: IDataObject) => (column.name)); + + const cleanedItems: IDataObject[] = []; + + // Delete columns the excel table does not have for (const item of items) { - values.push(Object.values(item.json)); + for (const key of Object.keys(item.json)) { + if (!columns.includes(key)) { + const property = { ...item.json }; + delete property[key]; + cleanedItems.push(property); + } + } } + + // Map the keys to the column index + const values: any[][] = []; + let value = []; + for (const item of cleanedItems) { + for (const column of columns) { + value.push(item[column]); + } + values.push(value); + value = []; + } + body.values = values; const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`, body, {}, '', { 'workbook-session-id': id }); @@ -204,8 +229,7 @@ export class MicrosoftExcel implements INodeType { responseData = responseData.value; } if (!rawData) { - //@ts-ignore - responseData = responseData.map(column => ({ name: column.name })); + responseData = responseData.map((column: IDataObject) => ({ name: column.name })); } else { const dataProperty = this.getNodeParameter('dataProperty', i) as string; responseData = { [dataProperty] : responseData }; @@ -251,6 +275,46 @@ export class MicrosoftExcel implements INodeType { } } } + if (operation === 'lookup') { + for (let i = 0; i < length; i++) { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const lookupColumn = this.getNodeParameter('lookupColumn', 0) as string; + const lookupValue = this.getNodeParameter('lookupValue', 0) as string; + const options = this.getNodeParameter('options', 0) as IDataObject; + + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + + qs['$select'] = 'name'; + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + columns = columns.map((column: IDataObject) => column.name); + for (let i = 0; i < responseData.length; i++) { + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + result.push({ ...object }); + } + responseData = result; + + if (!columns.includes(lookupColumn)) { + throw new Error(`Column ${lookupColumn} does not exist on the table selected`); + } + + if (options.returnAllMatches) { + + responseData = responseData.filter((data: IDataObject) => { + return (data[lookupColumn]?.toString() === lookupValue ); + }); + + } else { + + responseData = responseData.find((data: IDataObject) => { + return (data[lookupColumn]?.toString() === lookupValue ); + }); + } + } + } } if (resource === 'workbook') { for (let i = 0; i < length; i++) { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts index 8c491a16e1..ea8b78b9d6 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts @@ -28,6 +28,11 @@ export const tableOperations = [ value: 'getRows', description: 'Retrieve a list of tablerows', }, + { + name: 'Lookup', + value: 'lookup', + description: 'Looks for a specific column value and then returns the matching row' + }, ], default: 'addRow', description: 'The operation to perform.', @@ -484,4 +489,137 @@ export const tableFields = [ }, ] }, +/* -------------------------------------------------------------------------- */ +/* table:lookup */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Lookup Column', + name: 'lookupColumn', + type: 'string', + default: '', + placeholder: 'Email', + required: true, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup' + ], + }, + }, + description: 'The name of the column in which to look for value.', + }, + { + displayName: 'Lookup Value', + name: 'lookupValue', + type: 'string', + default: '', + placeholder: 'frank@example.com', + required: true, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup' + ], + }, + }, + description: 'The value to look for in column.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup', + ], + }, + }, + options: [ + { + displayName: 'Return All Matches', + name: 'returnAllMatches', + type: 'boolean', + default: false, + description: 'By default only the first result gets returned. If options gets set all found matches get returned.', + }, + ], + } ] as INodeProperties[]; From bba6a8494d5b4a616d1171bb1d3c7430b6d70460 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 28 Mar 2020 19:08:39 +0100 Subject: [PATCH 11/20] :zap: Fixed some issues with Excel-Node --- .../credentials/TestOAuth2Api.credentials.ts | 26 ++ .../nodes/Google/GoogleDriveTrigger.node.ts | 293 ++++++++++++++++++ .../Microsoft/Excel/MicrosoftExcel.node.ts | 141 +++++---- .../nodes/Microsoft/Excel/TableDescription.ts | 2 +- .../Microsoft/Excel/WorkbookDescription.ts | 2 +- .../Microsoft/Excel/WorksheetDescription.ts | 2 +- .../nodes/Microsoft/Excel/excel.png | Bin 5984 -> 1964 bytes packages/nodes-base/package.json | 26 +- 8 files changed, 412 insertions(+), 80 deletions(-) create mode 100644 packages/nodes-base/credentials/TestOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts diff --git a/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts new file mode 100644 index 0000000000..2a350faecf --- /dev/null +++ b/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts @@ -0,0 +1,26 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +const scopes = [ + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/calendar.events', +]; + +export class TestOAuth2Api implements ICredentialType { + name = 'testOAuth2Api'; + extends = [ + 'googleOAuth2Api', + ]; + displayName = 'Test OAuth2 API'; + properties = [ + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'asdf', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts b/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts new file mode 100644 index 0000000000..e347222e7e --- /dev/null +++ b/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts @@ -0,0 +1,293 @@ +// import { google } from 'googleapis'; + +// import { +// IHookFunctions, +// IWebhookFunctions, +// } from 'n8n-core'; + +// import { +// IDataObject, +// INodeTypeDescription, +// INodeType, +// IWebhookResponseData, +// } from 'n8n-workflow'; + +// import { getAuthenticationClient } from './GoogleApi'; + + +// export class GoogleDriveTrigger implements INodeType { +// description: INodeTypeDescription = { +// displayName: 'Google Drive Trigger', +// name: 'googleDriveTrigger', +// icon: 'file:googleDrive.png', +// group: ['trigger'], +// version: 1, +// subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}', +// description: 'Starts the workflow when a file on Google Drive got changed.', +// defaults: { +// name: 'Google Drive Trigger', +// color: '#3f87f2', +// }, +// inputs: [], +// outputs: ['main'], +// credentials: [ +// { +// name: 'googleApi', +// required: true, +// } +// ], +// webhooks: [ +// { +// name: 'default', +// httpMethod: 'POST', +// responseMode: 'onReceived', +// path: 'webhook', +// }, +// ], +// properties: [ +// { +// displayName: 'Resource Id', +// name: 'resourceId', +// type: 'string', +// default: '', +// required: true, +// placeholder: '', +// description: 'ID of the resource to watch, for example a file ID.', +// }, +// ], +// }; + +// // @ts-ignore (because of request) +// webhookMethods = { +// default: { +// async checkExists(this: IHookFunctions): Promise { +// // const webhookData = this.getWorkflowStaticData('node'); + +// // if (webhookData.webhookId === undefined) { +// // // No webhook id is set so no webhook can exist +// // return false; +// // } + +// // // Webhook got created before so check if it still exists +// // const owner = this.getNodeParameter('owner') as string; +// // const repository = this.getNodeParameter('repository') as string; +// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; + +// // try { +// // await githubApiRequest.call(this, 'GET', endpoint, {}); +// // } catch (e) { +// // if (e.message.includes('[404]:')) { +// // // Webhook does not exist +// // delete webhookData.webhookId; +// // delete webhookData.webhookEvents; + +// // return false; +// // } + +// // // Some error occured +// // throw e; +// // } + +// // If it did not error then the webhook exists +// // return true; +// return false; +// }, +// async create(this: IHookFunctions): Promise { +// const webhookUrl = this.getNodeWebhookUrl('default'); + +// const resourceId = this.getNodeParameter('resourceId') as string; + +// const credentials = this.getCredentials('googleApi'); + +// if (credentials === undefined) { +// throw new Error('No credentials got returned!'); +// } + +// const scopes = [ +// 'https://www.googleapis.com/auth/drive', +// 'https://www.googleapis.com/auth/drive.appdata', +// 'https://www.googleapis.com/auth/drive.photos.readonly', +// ]; + +// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); + +// const drive = google.drive({ +// version: 'v3', +// auth: client, +// }); + + +// const accessToken = await client.getAccessToken(); +// console.log('accessToken: '); +// console.log(accessToken); + +// const asdf = await drive.changes.getStartPageToken(); +// // console.log('asdf: '); +// // console.log(asdf); + + + + +// const response = await drive.changes.watch({ +// // +// pageToken: asdf.data.startPageToken, +// requestBody: { +// id: 'asdf-test-2', +// address: webhookUrl, +// resourceId, +// type: 'web_hook', +// // page_token: '', +// } +// }); + +// console.log('...response...CREATE'); +// console.log(JSON.stringify(response, null, 2)); + + + + + +// // const endpoint = `/repos/${owner}/${repository}/hooks`; + +// // const body = { +// // name: 'web', +// // config: { +// // url: webhookUrl, +// // content_type: 'json', +// // // secret: '...later...', +// // insecure_ssl: '1', // '0' -> not allow inscure ssl | '1' -> allow insercure SSL +// // }, +// // events, +// // active: true, +// // }; + + +// // let responseData; +// // try { +// // responseData = await githubApiRequest.call(this, 'POST', endpoint, body); +// // } catch (e) { +// // if (e.message.includes('[422]:')) { +// // throw new Error('A webhook with the identical URL exists already. Please delete it manually on Github!'); +// // } + +// // throw e; +// // } + +// // if (responseData.id === undefined || responseData.active !== true) { +// // // Required data is missing so was not successful +// // throw new Error('Github webhook creation response did not contain the expected data.'); +// // } + +// // const webhookData = this.getWorkflowStaticData('node'); +// // webhookData.webhookId = responseData.id as string; +// // webhookData.webhookEvents = responseData.events as string[]; + +// return true; +// }, +// async delete(this: IHookFunctions): Promise { +// const webhookUrl = this.getNodeWebhookUrl('default'); + +// const resourceId = this.getNodeParameter('resourceId') as string; + +// const credentials = this.getCredentials('googleApi'); + +// if (credentials === undefined) { +// throw new Error('No credentials got returned!'); +// } + +// const scopes = [ +// 'https://www.googleapis.com/auth/drive', +// 'https://www.googleapis.com/auth/drive.appdata', +// 'https://www.googleapis.com/auth/drive.photos.readonly', +// ]; + +// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); + +// const drive = google.drive({ +// version: 'v3', +// auth: client, +// }); + +// // Remove channel +// const response = await drive.channels.stop({ +// requestBody: { +// id: 'asdf-test-2', +// address: webhookUrl, +// resourceId, +// type: 'web_hook', +// } +// }); + + +// console.log('...response...DELETE'); +// console.log(JSON.stringify(response, null, 2)); + + + +// // const webhookData = this.getWorkflowStaticData('node'); + +// // if (webhookData.webhookId !== undefined) { +// // const owner = this.getNodeParameter('owner') as string; +// // const repository = this.getNodeParameter('repository') as string; +// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; +// // const body = {}; + +// // try { +// // await githubApiRequest.call(this, 'DELETE', endpoint, body); +// // } catch (e) { +// // return false; +// // } + +// // // Remove from the static workflow data so that it is clear +// // // that no webhooks are registred anymore +// // delete webhookData.webhookId; +// // delete webhookData.webhookEvents; +// // } + +// return true; +// }, +// }, +// }; + + + +// async webhook(this: IWebhookFunctions): Promise { +// const bodyData = this.getBodyData(); + +// console.log(''); +// console.log(''); +// console.log('GOT WEBHOOK CALL'); +// console.log(JSON.stringify(bodyData, null, 2)); + + + +// // Check if the webhook is only the ping from Github to confirm if it workshook_id +// if (bodyData.hook_id !== undefined && bodyData.action === undefined) { +// // Is only the ping and not an actual webhook call. So return 'OK' +// // but do not start the workflow. + +// return { +// webhookResponse: 'OK' +// }; +// } + +// // Is a regular webhoook call + +// // TODO: Add headers & requestPath +// const returnData: IDataObject[] = []; + +// returnData.push( +// { +// body: bodyData, +// headers: this.getHeaderData(), +// query: this.getQueryData(), +// } +// ); + +// return { +// workflowData: [ +// this.helpers.returnJsonArray(returnData) +// ], +// }; +// } +// } diff --git a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts index 4edab92d35..26e58ef9ad 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts @@ -153,23 +153,23 @@ export class MicrosoftExcel implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; const length = items.length as unknown as number; - const qs: IDataObject = {}; + let qs: IDataObject = {}; const result: IDataObject[] = []; - const object: IDataObject = {}; let responseData; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'table') { //https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http if (operation === 'addRow') { + // TODO: At some point it should be possible to use item dependent parameters. + // Is however important to then not make one separate request each. const workbookId = this.getNodeParameter('workbook', 0) as string; const worksheetId = this.getNodeParameter('worksheet', 0) as string; const tableId = this.getNodeParameter('table', 0) as string; const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject; const body: IDataObject = {}; - if (Object.keys(items[0].json).length === 0) { - throw new Error('Input cannot be empty'); - } + if (additionalFields.index) { body.index = additionalFields.index as number; } @@ -178,41 +178,35 @@ export class MicrosoftExcel implements INodeType { responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); const columns = responseData.value.map((column: IDataObject) => (column.name)); - const cleanedItems: IDataObject[] = []; + const rows: any[][] = []; // tslint:disable-line:no-any - // Delete columns the excel table does not have + // Bring the items into the correct format for (const item of items) { - for (const key of Object.keys(item.json)) { - if (!columns.includes(key)) { - const property = { ...item.json }; - delete property[key]; - cleanedItems.push(property); - } - } - } - - // Map the keys to the column index - const values: any[][] = []; - let value = []; - for (const item of cleanedItems) { + const row = []; for (const column of columns) { - value.push(item[column]); + row.push(item.json[column]); } - values.push(value); - value = []; + rows.push(row); } - body.values = values; + body.values = rows; const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`, body, {}, '', { 'workbook-session-id': id }); await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } } //https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http if (operation === 'getColumns') { for (let i = 0; i < length; i++) { - const workbookId = this.getNodeParameter('workbook', 0) as string; - const worksheetId = this.getNodeParameter('worksheet', 0) as string; - const tableId = this.getNodeParameter('table', 0) as string; + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; const returnAll = this.getNodeParameter('returnAll', i) as boolean; const rawData = this.getNodeParameter('rawData', i) as boolean; if (rawData) { @@ -234,14 +228,21 @@ export class MicrosoftExcel implements INodeType { const dataProperty = this.getNodeParameter('dataProperty', i) as string; responseData = { [dataProperty] : responseData }; } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } } } //https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http if (operation === 'getRows') { for (let i = 0; i < length; i++) { - const workbookId = this.getNodeParameter('workbook', 0) as string; - const worksheetId = this.getNodeParameter('worksheet', 0) as string; - const tableId = this.getNodeParameter('table', 0) as string; + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; const returnAll = this.getNodeParameter('returnAll', i) as boolean; const rawData = this.getNodeParameter('rawData', i) as boolean; if (rawData) { @@ -253,71 +254,78 @@ export class MicrosoftExcel implements INodeType { if (returnAll === true) { responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); } else { - qs['$top'] = this.getNodeParameter('limit', i) as number; - responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + const rowsQs = { ...qs }; + rowsQs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, rowsQs); responseData = responseData.value; } if (!rawData) { - qs['$select'] = 'name'; - let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + const columnsQs = { ...qs }; + columnsQs['$select'] = 'name'; + // TODO: That should probably be cached in the future + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, columnsQs); //@ts-ignore columns = columns.map(column => column.name); for (let i = 0; i < responseData.length; i++) { + const object: IDataObject = {}; for (let y = 0; y < columns.length; y++) { object[columns[y]] = responseData[i].values[0][y]; } - result.push({ ...object }); + returnData.push({ ...object }); } - responseData = result; } else { const dataProperty = this.getNodeParameter('dataProperty', i) as string; - responseData = { [dataProperty] : responseData }; + returnData.push({ [dataProperty]: responseData }); } } } if (operation === 'lookup') { for (let i = 0; i < length; i++) { - const workbookId = this.getNodeParameter('workbook', 0) as string; - const worksheetId = this.getNodeParameter('worksheet', 0) as string; - const tableId = this.getNodeParameter('table', 0) as string; - const lookupColumn = this.getNodeParameter('lookupColumn', 0) as string; - const lookupValue = this.getNodeParameter('lookupValue', 0) as string; - const options = this.getNodeParameter('options', 0) as IDataObject; + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; + const lookupColumn = this.getNodeParameter('lookupColumn', i) as string; + const lookupValue = this.getNodeParameter('lookupValue', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; - responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, {}); qs['$select'] = 'name'; + // TODO: That should probably be cached in the future let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); columns = columns.map((column: IDataObject) => column.name); - for (let i = 0; i < responseData.length; i++) { - for (let y = 0; y < columns.length; y++) { - object[columns[y]] = responseData[i].values[0][y]; - } - result.push({ ...object }); - } - responseData = result; if (!columns.includes(lookupColumn)) { throw new Error(`Column ${lookupColumn} does not exist on the table selected`); } + result.length = 0; + for (let i = 0; i < responseData.length; i++) { + const object: IDataObject = {}; + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + result.push({ ...object }); + } + if (options.returnAllMatches) { - - responseData = responseData.filter((data: IDataObject) => { + responseData = result.filter((data: IDataObject) => { return (data[lookupColumn]?.toString() === lookupValue ); }); - + returnData.push.apply(returnData, responseData as IDataObject[]); } else { - - responseData = responseData.find((data: IDataObject) => { + responseData = result.find((data: IDataObject) => { return (data[lookupColumn]?.toString() === lookupValue ); }); + returnData.push(responseData as IDataObject); } } } } if (resource === 'workbook') { for (let i = 0; i < length; i++) { + qs = {}; //https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http if (operation === 'addWorksheet') { const workbookId = this.getNodeParameter('workbook', i) as string; @@ -344,10 +352,17 @@ export class MicrosoftExcel implements INodeType { responseData = responseData.value; } } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } } } if (resource === 'worksheet') { for (let i = 0; i < length; i++) { + qs = {}; //https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http if (operation === 'getAll') { const returnAll = this.getNodeParameter('returnAll', i) as boolean; @@ -376,7 +391,9 @@ export class MicrosoftExcel implements INodeType { qs['$select'] = filters.fields; } } + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`, {}, qs); + if (!rawData) { const keyRow = this.getNodeParameter('keyRow', i) as number; const dataStartRow = this.getNodeParameter('dataStartRow', i) as number; @@ -385,24 +402,20 @@ export class MicrosoftExcel implements INodeType { } const keyValues = responseData.values[keyRow]; for (let i = dataStartRow; i < responseData.values.length; i++) { + const object: IDataObject = {}; for (let y = 0; y < keyValues.length; y++) { object[keyValues[y]] = responseData.values[i][y]; } - result.push({ ...object }); + returnData.push({ ...object }); } - responseData = result; } else { const dataProperty = this.getNodeParameter('dataProperty', i) as string; - responseData = { [dataProperty] : responseData }; + returnData.push({ [dataProperty]: responseData }); } } } } - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else if (responseData !== undefined) { - returnData.push(responseData as IDataObject); - } + return [this.helpers.returnJsonArray(returnData)]; } } diff --git a/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts index ea8b78b9d6..8c399330d5 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const tableOperations = [ { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts index 526a8ceb43..61f7b6f501 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const workbookOperations = [ { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts index f50fbb3ece..e5811e6c68 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const worksheetOperations = [ { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/excel.png b/packages/nodes-base/nodes/Microsoft/Excel/excel.png index 40631413a5f656ad5819f9faada8eecc78509bd4..ba2b10c06305af8b06f424a16a69ed42fa1a584f 100644 GIT binary patch delta 1961 zcmV;a2UhstF02ob8Gi!+006nq0-pc?0n1QKR7C&)03dQjFKs^`az!9-RPprjAaX?@ zazy}ZH2?qqV2g0g*3bZ93m|et0BSTMeM&odQaXB6A8|$>aYZ3;L?CiSJ9<MIUoS5pFjhazy}ZH6Laz!6=Mt>r7N5Rd-AaO(>azr0; zMj&xTAaO-%jdlQI8USlG0BSS|Tmbs}`yz5i8*xJ*bVdMeJN5SUBy&dqWdQs9`}g?wGIvh-`uV)cz$SA@0BSRa zqmMy+ST%T1Fn@MUB6>;yXDjFI>BZ2<0An2hSOC!2(yYC)K6_R?dQ>fSOS#9su)wt< zcu6UANpzNfaFl#Fc~Ssl7wYco*528`%)~@}S^#Dx0A2ye)68p;cvpsKPl94ee_a4< zHvnld0AdsJ_4DoU?y0)2UW#oebVvYYBLG$a%GAxY!+*J3h-)K!OXKL|c$tG2a6tfJ z4FF&U08#+b+SH!1q<@`>e4B<-gk%wLKmcYZ07wAj>E)!hs%DOKWQ}qPZ$1Ds0N&%@ zkg1r7q>+T7jQ~sl06YKyIsi6*R4s#2cDML-0000rbW%=J0Q&{h1N{B@&-^L;`SZ>R zg@Hml9)Hk=X!_r&n=u*?=+LxVPAMY!$G?kzb|CZ8(Yun0ficYhgmitF%Bz__Zq6xWeMCoUY=-D|a` zw7WFAZU(FMipEnXFCIL5RG*skr>un5+WWUcC`$Qtc*ov@=k^;+DSygj)fx;2y(B!X zF+%A}Sg}>>4(~j1a|*fR!hj}t-VJye?r}O>cmZHu>6yLlcdt>3B8_Z zV}I?vb!k%vm)5BpdnWypDXSh{5L|zN3T@f9MPVO&r0>f;hg6L{re1InDllw%_S5Hk zOZoh1?#BVuR_!){O_jU0*x1HS*5BT$QKM%i;eF&HNi#A#Nf{@JNOb@9v95(47m_fe z7G@Hy?^@`1;q4m?27`9gUku6=AEVU>p?@o7dTL@cfCj&XXkhef5?MgFp-Umad*kU? zKZ=ArULe-!!xd5#rBvl`INCX^3hx%)1<>YatWd31Up#w+DP$5pAMc>Ra5s@ov#jEC zjLZ*LhWpWTc6bDsC){{K=BZFyC{YafZ1hyVHcl;eIsVEuX19~b0^bs2E=1x8S$|(; zIW-U&BCs1O=={E7{qEV|upI|@8< z%RKskRUwlo2ce2cIcM{%N2Kdbo~H;2hMJ~FC-Q$C8;UPAIWywww)|`41hG%)Gn0#Z zd;<`h(m*aAj}M5LO>yw_Aef@`Q-Am;m@|2AtIdK~4cP+d;g@$}tyZ(ivB8)eSZ^o` zqy38h3_}zNd#P4z=9~5TI>g3T9|r1t5l;v&O+N=$JqTZ!hL0tb*aR$gjw6xNzK8{p zSVGp9pM^(*_YrztR(!;`%51Z;5R8dDB!VOn%9IkV+2$#qboceS$50^ICx4UtfG~wr zV(KF@OCy+1Gr!F?^aTB^46%7%4g>W2B;FVT4(i=2co^NA;)ym)%u~r#AU3s%2dD^c zY++h>%OQB>;&g&(3n?*Fn-(B8FRP7GsU&=Pgezou`XM6Wr7cY%-S*vmlg+-#_M^f` zb_mGM2XSMQSQvs<7A$5|qh&Pkwwh>vOuO8#1T^C!jojQZ$?b8 zUV4VJo4GnBku%?~t#I&BmFlVdolf%py?^S$NxxtHXr?C&*SGK4 z4s4Uz18m#2eftiLDu?!-Ki*?7!GX_2Xgdy%9j8$|bh{Q$%6>SKS_vo@#EKBk%eqd~ zW$oJv<7xRUPk>fB%gciR!fvk!+P4iS;kMzZH6lPRTFgx#WR vU$Yob?Es8jNFr2kN&erfsx{VFL!RV)@{00000NkvXXu0mjf0^f~! literal 5984 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 bdqb4Qmz%rb(0TCZ=~zQWN4Z?lGU$H*2Ppz* diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c5fa0e2e47..25f148aa7e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,8 +42,8 @@ "dist/credentials/GithubApi.credentials.js", "dist/credentials/GithubOAuth2Api.credentials.js", "dist/credentials/GitlabApi.credentials.js", - "dist/credentials/GoogleApi.credentials.js", - "dist/credentials/GoogleOAuth2Api.credentials.js", + "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/GoogleOAuth2Api.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", @@ -55,9 +55,9 @@ "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/MattermostApi.credentials.js", - "dist/credentials/MicrosoftOAuth2Api.credentials.js", - "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", + "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -84,8 +84,8 @@ "dist/credentials/TypeformApi.credentials.js", "dist/credentials/TogglApi.credentials.js", "dist/credentials/VeroApi.credentials.js", - "dist/credentials/WordpressApi.credentials.js", - "dist/credentials/ZohoOAuth2Api.credentials.js" + "dist/credentials/WordpressApi.credentials.js", + "dist/credentials/ZohoOAuth2Api.credentials.js" ], "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", @@ -119,8 +119,8 @@ "dist/nodes/Github/Github.node.js", "dist/nodes/Github/GithubTrigger.node.js", "dist/nodes/Gitlab/Gitlab.node.js", - "dist/nodes/Gitlab/GitlabTrigger.node.js", - "dist/nodes/Google/GoogleCalendar.node.js", + "dist/nodes/Gitlab/GitlabTrigger.node.js", + "dist/nodes/Google/GoogleCalendar.node.js", "dist/nodes/Google/GoogleDrive.node.js", "dist/nodes/Google/GoogleSheets.node.js", "dist/nodes/GraphQL/GraphQL.node.js", @@ -137,8 +137,8 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Merge.node.js", - "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", + "dist/nodes/Merge.node.js", + "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js", @@ -181,8 +181,8 @@ "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Wordpress/Wordpress.node.js", - "dist/nodes/Xml.node.js", - "dist/nodes/Zoho/ZohoCrm.node.js" + "dist/nodes/Xml.node.js", + "dist/nodes/Zoho/ZohoCrm.node.js" ] }, "devDependencies": { From 11fab3c702886da74173862c1708e3753fcaf630 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 28 Mar 2020 19:52:51 -0400 Subject: [PATCH 12/20] :zap: small fix --- packages/nodes-base/nodes/HelpScout/HelpScout.node.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts index 27534b2426..a339e6cfb4 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -399,11 +399,11 @@ export class HelpScout implements INodeType { responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`); } } - } - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else if (responseData !== undefined) { - returnData.push(responseData as IDataObject); + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } } return [this.helpers.returnJsonArray(returnData)]; } From 5cbe76a3ceffeba8e0c052d4694d913c8dd93ccb Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 28 Mar 2020 20:13:56 -0400 Subject: [PATCH 13/20] :zap: small fix --- .../nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index cd263d854f..cee6733bd5 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -98,12 +98,14 @@ export class MicrosoftOneDrive implements INodeType { } responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${fileId}/copy`, body, {}, undefined, {}, { json: true, resolveWithFullResponse: true }); responseData = { location : responseData.headers.location }; + returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online if (operation === 'delete') { const fileId = this.getNodeParameter('fileId', i) as string; responseData = await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${fileId}`); responseData = { success: true }; + returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online if (operation === 'download') { @@ -144,12 +146,14 @@ export class MicrosoftOneDrive implements INodeType { if (operation === 'get') { const fileId = this.getNodeParameter('fileId', i) as string; responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online if (operation === 'search') { const query = this.getNodeParameter('query', i) as string; responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); responseData = responseData.filter((item: IDataObject) => item.file); + returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file if (operation === 'upload') { @@ -176,6 +180,7 @@ export class MicrosoftOneDrive implements INodeType { const body = Buffer.from(binaryData.data, BINARY_ENCODING); responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body , {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length } ); + returnData.push(responseData as IDataObject); } else { const body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); @@ -183,6 +188,7 @@ export class MicrosoftOneDrive implements INodeType { throw new Error('File name must be defined'); } responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}.txt:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); + returnData.push(responseData as IDataObject); } } } @@ -191,12 +197,14 @@ export class MicrosoftOneDrive implements INodeType { if (operation === 'getChildren') { const folderId = this.getNodeParameter('folderId', i) as string; responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${folderId}/children`); + returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online if (operation === 'search') { const query = this.getNodeParameter('query', i) as string; responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); responseData = responseData.filter((item: IDataObject) => item.folder); + returnData.push(responseData as IDataObject); } } } From 83e6e8bf114399dc2dce70df330b02bc53f0cc28 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 29 Mar 2020 19:10:54 +0200 Subject: [PATCH 14/20] :zap: Improved HelpScout-Nodes --- .../HelpScout/ConversationDescription.ts | 126 +- .../nodes/HelpScout/ConversationInterface.ts | 10 +- .../nodes/HelpScout/CountriesCodes.ts | 2160 ++++++++--------- .../nodes/HelpScout/CustomerDescription.ts | 86 +- .../nodes/HelpScout/CustomerInterface.ts | 2 +- .../nodes/HelpScout/GenericFunctions.ts | 5 +- .../nodes/HelpScout/HelpScout.node.ts | 51 +- .../nodes/HelpScout/HelpScoutTrigger.node.ts | 49 +- .../nodes/HelpScout/MailboxDescription.ts | 2 +- .../nodes/HelpScout/ThreadDescription.ts | 14 +- .../nodes/HelpScout/ThreadInterface.ts | 2 +- .../nodes-base/nodes/HelpScout/helpScout.png | Bin 4862 -> 870 bytes packages/nodes-base/package.json | 8 +- 13 files changed, 1259 insertions(+), 1256 deletions(-) diff --git a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts index cce35b096a..5d882ac80f 100644 --- a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const conversationOperations = [ { @@ -310,7 +310,7 @@ export const conversationFields = [ alwaysOpenEditWindow: true }, default: '', - description: 'The message text, ' + description: 'The message text.' }, { displayName: 'Bcc', @@ -428,6 +428,13 @@ export const conversationFields = [ }, }, options: [ + { + displayName: 'Assign To', + name: 'assignTo', + type: 'number', + default: 0, + description: 'Filters conversations by assignee id', + }, { displayName: 'Embed', name: 'embed', @@ -441,13 +448,6 @@ export const conversationFields = [ default: '', description: 'Allows embedding/loading of sub-entities', }, - { - displayName: 'Mailbox ID', - name: 'mailbox', - type: 'string', - default: '', - description: 'Filters conversations from a specific mailbox', - }, { displayName: 'Folder ID', name: 'folder', @@ -456,54 +456,11 @@ export const conversationFields = [ description: 'Filters conversations from a specific folder id', }, { - displayName: 'Status', - name: 'status', - type: 'options', - options: [ - { - name: 'Active', - value: 'active', - }, - { - name: 'All', - value: 'all', - }, - { - name: 'Closed', - value: 'closed', - }, - { - name: 'Open', - value: 'open', - }, - { - name: 'Pending', - value: 'pending', - }, - { - name: 'Spam', - value: 'spam', - }, - ], - default: 'active', - description: 'Filter conversation by status', - }, - { - displayName: 'Tags', - name: 'tags', - type: 'multiOptions', - typeOptions: { - loadOptionsMethod: 'getTags', - }, - default: [], - description: 'Filter conversation by tags', - }, - { - displayName: 'Assign To', - name: 'assignTo', - type: 'number', - default: 0, - description: 'Filters conversations by assignee id', + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters conversations from a specific mailbox', }, { displayName: 'Modified Since', @@ -522,6 +479,16 @@ export const conversationFields = [ }, description: 'Looks up conversation by conversation number', }, + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Advanced search Examples' + }, { displayName: 'Sort Field', name: 'sortField', @@ -584,14 +551,47 @@ export const conversationFields = [ default: 'desc', }, { - displayName: 'Query', - name: 'query', - type: 'string', + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'All', + value: 'all', + }, + { + name: 'Closed', + value: 'closed', + }, + { + name: 'Open', + value: 'open', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Spam', + value: 'spam', + }, + ], + default: 'active', + description: 'Filter conversation by status', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', typeOptions: { - alwaysOpenEditWindow: true, + loadOptionsMethod: 'getTags', }, - default: '', - description: 'Advanced search Examples' + default: [], + description: 'Filter conversation by tags', }, ], }, diff --git a/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts index 4140fda218..ea617f5aa3 100644 --- a/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts +++ b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts @@ -1,4 +1,4 @@ -import { IDataObject } from "n8n-workflow"; +import { IDataObject } from 'n8n-workflow'; export interface IConversation { assignTo?: number; @@ -8,11 +8,11 @@ export interface IConversation { customer?: IDataObject; fields?: IDataObject[]; imported?: boolean; - mailboxId?: number; // - status?: string; // - subject?: string; // + mailboxId?: number; + status?: string; + subject?: string; tags?: IDataObject[]; threads?: IDataObject[]; - type?: string; // + type?: string; user?: number; } diff --git a/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts index 653e876beb..3c41a76c7c 100644 --- a/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts +++ b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts @@ -1,1579 +1,1579 @@ export const countriesCodes = [ { - "name": "Afghanistan", - "alpha2": "AF", - "alpha3": "AFG", - "numeric": "004" + 'name': 'Afghanistan', + 'alpha2': 'AF', + 'alpha3': 'AFG', + 'numeric': '004' }, { - "name": "Åland Islands", - "alpha2": "AX", - "alpha3": "ALA", - "numeric": "248", - "altName": "Aland Islands" + 'name': 'Åland Islands', + 'alpha2': 'AX', + 'alpha3': 'ALA', + 'numeric': '248', + 'altName': 'Aland Islands' }, { - "name": "Albania", - "alpha2": "AL", - "alpha3": "ALB", - "numeric": "008" + 'name': 'Albania', + 'alpha2': 'AL', + 'alpha3': 'ALB', + 'numeric': '008' }, { - "name": "Algeria", - "alpha2": "DZ", - "alpha3": "DZA", - "numeric": "012" + 'name': 'Algeria', + 'alpha2': 'DZ', + 'alpha3': 'DZA', + 'numeric': '012' }, { - "name": "American Samoa", - "alpha2": "AS", - "alpha3": "ASM", - "numeric": "016" + 'name': 'American Samoa', + 'alpha2': 'AS', + 'alpha3': 'ASM', + 'numeric': '016' }, { - "name": "Andorra", - "alpha2": "AD", - "alpha3": "AND", - "numeric": "020" + 'name': 'Andorra', + 'alpha2': 'AD', + 'alpha3': 'AND', + 'numeric': '020' }, { - "name": "Angola", - "alpha2": "AO", - "alpha3": "AGO", - "numeric": "024" + 'name': 'Angola', + 'alpha2': 'AO', + 'alpha3': 'AGO', + 'numeric': '024' }, { - "name": "Anguilla", - "alpha2": "AI", - "alpha3": "AIA", - "numeric": "660" + 'name': 'Anguilla', + 'alpha2': 'AI', + 'alpha3': 'AIA', + 'numeric': '660' }, { - "name": "Antarctica", - "alpha2": "AQ", - "alpha3": "ATA", - "numeric": "010" + 'name': 'Antarctica', + 'alpha2': 'AQ', + 'alpha3': 'ATA', + 'numeric': '010' }, { - "name": "Antigua and Barbuda", - "alpha2": "AG", - "alpha3": "ATG", - "numeric": "028" + 'name': 'Antigua and Barbuda', + 'alpha2': 'AG', + 'alpha3': 'ATG', + 'numeric': '028' }, { - "name": "Argentina", - "alpha2": "AR", - "alpha3": "ARG", - "numeric": "032" + 'name': 'Argentina', + 'alpha2': 'AR', + 'alpha3': 'ARG', + 'numeric': '032' }, { - "name": "Armenia", - "alpha2": "AM", - "alpha3": "ARM", - "numeric": "051" + 'name': 'Armenia', + 'alpha2': 'AM', + 'alpha3': 'ARM', + 'numeric': '051' }, { - "name": "Aruba", - "alpha2": "AW", - "alpha3": "ABW", - "numeric": "533" + 'name': 'Aruba', + 'alpha2': 'AW', + 'alpha3': 'ABW', + 'numeric': '533' }, { - "name": "Australia", - "alpha2": "AU", - "alpha3": "AUS", - "numeric": "036" + 'name': 'Australia', + 'alpha2': 'AU', + 'alpha3': 'AUS', + 'numeric': '036' }, { - "name": "Austria", - "alpha2": "AT", - "alpha3": "AUT", - "numeric": "040" + 'name': 'Austria', + 'alpha2': 'AT', + 'alpha3': 'AUT', + 'numeric': '040' }, { - "name": "Azerbaijan", - "alpha2": "AZ", - "alpha3": "AZE", - "numeric": "031" + 'name': 'Azerbaijan', + 'alpha2': 'AZ', + 'alpha3': 'AZE', + 'numeric': '031' }, { - "name": "Bahamas (the)", - "alpha2": "BS", - "alpha3": "BHS", - "numeric": "044", - "altName": "Bahamas" + 'name': 'Bahamas (the)', + 'alpha2': 'BS', + 'alpha3': 'BHS', + 'numeric': '044', + 'altName': 'Bahamas' }, { - "name": "Bahrain", - "alpha2": "BH", - "alpha3": "BHR", - "numeric": "048" + 'name': 'Bahrain', + 'alpha2': 'BH', + 'alpha3': 'BHR', + 'numeric': '048' }, { - "name": "Bangladesh", - "alpha2": "BD", - "alpha3": "BGD", - "numeric": "050" + 'name': 'Bangladesh', + 'alpha2': 'BD', + 'alpha3': 'BGD', + 'numeric': '050' }, { - "name": "Barbados", - "alpha2": "BB", - "alpha3": "BRB", - "numeric": "052" + 'name': 'Barbados', + 'alpha2': 'BB', + 'alpha3': 'BRB', + 'numeric': '052' }, { - "name": "Belarus", - "alpha2": "BY", - "alpha3": "BLR", - "numeric": "112" + 'name': 'Belarus', + 'alpha2': 'BY', + 'alpha3': 'BLR', + 'numeric': '112' }, { - "name": "Belgium", - "alpha2": "BE", - "alpha3": "BEL", - "numeric": "056" + 'name': 'Belgium', + 'alpha2': 'BE', + 'alpha3': 'BEL', + 'numeric': '056' }, { - "name": "Belize", - "alpha2": "BZ", - "alpha3": "BLZ", - "numeric": "084" + 'name': 'Belize', + 'alpha2': 'BZ', + 'alpha3': 'BLZ', + 'numeric': '084' }, { - "name": "Benin", - "alpha2": "BJ", - "alpha3": "BEN", - "numeric": "204" + 'name': 'Benin', + 'alpha2': 'BJ', + 'alpha3': 'BEN', + 'numeric': '204' }, { - "name": "Bermuda", - "alpha2": "BM", - "alpha3": "BMU", - "numeric": "060" + 'name': 'Bermuda', + 'alpha2': 'BM', + 'alpha3': 'BMU', + 'numeric': '060' }, { - "name": "Bhutan", - "alpha2": "BT", - "alpha3": "BTN", - "numeric": "064" + 'name': 'Bhutan', + 'alpha2': 'BT', + 'alpha3': 'BTN', + 'numeric': '064' }, { - "name": "Bolivia (Plurinational State of)", - "alpha2": "BO", - "alpha3": "BOL", - "numeric": "068", - "altName": "Bolivia" + 'name': 'Bolivia (Plurinational State of)', + 'alpha2': 'BO', + 'alpha3': 'BOL', + 'numeric': '068', + 'altName': 'Bolivia' }, { - "name": "Bonaire, Sint Eustatius and Saba", - "alpha2": "BQ", - "alpha3": "BES", - "numeric": "535" + 'name': 'Bonaire, Sint Eustatius and Saba', + 'alpha2': 'BQ', + 'alpha3': 'BES', + 'numeric': '535' }, { - "name": "Bosnia and Herzegovina", - "alpha2": "BA", - "alpha3": "BIH", - "numeric": "070" + 'name': 'Bosnia and Herzegovina', + 'alpha2': 'BA', + 'alpha3': 'BIH', + 'numeric': '070' }, { - "name": "Botswana", - "alpha2": "BW", - "alpha3": "BWA", - "numeric": "072" + 'name': 'Botswana', + 'alpha2': 'BW', + 'alpha3': 'BWA', + 'numeric': '072' }, { - "name": "Bouvet Island", - "alpha2": "BV", - "alpha3": "BVT", - "numeric": "074" + 'name': 'Bouvet Island', + 'alpha2': 'BV', + 'alpha3': 'BVT', + 'numeric': '074' }, { - "name": "Brazil", - "alpha2": "BR", - "alpha3": "BRA", - "numeric": "076" + 'name': 'Brazil', + 'alpha2': 'BR', + 'alpha3': 'BRA', + 'numeric': '076' }, { - "name": "British Indian Ocean Territory (the)", - "alpha2": "IO", - "alpha3": "IOT", - "numeric": "086", - "altName": "British Indian Ocean Territory" + 'name': 'British Indian Ocean Territory (the)', + 'alpha2': 'IO', + 'alpha3': 'IOT', + 'numeric': '086', + 'altName': 'British Indian Ocean Territory' }, { - "name": "Brunei Darussalam", - "alpha2": "BN", - "alpha3": "BRN", - "numeric": "096", - "shortName": "Brunei" + 'name': 'Brunei Darussalam', + 'alpha2': 'BN', + 'alpha3': 'BRN', + 'numeric': '096', + 'shortName': 'Brunei' }, { - "name": "Bulgaria", - "alpha2": "BG", - "alpha3": "BGR", - "numeric": "100" + 'name': 'Bulgaria', + 'alpha2': 'BG', + 'alpha3': 'BGR', + 'numeric': '100' }, { - "name": "Burkina Faso", - "alpha2": "BF", - "alpha3": "BFA", - "numeric": "854" + 'name': 'Burkina Faso', + 'alpha2': 'BF', + 'alpha3': 'BFA', + 'numeric': '854' }, { - "name": "Burundi", - "alpha2": "BI", - "alpha3": "BDI", - "numeric": "108" + 'name': 'Burundi', + 'alpha2': 'BI', + 'alpha3': 'BDI', + 'numeric': '108' }, { - "name": "Cabo Verde", - "alpha2": "CV", - "alpha3": "CPV", - "numeric": "132", - "altName": "Cape Verde" + 'name': 'Cabo Verde', + 'alpha2': 'CV', + 'alpha3': 'CPV', + 'numeric': '132', + 'altName': 'Cape Verde' }, { - "name": "Cambodia", - "alpha2": "KH", - "alpha3": "KHM", - "numeric": "116" + 'name': 'Cambodia', + 'alpha2': 'KH', + 'alpha3': 'KHM', + 'numeric': '116' }, { - "name": "Cameroon", - "alpha2": "CM", - "alpha3": "CMR", - "numeric": "120" + 'name': 'Cameroon', + 'alpha2': 'CM', + 'alpha3': 'CMR', + 'numeric': '120' }, { - "name": "Canada", - "alpha2": "CA", - "alpha3": "CAN", - "numeric": "124" + 'name': 'Canada', + 'alpha2': 'CA', + 'alpha3': 'CAN', + 'numeric': '124' }, { - "name": "Cayman Islands (the)", - "alpha2": "KY", - "alpha3": "CYM", - "numeric": "136", - "altName": "Cayman Islands" + 'name': 'Cayman Islands (the)', + 'alpha2': 'KY', + 'alpha3': 'CYM', + 'numeric': '136', + 'altName': 'Cayman Islands' }, { - "name": "Central African Republic (the)", - "alpha2": "CF", - "alpha3": "CAF", - "numeric": "140", - "altName": "Central African Republic" + 'name': 'Central African Republic (the)', + 'alpha2': 'CF', + 'alpha3': 'CAF', + 'numeric': '140', + 'altName': 'Central African Republic' }, { - "name": "Chad", - "alpha2": "TD", - "alpha3": "TCD", - "numeric": "148" + 'name': 'Chad', + 'alpha2': 'TD', + 'alpha3': 'TCD', + 'numeric': '148' }, { - "name": "Chile", - "alpha2": "CL", - "alpha3": "CHL", - "numeric": "152" + 'name': 'Chile', + 'alpha2': 'CL', + 'alpha3': 'CHL', + 'numeric': '152' }, { - "name": "China", - "alpha2": "CN", - "alpha3": "CHN", - "numeric": "156" + 'name': 'China', + 'alpha2': 'CN', + 'alpha3': 'CHN', + 'numeric': '156' }, { - "name": "Christmas Island", - "alpha2": "CX", - "alpha3": "CXR", - "numeric": "162" + 'name': 'Christmas Island', + 'alpha2': 'CX', + 'alpha3': 'CXR', + 'numeric': '162' }, { - "name": "Cocos (Keeling) Islands (the)", - "alpha2": "CC", - "alpha3": "CCK", - "numeric": "166", - "altName": "Cocos (Keeling) Islands", - "shortName": "Cocos Islands" + 'name': 'Cocos (Keeling) Islands (the)', + 'alpha2': 'CC', + 'alpha3': 'CCK', + 'numeric': '166', + 'altName': 'Cocos (Keeling) Islands', + 'shortName': 'Cocos Islands' }, { - "name": "Colombia", - "alpha2": "CO", - "alpha3": "COL", - "numeric": "170" + 'name': 'Colombia', + 'alpha2': 'CO', + 'alpha3': 'COL', + 'numeric': '170' }, { - "name": "Comoros (the)", - "alpha2": "KM", - "alpha3": "COM", - "numeric": "174", - "altName": "Comoros" + 'name': 'Comoros (the)', + 'alpha2': 'KM', + 'alpha3': 'COM', + 'numeric': '174', + 'altName': 'Comoros' }, { - "name": "Congo (the Democratic Republic of the)", - "alpha2": "CD", - "alpha3": "COD", - "numeric": "180", - "altName": "Congo, (Kinshasa)", - "shortName": "Democratic Republic of the Congo" + 'name': 'Congo (the Democratic Republic of the)', + 'alpha2': 'CD', + 'alpha3': 'COD', + 'numeric': '180', + 'altName': 'Congo, (Kinshasa)', + 'shortName': 'Democratic Republic of the Congo' }, { - "name": "Congo (the)", - "alpha2": "CG", - "alpha3": "COG", - "numeric": "178", - "altName": "Congo (Brazzaville)", - "shortName": "Republic of the Congo" + 'name': 'Congo (the)', + 'alpha2': 'CG', + 'alpha3': 'COG', + 'numeric': '178', + 'altName': 'Congo (Brazzaville)', + 'shortName': 'Republic of the Congo' }, { - "name": "Cook Islands (the)", - "alpha2": "CK", - "alpha3": "COK", - "numeric": "184", - "altName": "Cook Islands" + 'name': 'Cook Islands (the)', + 'alpha2': 'CK', + 'alpha3': 'COK', + 'numeric': '184', + 'altName': 'Cook Islands' }, { - "name": "Costa Rica", - "alpha2": "CR", - "alpha3": "CRI", - "numeric": "188" + 'name': 'Costa Rica', + 'alpha2': 'CR', + 'alpha3': 'CRI', + 'numeric': '188' }, { - "name": "Côte d'Ivoire", - "alpha2": "CI", - "alpha3": "CIV", - "numeric": "384", - "shortName": "Ivory Coast" + 'name': 'Côte d\'Ivoire', + 'alpha2': 'CI', + 'alpha3': 'CIV', + 'numeric': '384', + 'shortName': 'Ivory Coast' }, { - "name": "Croatia", - "alpha2": "HR", - "alpha3": "HRV", - "numeric": "191" + 'name': 'Croatia', + 'alpha2': 'HR', + 'alpha3': 'HRV', + 'numeric': '191' }, { - "name": "Cuba", - "alpha2": "CU", - "alpha3": "CUB", - "numeric": "192" + 'name': 'Cuba', + 'alpha2': 'CU', + 'alpha3': 'CUB', + 'numeric': '192' }, { - "name": "Curaçao", - "alpha2": "CW", - "alpha3": "CUW", - "numeric": "531", - "shortName": "Curacao" + 'name': 'Curaçao', + 'alpha2': 'CW', + 'alpha3': 'CUW', + 'numeric': '531', + 'shortName': 'Curacao' }, { - "name": "Cyprus", - "alpha2": "CY", - "alpha3": "CYP", - "numeric": "196" + 'name': 'Cyprus', + 'alpha2': 'CY', + 'alpha3': 'CYP', + 'numeric': '196' }, { - "name": "Czechia", - "alpha2": "CZ", - "alpha3": "CZE", - "numeric": "203", - "altName": "Czech Republic" + 'name': 'Czechia', + 'alpha2': 'CZ', + 'alpha3': 'CZE', + 'numeric': '203', + 'altName': 'Czech Republic' }, { - "name": "Denmark", - "alpha2": "DK", - "alpha3": "DNK", - "numeric": "208" + 'name': 'Denmark', + 'alpha2': 'DK', + 'alpha3': 'DNK', + 'numeric': '208' }, { - "name": "Djibouti", - "alpha2": "DJ", - "alpha3": "DJI", - "numeric": "262" + 'name': 'Djibouti', + 'alpha2': 'DJ', + 'alpha3': 'DJI', + 'numeric': '262' }, { - "name": "Dominica", - "alpha2": "DM", - "alpha3": "DMA", - "numeric": "212" + 'name': 'Dominica', + 'alpha2': 'DM', + 'alpha3': 'DMA', + 'numeric': '212' }, { - "name": "Dominican Republic (the)", - "alpha2": "DO", - "alpha3": "DOM", - "numeric": "214", - "altName": "Dominican Republic" + 'name': 'Dominican Republic (the)', + 'alpha2': 'DO', + 'alpha3': 'DOM', + 'numeric': '214', + 'altName': 'Dominican Republic' }, { - "name": "Ecuador", - "alpha2": "EC", - "alpha3": "ECU", - "numeric": "218" + 'name': 'Ecuador', + 'alpha2': 'EC', + 'alpha3': 'ECU', + 'numeric': '218' }, { - "name": "Egypt", - "alpha2": "EG", - "alpha3": "EGY", - "numeric": "818" + 'name': 'Egypt', + 'alpha2': 'EG', + 'alpha3': 'EGY', + 'numeric': '818' }, { - "name": "El Salvador", - "alpha2": "SV", - "alpha3": "SLV", - "numeric": "222" + 'name': 'El Salvador', + 'alpha2': 'SV', + 'alpha3': 'SLV', + 'numeric': '222' }, { - "name": "Equatorial Guinea", - "alpha2": "GQ", - "alpha3": "GNQ", - "numeric": "226" + 'name': 'Equatorial Guinea', + 'alpha2': 'GQ', + 'alpha3': 'GNQ', + 'numeric': '226' }, { - "name": "Eritrea", - "alpha2": "ER", - "alpha3": "ERI", - "numeric": "232" + 'name': 'Eritrea', + 'alpha2': 'ER', + 'alpha3': 'ERI', + 'numeric': '232' }, { - "name": "Estonia", - "alpha2": "EE", - "alpha3": "EST", - "numeric": "233" + 'name': 'Estonia', + 'alpha2': 'EE', + 'alpha3': 'EST', + 'numeric': '233' }, { - "name": "Ethiopia", - "alpha2": "ET", - "alpha3": "ETH", - "numeric": "231" + 'name': 'Ethiopia', + 'alpha2': 'ET', + 'alpha3': 'ETH', + 'numeric': '231' }, { - "name": "Falkland Islands (the) [Malvinas]", - "alpha2": "FK", - "alpha3": "FLK", - "numeric": "238", - "altName": "Falkland Islands (Malvinas)", - "shortName": "Falkland Islands" + 'name': 'Falkland Islands (the) [Malvinas]', + 'alpha2': 'FK', + 'alpha3': 'FLK', + 'numeric': '238', + 'altName': 'Falkland Islands (Malvinas)', + 'shortName': 'Falkland Islands' }, { - "name": "Faroe Islands (the)", - "alpha2": "FO", - "alpha3": "FRO", - "numeric": "234", - "altName": "Faroe Islands" + 'name': 'Faroe Islands (the)', + 'alpha2': 'FO', + 'alpha3': 'FRO', + 'numeric': '234', + 'altName': 'Faroe Islands' }, { - "name": "Fiji", - "alpha2": "FJ", - "alpha3": "FJI", - "numeric": "242" + 'name': 'Fiji', + 'alpha2': 'FJ', + 'alpha3': 'FJI', + 'numeric': '242' }, { - "name": "Finland", - "alpha2": "FI", - "alpha3": "FIN", - "numeric": "246" + 'name': 'Finland', + 'alpha2': 'FI', + 'alpha3': 'FIN', + 'numeric': '246' }, { - "name": "France", - "alpha2": "FR", - "alpha3": "FRA", - "numeric": "250" + 'name': 'France', + 'alpha2': 'FR', + 'alpha3': 'FRA', + 'numeric': '250' }, { - "name": "French Guiana", - "alpha2": "GF", - "alpha3": "GUF", - "numeric": "254" + 'name': 'French Guiana', + 'alpha2': 'GF', + 'alpha3': 'GUF', + 'numeric': '254' }, { - "name": "French Polynesia", - "alpha2": "PF", - "alpha3": "PYF", - "numeric": "258" + 'name': 'French Polynesia', + 'alpha2': 'PF', + 'alpha3': 'PYF', + 'numeric': '258' }, { - "name": "French Southern Territories (the)", - "alpha2": "TF", - "alpha3": "ATF", - "numeric": "260", - "altName": "French Southern Territories" + 'name': 'French Southern Territories (the)', + 'alpha2': 'TF', + 'alpha3': 'ATF', + 'numeric': '260', + 'altName': 'French Southern Territories' }, { - "name": "Gabon", - "alpha2": "GA", - "alpha3": "GAB", - "numeric": "266" + 'name': 'Gabon', + 'alpha2': 'GA', + 'alpha3': 'GAB', + 'numeric': '266' }, { - "name": "Gambia (the)", - "alpha2": "GM", - "alpha3": "GMB", - "numeric": "270", - "altName": "Gambia" + 'name': 'Gambia (the)', + 'alpha2': 'GM', + 'alpha3': 'GMB', + 'numeric': '270', + 'altName': 'Gambia' }, { - "name": "Georgia", - "alpha2": "GE", - "alpha3": "GEO", - "numeric": "268" + 'name': 'Georgia', + 'alpha2': 'GE', + 'alpha3': 'GEO', + 'numeric': '268' }, { - "name": "Germany", - "alpha2": "DE", - "alpha3": "DEU", - "numeric": "276" + 'name': 'Germany', + 'alpha2': 'DE', + 'alpha3': 'DEU', + 'numeric': '276' }, { - "name": "Ghana", - "alpha2": "GH", - "alpha3": "GHA", - "numeric": "288" + 'name': 'Ghana', + 'alpha2': 'GH', + 'alpha3': 'GHA', + 'numeric': '288' }, { - "name": "Gibraltar", - "alpha2": "GI", - "alpha3": "GIB", - "numeric": "292" + 'name': 'Gibraltar', + 'alpha2': 'GI', + 'alpha3': 'GIB', + 'numeric': '292' }, { - "name": "Greece", - "alpha2": "GR", - "alpha3": "GRC", - "numeric": "300" + 'name': 'Greece', + 'alpha2': 'GR', + 'alpha3': 'GRC', + 'numeric': '300' }, { - "name": "Greenland", - "alpha2": "GL", - "alpha3": "GRL", - "numeric": "304" + 'name': 'Greenland', + 'alpha2': 'GL', + 'alpha3': 'GRL', + 'numeric': '304' }, { - "name": "Grenada", - "alpha2": "GD", - "alpha3": "GRD", - "numeric": "308" + 'name': 'Grenada', + 'alpha2': 'GD', + 'alpha3': 'GRD', + 'numeric': '308' }, { - "name": "Guadeloupe", - "alpha2": "GP", - "alpha3": "GLP", - "numeric": "312" + 'name': 'Guadeloupe', + 'alpha2': 'GP', + 'alpha3': 'GLP', + 'numeric': '312' }, { - "name": "Guam", - "alpha2": "GU", - "alpha3": "GUM", - "numeric": "316" + 'name': 'Guam', + 'alpha2': 'GU', + 'alpha3': 'GUM', + 'numeric': '316' }, { - "name": "Guatemala", - "alpha2": "GT", - "alpha3": "GTM", - "numeric": "320" + 'name': 'Guatemala', + 'alpha2': 'GT', + 'alpha3': 'GTM', + 'numeric': '320' }, { - "name": "Guernsey", - "alpha2": "GG", - "alpha3": "GGY", - "numeric": "831" + 'name': 'Guernsey', + 'alpha2': 'GG', + 'alpha3': 'GGY', + 'numeric': '831' }, { - "name": "Guinea", - "alpha2": "GN", - "alpha3": "GIN", - "numeric": "324" + 'name': 'Guinea', + 'alpha2': 'GN', + 'alpha3': 'GIN', + 'numeric': '324' }, { - "name": "Guinea-Bissau", - "alpha2": "GW", - "alpha3": "GNB", - "numeric": "624" + 'name': 'Guinea-Bissau', + 'alpha2': 'GW', + 'alpha3': 'GNB', + 'numeric': '624' }, { - "name": "Guyana", - "alpha2": "GY", - "alpha3": "GUY", - "numeric": "328" + 'name': 'Guyana', + 'alpha2': 'GY', + 'alpha3': 'GUY', + 'numeric': '328' }, { - "name": "Haiti", - "alpha2": "HT", - "alpha3": "HTI", - "numeric": "332" + 'name': 'Haiti', + 'alpha2': 'HT', + 'alpha3': 'HTI', + 'numeric': '332' }, { - "name": "Heard Island and McDonald Islands", - "alpha2": "HM", - "alpha3": "HMD", - "numeric": "334", - "altName": "Heard and Mcdonald Islands" + 'name': 'Heard Island and McDonald Islands', + 'alpha2': 'HM', + 'alpha3': 'HMD', + 'numeric': '334', + 'altName': 'Heard and Mcdonald Islands' }, { - "name": "Holy See (the)", - "alpha2": "VA", - "alpha3": "VAT", - "numeric": "336", - "altName": "Holy See (Vatican City State)", - "shortName": "Vatican" + 'name': 'Holy See (the)', + 'alpha2': 'VA', + 'alpha3': 'VAT', + 'numeric': '336', + 'altName': 'Holy See (Vatican City State)', + 'shortName': 'Vatican' }, { - "name": "Honduras", - "alpha2": "HN", - "alpha3": "HND", - "numeric": "340" + 'name': 'Honduras', + 'alpha2': 'HN', + 'alpha3': 'HND', + 'numeric': '340' }, { - "name": "Hong Kong", - "alpha2": "HK", - "alpha3": "HKG", - "numeric": "344", - "altName": "Hong Kong, SAR China" + 'name': 'Hong Kong', + 'alpha2': 'HK', + 'alpha3': 'HKG', + 'numeric': '344', + 'altName': 'Hong Kong, SAR China' }, { - "name": "Hungary", - "alpha2": "HU", - "alpha3": "HUN", - "numeric": "348" + 'name': 'Hungary', + 'alpha2': 'HU', + 'alpha3': 'HUN', + 'numeric': '348' }, { - "name": "Iceland", - "alpha2": "IS", - "alpha3": "ISL", - "numeric": "352" + 'name': 'Iceland', + 'alpha2': 'IS', + 'alpha3': 'ISL', + 'numeric': '352' }, { - "name": "India", - "alpha2": "IN", - "alpha3": "IND", - "numeric": "356" + 'name': 'India', + 'alpha2': 'IN', + 'alpha3': 'IND', + 'numeric': '356' }, { - "name": "Indonesia", - "alpha2": "ID", - "alpha3": "IDN", - "numeric": "360" + 'name': 'Indonesia', + 'alpha2': 'ID', + 'alpha3': 'IDN', + 'numeric': '360' }, { - "name": "Iran (Islamic Republic of)", - "alpha2": "IR", - "alpha3": "IRN", - "numeric": "364", - "altName": "Iran, Islamic Republic of", - "shortName": "Iran" + 'name': 'Iran (Islamic Republic of)', + 'alpha2': 'IR', + 'alpha3': 'IRN', + 'numeric': '364', + 'altName': 'Iran, Islamic Republic of', + 'shortName': 'Iran' }, { - "name": "Iraq", - "alpha2": "IQ", - "alpha3": "IRQ", - "numeric": "368" + 'name': 'Iraq', + 'alpha2': 'IQ', + 'alpha3': 'IRQ', + 'numeric': '368' }, { - "name": "Ireland", - "alpha2": "IE", - "alpha3": "IRL", - "numeric": "372" + 'name': 'Ireland', + 'alpha2': 'IE', + 'alpha3': 'IRL', + 'numeric': '372' }, { - "name": "Isle of Man", - "alpha2": "IM", - "alpha3": "IMN", - "numeric": "833" + 'name': 'Isle of Man', + 'alpha2': 'IM', + 'alpha3': 'IMN', + 'numeric': '833' }, { - "name": "Israel", - "alpha2": "IL", - "alpha3": "ISR", - "numeric": "376" + 'name': 'Israel', + 'alpha2': 'IL', + 'alpha3': 'ISR', + 'numeric': '376' }, { - "name": "Italy", - "alpha2": "IT", - "alpha3": "ITA", - "numeric": "380" + 'name': 'Italy', + 'alpha2': 'IT', + 'alpha3': 'ITA', + 'numeric': '380' }, { - "name": "Jamaica", - "alpha2": "JM", - "alpha3": "JAM", - "numeric": "388" + 'name': 'Jamaica', + 'alpha2': 'JM', + 'alpha3': 'JAM', + 'numeric': '388' }, { - "name": "Japan", - "alpha2": "JP", - "alpha3": "JPN", - "numeric": "392" + 'name': 'Japan', + 'alpha2': 'JP', + 'alpha3': 'JPN', + 'numeric': '392' }, { - "name": "Jersey", - "alpha2": "JE", - "alpha3": "JEY", - "numeric": "832" + 'name': 'Jersey', + 'alpha2': 'JE', + 'alpha3': 'JEY', + 'numeric': '832' }, { - "name": "Jordan", - "alpha2": "JO", - "alpha3": "JOR", - "numeric": "400" + 'name': 'Jordan', + 'alpha2': 'JO', + 'alpha3': 'JOR', + 'numeric': '400' }, { - "name": "Kazakhstan", - "alpha2": "KZ", - "alpha3": "KAZ", - "numeric": "398" + 'name': 'Kazakhstan', + 'alpha2': 'KZ', + 'alpha3': 'KAZ', + 'numeric': '398' }, { - "name": "Kenya", - "alpha2": "KE", - "alpha3": "KEN", - "numeric": "404" + 'name': 'Kenya', + 'alpha2': 'KE', + 'alpha3': 'KEN', + 'numeric': '404' }, { - "name": "Kiribati", - "alpha2": "KI", - "alpha3": "KIR", - "numeric": "296" + 'name': 'Kiribati', + 'alpha2': 'KI', + 'alpha3': 'KIR', + 'numeric': '296' }, { - "name": "Korea (the Democratic People's Republic of)", - "alpha2": "KP", - "alpha3": "PRK", - "numeric": "408", - "altName": "Korea (North)", - "shortName": "North Korea" + 'name': 'Korea (the Democratic People\'s Republic of)', + 'alpha2': 'KP', + 'alpha3': 'PRK', + 'numeric': '408', + 'altName': 'Korea (North)', + 'shortName': 'North Korea' }, { - "name": "Korea (the Republic of)", - "alpha2": "KR", - "alpha3": "KOR", - "numeric": "410", - "altName": "Korea (South)", - "shortName": "South Korea" + 'name': 'Korea (the Republic of)', + 'alpha2': 'KR', + 'alpha3': 'KOR', + 'numeric': '410', + 'altName': 'Korea (South)', + 'shortName': 'South Korea' }, { - "name": "Kuwait", - "alpha2": "KW", - "alpha3": "KWT", - "numeric": "414" + 'name': 'Kuwait', + 'alpha2': 'KW', + 'alpha3': 'KWT', + 'numeric': '414' }, { - "name": "Kyrgyzstan", - "alpha2": "KG", - "alpha3": "KGZ", - "numeric": "417" + 'name': 'Kyrgyzstan', + 'alpha2': 'KG', + 'alpha3': 'KGZ', + 'numeric': '417' }, { - "name": "Lao People's Democratic Republic (the)", - "alpha2": "LA", - "alpha3": "LAO", - "numeric": "418", - "altName": "Lao PDR", - "shortName": "Laos" + 'name': 'Lao People\'s Democratic Republic (the)', + 'alpha2': 'LA', + 'alpha3': 'LAO', + 'numeric': '418', + 'altName': 'Lao PDR', + 'shortName': 'Laos' }, { - "name": "Latvia", - "alpha2": "LV", - "alpha3": "LVA", - "numeric": "428" + 'name': 'Latvia', + 'alpha2': 'LV', + 'alpha3': 'LVA', + 'numeric': '428' }, { - "name": "Lebanon", - "alpha2": "LB", - "alpha3": "LBN", - "numeric": "422" + 'name': 'Lebanon', + 'alpha2': 'LB', + 'alpha3': 'LBN', + 'numeric': '422' }, { - "name": "Lesotho", - "alpha2": "LS", - "alpha3": "LSO", - "numeric": "426" + 'name': 'Lesotho', + 'alpha2': 'LS', + 'alpha3': 'LSO', + 'numeric': '426' }, { - "name": "Liberia", - "alpha2": "LR", - "alpha3": "LBR", - "numeric": "430" + 'name': 'Liberia', + 'alpha2': 'LR', + 'alpha3': 'LBR', + 'numeric': '430' }, { - "name": "Libya", - "alpha2": "LY", - "alpha3": "LBY", - "numeric": "434" + 'name': 'Libya', + 'alpha2': 'LY', + 'alpha3': 'LBY', + 'numeric': '434' }, { - "name": "Liechtenstein", - "alpha2": "LI", - "alpha3": "LIE", - "numeric": "438" + 'name': 'Liechtenstein', + 'alpha2': 'LI', + 'alpha3': 'LIE', + 'numeric': '438' }, { - "name": "Lithuania", - "alpha2": "LT", - "alpha3": "LTU", - "numeric": "440" + 'name': 'Lithuania', + 'alpha2': 'LT', + 'alpha3': 'LTU', + 'numeric': '440' }, { - "name": "Luxembourg", - "alpha2": "LU", - "alpha3": "LUX", - "numeric": "442" + 'name': 'Luxembourg', + 'alpha2': 'LU', + 'alpha3': 'LUX', + 'numeric': '442' }, { - "name": "Macao", - "alpha2": "MO", - "alpha3": "MAC", - "numeric": "446", - "altName": "Macao, SAR China", - "shortName": "Macau" + 'name': 'Macao', + 'alpha2': 'MO', + 'alpha3': 'MAC', + 'numeric': '446', + 'altName': 'Macao, SAR China', + 'shortName': 'Macau' }, { - "name": "Macedonia (the former Yugoslav Republic of)", - "alpha2": "MK", - "alpha3": "MKD", - "numeric": "807", - "altName": "Macedonia, Republic of", - "shortName": "Macedonia" + 'name': 'Macedonia (the former Yugoslav Republic of)', + 'alpha2': 'MK', + 'alpha3': 'MKD', + 'numeric': '807', + 'altName': 'Macedonia, Republic of', + 'shortName': 'Macedonia' }, { - "name": "Madagascar", - "alpha2": "MG", - "alpha3": "MDG", - "numeric": "450" + 'name': 'Madagascar', + 'alpha2': 'MG', + 'alpha3': 'MDG', + 'numeric': '450' }, { - "name": "Malawi", - "alpha2": "MW", - "alpha3": "MWI", - "numeric": "454" + 'name': 'Malawi', + 'alpha2': 'MW', + 'alpha3': 'MWI', + 'numeric': '454' }, { - "name": "Malaysia", - "alpha2": "MY", - "alpha3": "MYS", - "numeric": "458" + 'name': 'Malaysia', + 'alpha2': 'MY', + 'alpha3': 'MYS', + 'numeric': '458' }, { - "name": "Maldives", - "alpha2": "MV", - "alpha3": "MDV", - "numeric": "462" + 'name': 'Maldives', + 'alpha2': 'MV', + 'alpha3': 'MDV', + 'numeric': '462' }, { - "name": "Mali", - "alpha2": "ML", - "alpha3": "MLI", - "numeric": "466" + 'name': 'Mali', + 'alpha2': 'ML', + 'alpha3': 'MLI', + 'numeric': '466' }, { - "name": "Malta", - "alpha2": "MT", - "alpha3": "MLT", - "numeric": "470" + 'name': 'Malta', + 'alpha2': 'MT', + 'alpha3': 'MLT', + 'numeric': '470' }, { - "name": "Marshall Islands (the)", - "alpha2": "MH", - "alpha3": "MHL", - "numeric": "584", - "altName": "Marshall Islands" + 'name': 'Marshall Islands (the)', + 'alpha2': 'MH', + 'alpha3': 'MHL', + 'numeric': '584', + 'altName': 'Marshall Islands' }, { - "name": "Martinique", - "alpha2": "MQ", - "alpha3": "MTQ", - "numeric": "474" + 'name': 'Martinique', + 'alpha2': 'MQ', + 'alpha3': 'MTQ', + 'numeric': '474' }, { - "name": "Mauritania", - "alpha2": "MR", - "alpha3": "MRT", - "numeric": "478" + 'name': 'Mauritania', + 'alpha2': 'MR', + 'alpha3': 'MRT', + 'numeric': '478' }, { - "name": "Mauritius", - "alpha2": "MU", - "alpha3": "MUS", - "numeric": "480" + 'name': 'Mauritius', + 'alpha2': 'MU', + 'alpha3': 'MUS', + 'numeric': '480' }, { - "name": "Mayotte", - "alpha2": "YT", - "alpha3": "MYT", - "numeric": "175" + 'name': 'Mayotte', + 'alpha2': 'YT', + 'alpha3': 'MYT', + 'numeric': '175' }, { - "name": "Mexico", - "alpha2": "MX", - "alpha3": "MEX", - "numeric": "484" + 'name': 'Mexico', + 'alpha2': 'MX', + 'alpha3': 'MEX', + 'numeric': '484' }, { - "name": "Micronesia (Federated States of)", - "alpha2": "FM", - "alpha3": "FSM", - "numeric": "583", - "altName": "Micronesia, Federated States of", - "shortName": "Micronesia" + 'name': 'Micronesia (Federated States of)', + 'alpha2': 'FM', + 'alpha3': 'FSM', + 'numeric': '583', + 'altName': 'Micronesia, Federated States of', + 'shortName': 'Micronesia' }, { - "name": "Moldova (the Republic of)", - "alpha2": "MD", - "alpha3": "MDA", - "numeric": "498", - "altName": "Moldova" + 'name': 'Moldova (the Republic of)', + 'alpha2': 'MD', + 'alpha3': 'MDA', + 'numeric': '498', + 'altName': 'Moldova' }, { - "name": "Monaco", - "alpha2": "MC", - "alpha3": "MCO", - "numeric": "492" + 'name': 'Monaco', + 'alpha2': 'MC', + 'alpha3': 'MCO', + 'numeric': '492' }, { - "name": "Mongolia", - "alpha2": "MN", - "alpha3": "MNG", - "numeric": "496" + 'name': 'Mongolia', + 'alpha2': 'MN', + 'alpha3': 'MNG', + 'numeric': '496' }, { - "name": "Montenegro", - "alpha2": "ME", - "alpha3": "MNE", - "numeric": "499" + 'name': 'Montenegro', + 'alpha2': 'ME', + 'alpha3': 'MNE', + 'numeric': '499' }, { - "name": "Montserrat", - "alpha2": "MS", - "alpha3": "MSR", - "numeric": "500" + 'name': 'Montserrat', + 'alpha2': 'MS', + 'alpha3': 'MSR', + 'numeric': '500' }, { - "name": "Morocco", - "alpha2": "MA", - "alpha3": "MAR", - "numeric": "504" + 'name': 'Morocco', + 'alpha2': 'MA', + 'alpha3': 'MAR', + 'numeric': '504' }, { - "name": "Mozambique", - "alpha2": "MZ", - "alpha3": "MOZ", - "numeric": "508" + 'name': 'Mozambique', + 'alpha2': 'MZ', + 'alpha3': 'MOZ', + 'numeric': '508' }, { - "name": "Myanmar", - "alpha2": "MM", - "alpha3": "MMR", - "numeric": "104" + 'name': 'Myanmar', + 'alpha2': 'MM', + 'alpha3': 'MMR', + 'numeric': '104' }, { - "name": "Namibia", - "alpha2": "NA", - "alpha3": "NAM", - "numeric": "516" + 'name': 'Namibia', + 'alpha2': 'NA', + 'alpha3': 'NAM', + 'numeric': '516' }, { - "name": "Nauru", - "alpha2": "NR", - "alpha3": "NRU", - "numeric": "520" + 'name': 'Nauru', + 'alpha2': 'NR', + 'alpha3': 'NRU', + 'numeric': '520' }, { - "name": "Nepal", - "alpha2": "NP", - "alpha3": "NPL", - "numeric": "524" + 'name': 'Nepal', + 'alpha2': 'NP', + 'alpha3': 'NPL', + 'numeric': '524' }, { - "name": "Netherlands (the)", - "alpha2": "NL", - "alpha3": "NLD", - "numeric": "528", - "altName": "Netherlands" + 'name': 'Netherlands (the)', + 'alpha2': 'NL', + 'alpha3': 'NLD', + 'numeric': '528', + 'altName': 'Netherlands' }, { - "name": "New Caledonia", - "alpha2": "NC", - "alpha3": "NCL", - "numeric": "540" + 'name': 'New Caledonia', + 'alpha2': 'NC', + 'alpha3': 'NCL', + 'numeric': '540' }, { - "name": "New Zealand", - "alpha2": "NZ", - "alpha3": "NZL", - "numeric": "554" + 'name': 'New Zealand', + 'alpha2': 'NZ', + 'alpha3': 'NZL', + 'numeric': '554' }, { - "name": "Nicaragua", - "alpha2": "NI", - "alpha3": "NIC", - "numeric": "558" + 'name': 'Nicaragua', + 'alpha2': 'NI', + 'alpha3': 'NIC', + 'numeric': '558' }, { - "name": "Niger (the)", - "alpha2": "NE", - "alpha3": "NER", - "numeric": "562", - "altName": "Niger" + 'name': 'Niger (the)', + 'alpha2': 'NE', + 'alpha3': 'NER', + 'numeric': '562', + 'altName': 'Niger' }, { - "name": "Nigeria", - "alpha2": "NG", - "alpha3": "NGA", - "numeric": "566" + 'name': 'Nigeria', + 'alpha2': 'NG', + 'alpha3': 'NGA', + 'numeric': '566' }, { - "name": "Niue", - "alpha2": "NU", - "alpha3": "NIU", - "numeric": "570" + 'name': 'Niue', + 'alpha2': 'NU', + 'alpha3': 'NIU', + 'numeric': '570' }, { - "name": "Norfolk Island", - "alpha2": "NF", - "alpha3": "NFK", - "numeric": "574" + 'name': 'Norfolk Island', + 'alpha2': 'NF', + 'alpha3': 'NFK', + 'numeric': '574' }, { - "name": "Northern Mariana Islands (the)", - "alpha2": "MP", - "alpha3": "MNP", - "numeric": "580", - "altName": "Northern Mariana Islands" + 'name': 'Northern Mariana Islands (the)', + 'alpha2': 'MP', + 'alpha3': 'MNP', + 'numeric': '580', + 'altName': 'Northern Mariana Islands' }, { - "name": "Norway", - "alpha2": "NO", - "alpha3": "NOR", - "numeric": "578" + 'name': 'Norway', + 'alpha2': 'NO', + 'alpha3': 'NOR', + 'numeric': '578' }, { - "name": "Oman", - "alpha2": "OM", - "alpha3": "OMN", - "numeric": "512" + 'name': 'Oman', + 'alpha2': 'OM', + 'alpha3': 'OMN', + 'numeric': '512' }, { - "name": "Pakistan", - "alpha2": "PK", - "alpha3": "PAK", - "numeric": "586" + 'name': 'Pakistan', + 'alpha2': 'PK', + 'alpha3': 'PAK', + 'numeric': '586' }, { - "name": "Palau", - "alpha2": "PW", - "alpha3": "PLW", - "numeric": "585" + 'name': 'Palau', + 'alpha2': 'PW', + 'alpha3': 'PLW', + 'numeric': '585' }, { - "name": "Palestine, State of", - "alpha2": "PS", - "alpha3": "PSE", - "numeric": "275", - "altName": "Palestinian Territory", - "shortName": "Palestine" + 'name': 'Palestine, State of', + 'alpha2': 'PS', + 'alpha3': 'PSE', + 'numeric': '275', + 'altName': 'Palestinian Territory', + 'shortName': 'Palestine' }, { - "name": "Panama", - "alpha2": "PA", - "alpha3": "PAN", - "numeric": "591" + 'name': 'Panama', + 'alpha2': 'PA', + 'alpha3': 'PAN', + 'numeric': '591' }, { - "name": "Papua New Guinea", - "alpha2": "PG", - "alpha3": "PNG", - "numeric": "598" + 'name': 'Papua New Guinea', + 'alpha2': 'PG', + 'alpha3': 'PNG', + 'numeric': '598' }, { - "name": "Paraguay", - "alpha2": "PY", - "alpha3": "PRY", - "numeric": "600" + 'name': 'Paraguay', + 'alpha2': 'PY', + 'alpha3': 'PRY', + 'numeric': '600' }, { - "name": "Peru", - "alpha2": "PE", - "alpha3": "PER", - "numeric": "604" + 'name': 'Peru', + 'alpha2': 'PE', + 'alpha3': 'PER', + 'numeric': '604' }, { - "name": "Philippines (the)", - "alpha2": "PH", - "alpha3": "PHL", - "numeric": "608", - "altName": "Philippines" + 'name': 'Philippines (the)', + 'alpha2': 'PH', + 'alpha3': 'PHL', + 'numeric': '608', + 'altName': 'Philippines' }, { - "name": "Pitcairn", - "alpha2": "PN", - "alpha3": "PCN", - "numeric": "612" + 'name': 'Pitcairn', + 'alpha2': 'PN', + 'alpha3': 'PCN', + 'numeric': '612' }, { - "name": "Poland", - "alpha2": "PL", - "alpha3": "POL", - "numeric": "616" + 'name': 'Poland', + 'alpha2': 'PL', + 'alpha3': 'POL', + 'numeric': '616' }, { - "name": "Portugal", - "alpha2": "PT", - "alpha3": "PRT", - "numeric": "620" + 'name': 'Portugal', + 'alpha2': 'PT', + 'alpha3': 'PRT', + 'numeric': '620' }, { - "name": "Puerto Rico", - "alpha2": "PR", - "alpha3": "PRI", - "numeric": "630" + 'name': 'Puerto Rico', + 'alpha2': 'PR', + 'alpha3': 'PRI', + 'numeric': '630' }, { - "name": "Qatar", - "alpha2": "QA", - "alpha3": "QAT", - "numeric": "634" + 'name': 'Qatar', + 'alpha2': 'QA', + 'alpha3': 'QAT', + 'numeric': '634' }, { - "name": "Réunion", - "alpha2": "RE", - "alpha3": "REU", - "numeric": "638", - "shortName": "Reunion" + 'name': 'Réunion', + 'alpha2': 'RE', + 'alpha3': 'REU', + 'numeric': '638', + 'shortName': 'Reunion' }, { - "name": "Romania", - "alpha2": "RO", - "alpha3": "ROU", - "numeric": "642" + 'name': 'Romania', + 'alpha2': 'RO', + 'alpha3': 'ROU', + 'numeric': '642' }, { - "name": "Russian Federation (the)", - "alpha2": "RU", - "alpha3": "RUS", - "numeric": "643", - "altName": "Russian Federation", - "shortName": "Russia" + 'name': 'Russian Federation (the)', + 'alpha2': 'RU', + 'alpha3': 'RUS', + 'numeric': '643', + 'altName': 'Russian Federation', + 'shortName': 'Russia' }, { - "name": "Rwanda", - "alpha2": "RW", - "alpha3": "RWA", - "numeric": "646" + 'name': 'Rwanda', + 'alpha2': 'RW', + 'alpha3': 'RWA', + 'numeric': '646' }, { - "name": "Saint Barthélemy", - "alpha2": "BL", - "alpha3": "BLM", - "numeric": "652", - "altName": "Saint-Barthélemy", - "shortName": "Saint Barthelemy" + 'name': 'Saint Barthélemy', + 'alpha2': 'BL', + 'alpha3': 'BLM', + 'numeric': '652', + 'altName': 'Saint-Barthélemy', + 'shortName': 'Saint Barthelemy' }, { - "name": "Saint Helena, Ascension and Tristan da Cunha", - "alpha2": "SH", - "alpha3": "SHN", - "numeric": "654", - "altName": "Saint Helena" + 'name': 'Saint Helena, Ascension and Tristan da Cunha', + 'alpha2': 'SH', + 'alpha3': 'SHN', + 'numeric': '654', + 'altName': 'Saint Helena' }, { - "name": "Saint Kitts and Nevis", - "alpha2": "KN", - "alpha3": "KNA", - "numeric": "659" + 'name': 'Saint Kitts and Nevis', + 'alpha2': 'KN', + 'alpha3': 'KNA', + 'numeric': '659' }, { - "name": "Saint Lucia", - "alpha2": "LC", - "alpha3": "LCA", - "numeric": "662" + 'name': 'Saint Lucia', + 'alpha2': 'LC', + 'alpha3': 'LCA', + 'numeric': '662' }, { - "name": "Saint Martin (French part)", - "alpha2": "MF", - "alpha3": "MAF", - "numeric": "663", - "altName": "Saint-Martin (French part)", - "shortName": "Saint Martin" + 'name': 'Saint Martin (French part)', + 'alpha2': 'MF', + 'alpha3': 'MAF', + 'numeric': '663', + 'altName': 'Saint-Martin (French part)', + 'shortName': 'Saint Martin' }, { - "name": "Saint Pierre and Miquelon", - "alpha2": "PM", - "alpha3": "SPM", - "numeric": "666" + 'name': 'Saint Pierre and Miquelon', + 'alpha2': 'PM', + 'alpha3': 'SPM', + 'numeric': '666' }, { - "name": "Saint Vincent and the Grenadines", - "alpha2": "VC", - "alpha3": "VCT", - "numeric": "670", - "altName": "Saint Vincent and Grenadines" + 'name': 'Saint Vincent and the Grenadines', + 'alpha2': 'VC', + 'alpha3': 'VCT', + 'numeric': '670', + 'altName': 'Saint Vincent and Grenadines' }, { - "name": "Samoa", - "alpha2": "WS", - "alpha3": "WSM", - "numeric": "882" + 'name': 'Samoa', + 'alpha2': 'WS', + 'alpha3': 'WSM', + 'numeric': '882' }, { - "name": "San Marino", - "alpha2": "SM", - "alpha3": "SMR", - "numeric": "674" + 'name': 'San Marino', + 'alpha2': 'SM', + 'alpha3': 'SMR', + 'numeric': '674' }, { - "name": "Sao Tome and Principe", - "alpha2": "ST", - "alpha3": "STP", - "numeric": "678" + 'name': 'Sao Tome and Principe', + 'alpha2': 'ST', + 'alpha3': 'STP', + 'numeric': '678' }, { - "name": "Saudi Arabia", - "alpha2": "SA", - "alpha3": "SAU", - "numeric": "682" + 'name': 'Saudi Arabia', + 'alpha2': 'SA', + 'alpha3': 'SAU', + 'numeric': '682' }, { - "name": "Senegal", - "alpha2": "SN", - "alpha3": "SEN", - "numeric": "686" + 'name': 'Senegal', + 'alpha2': 'SN', + 'alpha3': 'SEN', + 'numeric': '686' }, { - "name": "Serbia", - "alpha2": "RS", - "alpha3": "SRB", - "numeric": "688" + 'name': 'Serbia', + 'alpha2': 'RS', + 'alpha3': 'SRB', + 'numeric': '688' }, { - "name": "Seychelles", - "alpha2": "SC", - "alpha3": "SYC", - "numeric": "690" + 'name': 'Seychelles', + 'alpha2': 'SC', + 'alpha3': 'SYC', + 'numeric': '690' }, { - "name": "Sierra Leone", - "alpha2": "SL", - "alpha3": "SLE", - "numeric": "694" + 'name': 'Sierra Leone', + 'alpha2': 'SL', + 'alpha3': 'SLE', + 'numeric': '694' }, { - "name": "Singapore", - "alpha2": "SG", - "alpha3": "SGP", - "numeric": "702" + 'name': 'Singapore', + 'alpha2': 'SG', + 'alpha3': 'SGP', + 'numeric': '702' }, { - "name": "Sint Maarten (Dutch part)", - "alpha2": "SX", - "alpha3": "SXM", - "numeric": "534", - "shortName": "Sint Maarten" + 'name': 'Sint Maarten (Dutch part)', + 'alpha2': 'SX', + 'alpha3': 'SXM', + 'numeric': '534', + 'shortName': 'Sint Maarten' }, { - "name": "Slovakia", - "alpha2": "SK", - "alpha3": "SVK", - "numeric": "703" + 'name': 'Slovakia', + 'alpha2': 'SK', + 'alpha3': 'SVK', + 'numeric': '703' }, { - "name": "Slovenia", - "alpha2": "SI", - "alpha3": "SVN", - "numeric": "705" + 'name': 'Slovenia', + 'alpha2': 'SI', + 'alpha3': 'SVN', + 'numeric': '705' }, { - "name": "Solomon Islands", - "alpha2": "SB", - "alpha3": "SLB", - "numeric": "090" + 'name': 'Solomon Islands', + 'alpha2': 'SB', + 'alpha3': 'SLB', + 'numeric': '090' }, { - "name": "Somalia", - "alpha2": "SO", - "alpha3": "SOM", - "numeric": "706" + 'name': 'Somalia', + 'alpha2': 'SO', + 'alpha3': 'SOM', + 'numeric': '706' }, { - "name": "South Africa", - "alpha2": "ZA", - "alpha3": "ZAF", - "numeric": "710" + 'name': 'South Africa', + 'alpha2': 'ZA', + 'alpha3': 'ZAF', + 'numeric': '710' }, { - "name": "South Georgia and the South Sandwich Islands", - "alpha2": "GS", - "alpha3": "SGS", - "numeric": "239" + 'name': 'South Georgia and the South Sandwich Islands', + 'alpha2': 'GS', + 'alpha3': 'SGS', + 'numeric': '239' }, { - "name": "South Sudan", - "alpha2": "SS", - "alpha3": "SSD", - "numeric": "728" + 'name': 'South Sudan', + 'alpha2': 'SS', + 'alpha3': 'SSD', + 'numeric': '728' }, { - "name": "Spain", - "alpha2": "ES", - "alpha3": "ESP", - "numeric": "724" + 'name': 'Spain', + 'alpha2': 'ES', + 'alpha3': 'ESP', + 'numeric': '724' }, { - "name": "Sri Lanka", - "alpha2": "LK", - "alpha3": "LKA", - "numeric": "144" + 'name': 'Sri Lanka', + 'alpha2': 'LK', + 'alpha3': 'LKA', + 'numeric': '144' }, { - "name": "Sudan (the)", - "alpha2": "SD", - "alpha3": "SDN", - "numeric": "729", - "altName": "Sudan" + 'name': 'Sudan (the)', + 'alpha2': 'SD', + 'alpha3': 'SDN', + 'numeric': '729', + 'altName': 'Sudan' }, { - "name": "Suriname", - "alpha2": "SR", - "alpha3": "SUR", - "numeric": "740" + 'name': 'Suriname', + 'alpha2': 'SR', + 'alpha3': 'SUR', + 'numeric': '740' }, { - "name": "Svalbard and Jan Mayen", - "alpha2": "SJ", - "alpha3": "SJM", - "numeric": "744", - "altName": "Svalbard and Jan Mayen Islands" + 'name': 'Svalbard and Jan Mayen', + 'alpha2': 'SJ', + 'alpha3': 'SJM', + 'numeric': '744', + 'altName': 'Svalbard and Jan Mayen Islands' }, { - "name": "Swaziland", - "alpha2": "SZ", - "alpha3": "SWZ", - "numeric": "748" + 'name': 'Swaziland', + 'alpha2': 'SZ', + 'alpha3': 'SWZ', + 'numeric': '748' }, { - "name": "Sweden", - "alpha2": "SE", - "alpha3": "SWE", - "numeric": "752" + 'name': 'Sweden', + 'alpha2': 'SE', + 'alpha3': 'SWE', + 'numeric': '752' }, { - "name": "Switzerland", - "alpha2": "CH", - "alpha3": "CHE", - "numeric": "756" + 'name': 'Switzerland', + 'alpha2': 'CH', + 'alpha3': 'CHE', + 'numeric': '756' }, { - "name": "Syrian Arab Republic", - "alpha2": "SY", - "alpha3": "SYR", - "numeric": "760", - "altName": "Syrian Arab Republic (Syria)", - "shortName": "Syria" + 'name': 'Syrian Arab Republic', + 'alpha2': 'SY', + 'alpha3': 'SYR', + 'numeric': '760', + 'altName': 'Syrian Arab Republic (Syria)', + 'shortName': 'Syria' }, { - "name": "Taiwan (Province of China)", - "alpha2": "TW", - "alpha3": "TWN", - "numeric": "158", - "altName": "Taiwan, Republic of China", - "shortName": "Taiwan" + 'name': 'Taiwan (Province of China)', + 'alpha2': 'TW', + 'alpha3': 'TWN', + 'numeric': '158', + 'altName': 'Taiwan, Republic of China', + 'shortName': 'Taiwan' }, { - "name": "Tajikistan", - "alpha2": "TJ", - "alpha3": "TJK", - "numeric": "762" + 'name': 'Tajikistan', + 'alpha2': 'TJ', + 'alpha3': 'TJK', + 'numeric': '762' }, { - "name": "Tanzania, United Republic of", - "alpha2": "TZ", - "alpha3": "TZA", - "numeric": "834", - "shortName": "Tanzania" + 'name': 'Tanzania, United Republic of', + 'alpha2': 'TZ', + 'alpha3': 'TZA', + 'numeric': '834', + 'shortName': 'Tanzania' }, { - "name": "Thailand", - "alpha2": "TH", - "alpha3": "THA", - "numeric": "764" + 'name': 'Thailand', + 'alpha2': 'TH', + 'alpha3': 'THA', + 'numeric': '764' }, { - "name": "Timor-Leste", - "alpha2": "TL", - "alpha3": "TLS", - "numeric": "626", - "shortName": "East Timor" + 'name': 'Timor-Leste', + 'alpha2': 'TL', + 'alpha3': 'TLS', + 'numeric': '626', + 'shortName': 'East Timor' }, { - "name": "Togo", - "alpha2": "TG", - "alpha3": "TGO", - "numeric": "768" + 'name': 'Togo', + 'alpha2': 'TG', + 'alpha3': 'TGO', + 'numeric': '768' }, { - "name": "Tokelau", - "alpha2": "TK", - "alpha3": "TKL", - "numeric": "772" + 'name': 'Tokelau', + 'alpha2': 'TK', + 'alpha3': 'TKL', + 'numeric': '772' }, { - "name": "Tonga", - "alpha2": "TO", - "alpha3": "TON", - "numeric": "776" + 'name': 'Tonga', + 'alpha2': 'TO', + 'alpha3': 'TON', + 'numeric': '776' }, { - "name": "Trinidad and Tobago", - "alpha2": "TT", - "alpha3": "TTO", - "numeric": "780" + 'name': 'Trinidad and Tobago', + 'alpha2': 'TT', + 'alpha3': 'TTO', + 'numeric': '780' }, { - "name": "Tunisia", - "alpha2": "TN", - "alpha3": "TUN", - "numeric": "788" + 'name': 'Tunisia', + 'alpha2': 'TN', + 'alpha3': 'TUN', + 'numeric': '788' }, { - "name": "Turkey", - "alpha2": "TR", - "alpha3": "TUR", - "numeric": "792" + 'name': 'Turkey', + 'alpha2': 'TR', + 'alpha3': 'TUR', + 'numeric': '792' }, { - "name": "Turkmenistan", - "alpha2": "TM", - "alpha3": "TKM", - "numeric": "795" + 'name': 'Turkmenistan', + 'alpha2': 'TM', + 'alpha3': 'TKM', + 'numeric': '795' }, { - "name": "Turks and Caicos Islands (the)", - "alpha2": "TC", - "alpha3": "TCA", - "numeric": "796", - "altName": "Turks and Caicos Islands" + 'name': 'Turks and Caicos Islands (the)', + 'alpha2': 'TC', + 'alpha3': 'TCA', + 'numeric': '796', + 'altName': 'Turks and Caicos Islands' }, { - "name": "Tuvalu", - "alpha2": "TV", - "alpha3": "TUV", - "numeric": "798" + 'name': 'Tuvalu', + 'alpha2': 'TV', + 'alpha3': 'TUV', + 'numeric': '798' }, { - "name": "Uganda", - "alpha2": "UG", - "alpha3": "UGA", - "numeric": "800" + 'name': 'Uganda', + 'alpha2': 'UG', + 'alpha3': 'UGA', + 'numeric': '800' }, { - "name": "Ukraine", - "alpha2": "UA", - "alpha3": "UKR", - "numeric": "804" + 'name': 'Ukraine', + 'alpha2': 'UA', + 'alpha3': 'UKR', + 'numeric': '804' }, { - "name": "United Arab Emirates (the)", - "alpha2": "AE", - "alpha3": "ARE", - "numeric": "784", - "altName": "United Arab Emirates" + 'name': 'United Arab Emirates (the)', + 'alpha2': 'AE', + 'alpha3': 'ARE', + 'numeric': '784', + 'altName': 'United Arab Emirates' }, { - "name": "United Kingdom of Great Britain and Northern Ireland (the)", - "alpha2": "GB", - "alpha3": "GBR", - "numeric": "826", - "altName": "United Kingdom" + 'name': 'United Kingdom of Great Britain and Northern Ireland (the)', + 'alpha2': 'GB', + 'alpha3': 'GBR', + 'numeric': '826', + 'altName': 'United Kingdom' }, { - "name": "United States Minor Outlying Islands (the)", - "alpha2": "UM", - "alpha3": "UMI", - "numeric": "581", - "altName": "US Minor Outlying Islands" + 'name': 'United States Minor Outlying Islands (the)', + 'alpha2': 'UM', + 'alpha3': 'UMI', + 'numeric': '581', + 'altName': 'US Minor Outlying Islands' }, { - "name": "United States of America (the)", - "alpha2": "US", - "alpha3": "USA", - "numeric": "840", - "altName": "United States of America", - "shortName": "United States" + 'name': 'United States of America (the)', + 'alpha2': 'US', + 'alpha3': 'USA', + 'numeric': '840', + 'altName': 'United States of America', + 'shortName': 'United States' }, { - "name": "Uruguay", - "alpha2": "UY", - "alpha3": "URY", - "numeric": "858" + 'name': 'Uruguay', + 'alpha2': 'UY', + 'alpha3': 'URY', + 'numeric': '858' }, { - "name": "Uzbekistan", - "alpha2": "UZ", - "alpha3": "UZB", - "numeric": "860" + 'name': 'Uzbekistan', + 'alpha2': 'UZ', + 'alpha3': 'UZB', + 'numeric': '860' }, { - "name": "Vanuatu", - "alpha2": "VU", - "alpha3": "VUT", - "numeric": "548" + 'name': 'Vanuatu', + 'alpha2': 'VU', + 'alpha3': 'VUT', + 'numeric': '548' }, { - "name": "Venezuela (Bolivarian Republic of)", - "alpha2": "VE", - "alpha3": "VEN", - "numeric": "862", - "altName": "Venezuela (Bolivarian Republic)", - "shortName": "Venezuela" + 'name': 'Venezuela (Bolivarian Republic of)', + 'alpha2': 'VE', + 'alpha3': 'VEN', + 'numeric': '862', + 'altName': 'Venezuela (Bolivarian Republic)', + 'shortName': 'Venezuela' }, { - "name": "Viet Nam", - "alpha2": "VN", - "alpha3": "VNM", - "numeric": "704", - "shortName": "Vietnam" + 'name': 'Viet Nam', + 'alpha2': 'VN', + 'alpha3': 'VNM', + 'numeric': '704', + 'shortName': 'Vietnam' }, { - "name": "Virgin Islands (British)", - "alpha2": "VG", - "alpha3": "VGB", - "numeric": "092", - "altName": "British Virgin Islands" + 'name': 'Virgin Islands (British)', + 'alpha2': 'VG', + 'alpha3': 'VGB', + 'numeric': '092', + 'altName': 'British Virgin Islands' }, { - "name": "Virgin Islands (U.S.)", - "alpha2": "VI", - "alpha3": "VIR", - "numeric": "850", - "altName": "Virgin Islands, US", - "shortName": "U.S. Virgin Islands" + 'name': 'Virgin Islands (U.S.)', + 'alpha2': 'VI', + 'alpha3': 'VIR', + 'numeric': '850', + 'altName': 'Virgin Islands, US', + 'shortName': 'U.S. Virgin Islands' }, { - "name": "Wallis and Futuna", - "alpha2": "WF", - "alpha3": "WLF", - "numeric": "876", - "altName": "Wallis and Futuna Islands" + 'name': 'Wallis and Futuna', + 'alpha2': 'WF', + 'alpha3': 'WLF', + 'numeric': '876', + 'altName': 'Wallis and Futuna Islands' }, { - "name": "Western Sahara*", - "alpha2": "EH", - "alpha3": "ESH", - "numeric": "732", - "altName": "Western Sahara" + 'name': 'Western Sahara*', + 'alpha2': 'EH', + 'alpha3': 'ESH', + 'numeric': '732', + 'altName': 'Western Sahara' }, { - "name": "Yemen", - "alpha2": "YE", - "alpha3": "YEM", - "numeric": "887" + 'name': 'Yemen', + 'alpha2': 'YE', + 'alpha3': 'YEM', + 'numeric': '887' }, { - "name": "Zambia", - "alpha2": "ZM", - "alpha3": "ZMB", - "numeric": "894" + 'name': 'Zambia', + 'alpha2': 'ZM', + 'alpha3': 'ZMB', + 'numeric': '894' }, { - "name": "Zimbabwe", - "alpha2": "ZW", - "alpha3": "ZWE", - "numeric": "716" + 'name': 'Zimbabwe', + 'alpha2': 'ZW', + 'alpha3': 'ZWE', + 'numeric': '716' } - ]; +]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts index 9f909a0b6b..584108cf77 100644 --- a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const customerOperations = [ { @@ -18,11 +18,6 @@ export const customerOperations = [ value: 'create', description: 'Create a new customer', }, - { - name: 'Properties', - value: 'properties', - description: 'Get customer property definitions', - }, { name: 'Get', value: 'get', @@ -33,6 +28,11 @@ export const customerOperations = [ value: 'getAll', description: 'Get all customers', }, + { + name: 'Properties', + value: 'properties', + description: 'Get customer property definitions', + }, { name: 'Update', value: 'update', @@ -92,16 +92,6 @@ export const customerFields = [ default: 1, description: `Customer’s age`, }, - { - displayName: 'Notes', - name: 'background', - type: 'string', - typeOptions: { - alwaysOpenEditWindow: true, - }, - default: '', - description: `Notes`, - }, { displayName: 'First Name', name: 'firstName', @@ -151,6 +141,16 @@ export const customerFields = [ default: '', description: 'Location of the customer.', }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, { displayName: 'Organization', name: 'organization', @@ -267,39 +267,39 @@ export const customerFields = [ type: 'options', options: [ { - name: 'aim', + name: 'AIM', value: 'aim', }, { - name: 'gtalk', + name: 'Google Talk', value: 'gtalk', }, { - name: 'icq', + name: 'ICQ', value: 'icq', }, { - name: 'msn', + name: 'MSN', value: 'msn', }, { - name: 'other', + name: 'Other', value: 'other', }, { - name: 'qq', + name: 'QQ', value: 'qq', }, { - name: 'skype', + name: 'Skype', value: 'skype', }, { - name: 'xmpp', + name: 'XMPP', value: 'xmpp', }, { - name: 'yahoo', + name: 'Yahoo', value: 'yahoo', }, ], @@ -584,13 +584,6 @@ export const customerFields = [ }, }, options: [ - { - displayName: 'Mailbox ID', - name: 'mailbox', - type: 'string', - default: '', - description: 'Filters customers from a specific mailbox', - }, { displayName: 'First Name', name: 'firstName', @@ -605,6 +598,13 @@ export const customerFields = [ default: '', description: 'Filters customers by last name', }, + { + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters customers from a specific mailbox', + }, { displayName: 'Modified Since', name: 'modifiedSince', @@ -733,16 +733,6 @@ export const customerFields = [ default: 1, description: `Customer’s age`, }, - { - displayName: 'Notes', - name: 'background', - type: 'string', - typeOptions: { - alwaysOpenEditWindow: true, - }, - default: '', - description: `Notes`, - }, { displayName: 'First Name', name: 'firstName', @@ -792,6 +782,16 @@ export const customerFields = [ default: '', description: 'Location of the customer.', }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, { displayName: 'Organization', name: 'organization', @@ -806,6 +806,6 @@ export const customerFields = [ default: '', description: 'URL of the customer’s photo', }, - ] + ], }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts index 569a92c9aa..fe50a7cb3c 100644 --- a/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts +++ b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts @@ -1,4 +1,4 @@ -import { IDataObject } from "n8n-workflow"; +import { IDataObject } from 'n8n-workflow'; export interface ICustomer { address?: IDataObject; diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts index c7cda4eac6..fb04b669e6 100644 --- a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -1,9 +1,9 @@ import { OptionsWithUri } from 'request'; import { + IHookFunctions, IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions, - IHookFunctions, } from 'n8n-core'; import { IDataObject, @@ -43,7 +43,8 @@ export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSing return `${error.path} ${error.message}`; }).join('-')}`); } - throw error; + + throw new Error(`HelpScout error response [${error.statusCode}]: ${error.message}`); } } diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts index a339e6cfb4..190b276cb8 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -3,56 +3,57 @@ import { } from 'n8n-core'; import { + IBinaryKeyData, IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodePropertyOptions, INodeTypeDescription, INodeType, - ILoadOptionsFunctions, - INodePropertyOptions, - IBinaryKeyData, } from 'n8n-workflow'; import { - helpscoutApiRequest, - helpscoutApiRequestAllItems, -} from './GenericFunctions'; + countriesCodes +} from './CountriesCodes'; import { - conversationOperations, conversationFields, + conversationOperations, } from './ConversationDescription'; import { - customerOperations, customerFields, + customerOperations, } from './CustomerDescription'; -import { - mailboxOperations, - mailboxFields, -} from './MailboxDescription'; - -import { - threadOperations, - threadFields, -} from './ThreadDescription'; - import { ICustomer, } from './CustomerInterface'; import { IConversation, - } from './ConversationInterface'; +} from './ConversationInterface'; - import { - IThread, +import { + helpscoutApiRequest, + helpscoutApiRequestAllItems, +} from './GenericFunctions'; + +import { + mailboxFields, + mailboxOperations, +} from './MailboxDescription'; + +import { + threadFields, + threadOperations, +} from './ThreadDescription'; + +import { IAttachment, - } from './ThreadInterface'; + IThread, +} from './ThreadInterface'; - import { - countriesCodes -} from './CountriesCodes'; export class HelpScout implements INodeType { description: INodeTypeDescription = { diff --git a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts index ecb7e2a12a..63592aa14e 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts @@ -4,10 +4,10 @@ import { } from 'n8n-core'; import { - INodeTypeDescription, - INodeType, - IWebhookResponseData, IDataObject, + INodeType, + INodeTypeDescription, + IWebhookResponseData, } from 'n8n-workflow'; import { @@ -51,52 +51,53 @@ export class HelpScoutTrigger implements INodeType { name: 'events', type: 'multiOptions', options: [ + { - name: 'convo.agent.reply.created', - value: 'convo.agent.reply.created', - }, - { - name: 'convo.assigned', + name: 'Conversation - Assigned', value: 'convo.assigned', }, { - name: 'convo.created', + name: 'Conversation - Created', value: 'convo.created', }, { - name: 'convo.customer.reply.created', - value: 'convo.customer.reply.created', - }, - { - name: 'convo.deleted', + name: 'Conversation - Deleted', value: 'convo.deleted', }, { - name: 'convo.merged', + name: 'Conversation - Merged', value: 'convo.merged', }, { - name: 'convo.moved', + name: 'Conversation - Moved', value: 'convo.moved', }, { - name: 'convo.note.created', - value: 'convo.note.created', - }, - { - name: 'convo.status', + name: 'Conversation - Status', value: 'convo.status', }, { - name: 'convo.tags', + name: 'Conversation - Tags', value: 'convo.tags', }, { - name: 'customer.created', + name: 'Conversation Agent Reply - Created', + value: 'convo.agent.reply.created', + }, + { + name: 'Conversation Customer Reply - Created', + value: 'convo.customer.reply.created', + }, + { + name: 'Conversation Note - Created', + value: 'convo.note.created', + }, + { + name: 'Customer - Created', value: 'customer.created', }, { - name: 'satisfaction.ratings', + name: 'Rating - Received', value: 'satisfaction.ratings', }, ], diff --git a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts index 923b01677f..27063c05ed 100644 --- a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const mailboxOperations = [ { diff --git a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts index 230b99ce56..4374e68453 100644 --- a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const threadOperations = [ { @@ -128,6 +128,12 @@ export const threadFields = [ }, }, options: [ + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + }, { displayName: 'Customer Email', name: 'customerEmail', @@ -161,12 +167,6 @@ export const threadFields = [ default: false, description: 'When imported is set to true, no outgoing emails or notifications will be generated.', }, - { - displayName: 'Created At', - name: 'createdAt', - type: 'dateTime', - default: '', - }, ] }, { diff --git a/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts index b6cbbda213..e47a0a6747 100644 --- a/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts +++ b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts @@ -1,4 +1,4 @@ -import { IDataObject } from "n8n-workflow"; +import { IDataObject } from 'n8n-workflow'; export interface IAttachment { fileName?: string; diff --git a/packages/nodes-base/nodes/HelpScout/helpScout.png b/packages/nodes-base/nodes/HelpScout/helpScout.png index a2be95ba7f734e4f120cc2b6624a660b4ddec7b2..1ebdaa8e5e17325e79cec03c48aa5321c6e5e2a9 100644 GIT binary patch delta 858 zcmV-g1Eu`_CFTZ@8Gi!+002ht61D&U0D(|UR7De#?*IS){r&#{j_wbV?kkn=3X$#r zknRAC?cM15CYA3PlkOpu?*NJI0EX@D^ZhWE@9FUT*yZ{;nC~^0?;Mlv@%H^no$o%H z@8j(I$lLfuobOzt@b&rraI5jr;rid|`%$0ov(5H>u<~oE@qdlD@^-E9w$Jvg%J#n0 z_qx&caupn$0007@NklceO@hvM9nLXkUCmgpvq zQc@4TUvUL=YNXIIXHXJ%Qo_pp+AB@YqbRmVIlV!}JAVr8aK@r3-i-6&mERH5$1)gD zwsK){U-}9t#X=O7in`Zx6;KYntOV$=8J;)2o|-{n#*{1#FUfo+iKkQ(6poZoWt|!P z*kn@_9ZoGJ?2fvSVr3zZBZ>tRPCqTrIO{|yCMbnGT!QJxwE1}UtPirFv-gw|be&v+ zaigI{H-CJ)tJIu`d#vn9XB>ec{3%zc1%gHUHXOR z1}Uv6CMe<>mwq(GNGYc%tLXG&3X1JHFD5Bn`rU%*$CQv@|DjmgZy&2cPQ@p@_*0{k z3jhC4c<~CJ{2-=7hM=fC=t z5_m-V$s2xo;02B+uV8u*CH$KjBqo_5${~uP1a~>*)0=7I=WZRQQ2N~{{fN@VDrHa< zg=Lg}9fc{_~9Pr-H-6b;aO*DlnGdO(YF1beZaGn+IThqWQx9>NA$M~)YU0_a kB4r+F;@Rv~ulnir7wLQ}BpcsNu>b%707*qoM6N<$f;s235&!@I literal 4862 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 Vf)A~m9w$G3I@b&|pQ+p4{~u?LqWk~= diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ecef7e0f5e..67490dd1cc 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -198,8 +198,8 @@ "@types/gm": "^1.18.2", "@types/imap-simple": "^4.2.0", "@types/jest": "^24.0.18", - "@types/lodash.set": "^4.3.6", - "@types/moment-timezone": "^0.5.12", + "@types/lodash.set": "^4.3.6", + "@types/moment-timezone": "^0.5.12", "@types/mongodb": "^3.3.6", "@types/node": "^10.10.1", "@types/nodemailer": "^4.6.5", @@ -225,8 +225,8 @@ "imap-simple": "^4.3.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "lodash.unset": "^4.5.2", - "moment-timezone": "0.5.28", + "lodash.unset": "^4.5.2", + "moment-timezone": "0.5.28", "mongodb": "^3.3.2", "mysql2": "^2.0.1", "n8n-core": "~0.20.0", From bc7dfd97de43814e8600331c0e6c27b7b55afc40 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 30 Mar 2020 00:43:26 +0200 Subject: [PATCH 15/20] :zap: Fix OneDrive-Node --- .../Microsoft/OneDrive/FileDescription.ts | 2 +- .../Microsoft/OneDrive/FolderDescriptiont.ts | 75 ------------------ .../OneDrive/MicrosoftOneDrive.node.ts | 61 +++++++------- .../nodes/Microsoft/OneDrive/oneDrive.png | Bin 4123 -> 1088 bytes 4 files changed, 29 insertions(+), 109 deletions(-) delete mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts index 32fe70a948..0e9bc26cba 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const fileOperations = [ { diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts deleted file mode 100644 index 83bd871143..0000000000 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescriptiont.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { INodeProperties } from "n8n-workflow"; - -export const folderOperations = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - displayOptions: { - show: { - resource: [ - 'folder', - ], - }, - }, - options: [ - { - name: 'Get Children', - value: 'getChildren', - description: 'Get items inside a folder', - }, - { - name: 'Search', - value: 'search', - description: 'Search a folder', - }, - ], - default: 'getChildren', - description: 'The operation to perform.', - }, -] as INodeProperties[]; - -export const folderFields = [ - -/* -------------------------------------------------------------------------- */ -/* folder:getChildren */ -/* -------------------------------------------------------------------------- */ - { - displayName: 'Folder ID', - name: 'folderId', - type: 'string', - displayOptions: { - show: { - operation: [ - 'getChildren', - ], - resource: [ - 'folder', - ], - }, - }, - default: '', - description: 'Folder ID', - }, -/* -------------------------------------------------------------------------- */ -/* folder:search */ -/* -------------------------------------------------------------------------- */ - { - displayName: 'Query', - name: 'query', - type: 'string', - displayOptions: { - show: { - operation: [ - 'search', - ], - resource: [ - 'folder', - ], - }, - }, - default: '', - description: `The query text used to search for items. Values may be matched - across several fields including filename, metadata, and file content.`, - }, -] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index cee6733bd5..199dd48798 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -1,16 +1,14 @@ import { - IExecuteFunctions, BINARY_ENCODING, + IExecuteFunctions, } from 'n8n-core'; import { + IBinaryKeyData, IDataObject, INodeExecutionData, - INodeTypeDescription, INodeType, - ILoadOptionsFunctions, - INodePropertyOptions, - IBinaryKeyData, + INodeTypeDescription, } from 'n8n-workflow'; import { @@ -19,14 +17,14 @@ import { } from './GenericFunctions'; import { - fileOperations, fileFields, + fileOperations, } from './FileDescription'; import { + folderFields, folderOperations, - folderFields -} from './FolderDescriptiont'; +} from './FolderDescription'; export class MicrosoftOneDrive implements INodeType { description: INodeTypeDescription = { @@ -113,10 +111,17 @@ export class MicrosoftOneDrive implements INodeType { const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + const fileName = responseData.name; + if (responseData.file === undefined) { throw new Error('The ID you provided does not belong to a file.'); } + let mimeType: string | undefined; + if (responseData.file.mimeType) { + mimeType = responseData.file.mimeType; + } + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}/content`, {}, {}, undefined, {}, { encoding: null, resolveWithFullResponse: true }); const newItem: INodeExecutionData = { @@ -124,8 +129,7 @@ export class MicrosoftOneDrive implements INodeType { binary: {}, }; - let mimeType: string | undefined; - if (responseData.headers['content-type']) { + if (mimeType === undefined && responseData.headers['content-type']) { mimeType = responseData.headers['content-type']; } @@ -140,7 +144,7 @@ export class MicrosoftOneDrive implements INodeType { const data = Buffer.from(responseData.body); - items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, undefined, mimeType); + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online if (operation === 'get') { @@ -151,17 +155,17 @@ export class MicrosoftOneDrive implements INodeType { //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online if (operation === 'search') { const query = this.getNodeParameter('query', i) as string; - responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='${query}')`); responseData = responseData.filter((item: IDataObject) => item.file); - returnData.push(responseData as IDataObject); + returnData.push.apply(returnData, responseData as IDataObject[]); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file if (operation === 'upload') { const parentId = this.getNodeParameter('parentId', i) as string; - const binaryData = this.getNodeParameter('binaryData', 0) as boolean; - let fileName = this.getNodeParameter('fileName', 0) as string; + const isBinaryData = this.getNodeParameter('binaryData', i) as boolean; + const fileName = this.getNodeParameter('fileName', i) as string; - if (binaryData) { + if (isBinaryData) { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; if (items[i].binary === undefined) { @@ -174,20 +178,16 @@ export class MicrosoftOneDrive implements INodeType { const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; - if (fileName !== '') { - fileName = `${fileName}.${binaryData.fileExtension}`; - } - const body = Buffer.from(binaryData.data, BINARY_ENCODING); - responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body , {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length } ); - returnData.push(responseData as IDataObject); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body, {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length }, {} ); + returnData.push(JSON.parse(responseData) as IDataObject); } else { - const body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); + const body = this.getNodeParameter('fileContent', i) as string; if (fileName === '') { - throw new Error('File name must be defined'); + throw new Error('File name must be set!'); } - responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}.txt:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); returnData.push(responseData as IDataObject); } } @@ -197,14 +197,14 @@ export class MicrosoftOneDrive implements INodeType { if (operation === 'getChildren') { const folderId = this.getNodeParameter('folderId', i) as string; responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${folderId}/children`); - returnData.push(responseData as IDataObject); + returnData.push.apply(returnData, responseData as IDataObject[]); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online if (operation === 'search') { const query = this.getNodeParameter('query', i) as string; - responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='{${query}}')`); + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='${query}')`); responseData = responseData.filter((item: IDataObject) => item.folder); - returnData.push(responseData as IDataObject); + returnData.push.apply(returnData, responseData as IDataObject[]); } } } @@ -212,11 +212,6 @@ export class MicrosoftOneDrive implements INodeType { // For file downloads the files get attached to the existing items return this.prepareOutputData(items); } else { - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else if (responseData !== undefined) { - returnData.push(responseData as IDataObject); - } return [this.helpers.returnJsonArray(returnData)]; } } diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png b/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png index b33d98a8bb25e5b516df50c7769df1e617a38509..44f3f594ffe7233c976100cd21fb6b472b0497b0 100644 GIT binary patch delta 1078 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 Date: Sun, 29 Mar 2020 22:08:00 -0400 Subject: [PATCH 16/20] :zap: Improved getall operations --- .../HelpScout/ConversationDescription.ts | 40 +++++++++++++++++ .../nodes/HelpScout/CustomerDescription.ts | 40 +++++++++++++++++ .../nodes/HelpScout/GenericFunctions.ts | 5 ++- .../nodes/HelpScout/HelpScout.node.ts | 36 ++++++++++++++-- .../nodes/HelpScout/MailboxDescription.ts | 43 +++++++++++++++++++ .../nodes/HelpScout/ThreadDescription.ts | 40 +++++++++++++++++ 6 files changed, 199 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts index 5d882ac80f..30b46368a7 100644 --- a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts @@ -411,6 +411,46 @@ export const conversationFields = [ /* -------------------------------------------------------------------------- */ /* conversation:getAll */ /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'conversation', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'conversation', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, { displayName: 'Options', name: 'options', diff --git a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts index 584108cf77..5f353d0bd5 100644 --- a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts @@ -567,6 +567,46 @@ export const customerFields = [ /* -------------------------------------------------------------------------- */ /* customer:getAll */ /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'customer', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'customer', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, { displayName: 'Options', name: 'options', diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts index fb04b669e6..ab933856c9 100644 --- a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -12,6 +12,7 @@ import { import { get, } from 'lodash'; +import { queryResult } from 'pg-promise'; export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any let options: OptionsWithUri = { @@ -53,13 +54,15 @@ export async function helpscoutApiRequestAllItems(this: IExecuteFunctions | ILoa const returnData: IDataObject[] = []; let responseData; - query.size = 50; let uri; do { responseData = await helpscoutApiRequest.call(this, method, endpoint, body, query, uri); uri = get(responseData, '_links.next.href'); returnData.push.apply(returnData, get(responseData, propertyName)); + if (query.limit && query.limit <= returnData.length) { + return returnData; + } } while ( responseData['_links'] !== undefined && responseData['_links'].next !== undefined && diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts index 190b276cb8..ce1f02d648 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -241,9 +241,16 @@ export class HelpScout implements INodeType { } //https://developer.helpscout.com/mailbox-api/endpoints/conversations/list if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; const options = this.getNodeParameter('options', i) as IDataObject; Object.assign(qs, options); - responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + responseData = responseData.splice(0, qs.limit); + } } } if (resource === 'customer') { @@ -307,9 +314,16 @@ export class HelpScout implements INodeType { } //https://developer.helpscout.com/mailbox-api/endpoints/customers/list if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; const options = this.getNodeParameter('options', i) as IDataObject; Object.assign(qs, options); - responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + responseData = responseData.splice(0, qs.limit); + } } //https://developer.helpscout.com/mailbox-api/endpoints/customers/overwrite/ if (operation === 'update') { @@ -335,7 +349,14 @@ export class HelpScout implements INodeType { } //https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/list if (operation === 'getAll') { - responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + responseData = responseData.splice(0, qs.limit); + } } } if (resource === 'thread') { @@ -396,8 +417,15 @@ export class HelpScout implements INodeType { } //https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; const conversationId = this.getNodeParameter('conversationId', i) as string; - responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`); + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`, {}, qs); + responseData = responseData.splice(0, qs.limit); + } } } if (Array.isArray(responseData)) { diff --git a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts index 27063c05ed..d9aff61100 100644 --- a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts @@ -51,4 +51,47 @@ export const mailboxFields = [ }, }, }, +/* -------------------------------------------------------------------------- */ +/* mailbox:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'mailbox', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'mailbox', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts index 4374e68453..eb2039745c 100644 --- a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts @@ -254,4 +254,44 @@ export const threadFields = [ }, description: 'conversation ID', }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'thread', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'thread', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, ] as INodeProperties[]; From 79fda65e7ed56c3bc3997aff5bc0c3777d4fb844 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 29 Mar 2020 22:39:43 -0400 Subject: [PATCH 17/20] :zap: Added create folder operation --- .../Microsoft/OneDrive/FolderDescription.ts | 101 ++++++++++++++++++ .../Microsoft/OneDrive/GenericFunctions.ts | 1 + .../OneDrive/MicrosoftOneDrive.node.ts | 10 ++ 3 files changed, 112 insertions(+) create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts new file mode 100644 index 0000000000..8f31be22a5 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -0,0 +1,101 @@ +import { INodeProperties } from "n8n-workflow"; + +export const folderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a folder', + }, + { + name: 'Get Children', + value: 'getChildren', + description: 'Get items inside a folder', + }, + { + name: 'Search', + value: 'search', + description: 'Search a folder', + }, + ], + default: 'getChildren', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const folderFields = [ + +/* -------------------------------------------------------------------------- */ +/* folder:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `Folder's name`, + }, +/* -------------------------------------------------------------------------- */ +/* folder:getChildren */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Folder ID', + name: 'folderId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getChildren', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: 'Folder ID', + }, +/* -------------------------------------------------------------------------- */ +/* folder:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts index 9330437f77..1bd1bee202 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts @@ -33,6 +33,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing if (Object.keys(body).length === 0) { delete options.body; } + //@ts-ignore return await this.helpers.requestOAuth.call(this, 'microsoftOneDriveOAuth2Api', options); } catch (error) { diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index 199dd48798..2af523cbc0 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -193,6 +193,16 @@ export class MicrosoftOneDrive implements INodeType { } } if (resource === 'folder') { + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const body: IDataObject = { + name, + folder: {}, + }; + responseData = await microsoftApiRequest.call(this, 'POST', '/drive/root/children', body); + returnData.push(responseData); + } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online if (operation === 'getChildren') { const folderId = this.getNodeParameter('folderId', i) as string; From e9f71f1b8e1713ff09ed5d36768d5d24d6917104 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 30 Mar 2020 08:19:59 +0200 Subject: [PATCH 18/20] :zap: Add back missing file --- .../Microsoft/OneDrive/FolderDescription.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts new file mode 100644 index 0000000000..cead016ab4 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -0,0 +1,75 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const folderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'Get Children', + value: 'getChildren', + description: 'Get items inside a folder', + }, + { + name: 'Search', + value: 'search', + description: 'Search a folder', + }, + ], + default: 'getChildren', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const folderFields = [ + +/* -------------------------------------------------------------------------- */ +/* folder:getChildren */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Folder ID', + name: 'folderId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getChildren', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: 'Folder ID', + }, +/* -------------------------------------------------------------------------- */ +/* folder:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +] as INodeProperties[]; From a45c9acb671e0266a65779bb1c8a6e4db1adf121 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 30 Mar 2020 09:20:40 +0200 Subject: [PATCH 19/20] :zap: Minor cleanup HelpScout-Node --- packages/nodes-base/nodes/HelpScout/ConversationDescription.ts | 2 +- packages/nodes-base/nodes/HelpScout/CustomerDescription.ts | 2 +- packages/nodes-base/nodes/HelpScout/GenericFunctions.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts index 30b46368a7..1ab9dd88ea 100644 --- a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts @@ -584,7 +584,7 @@ export const conversationFields = [ value: 'asc', }, { - name: 'Desc', + name: 'DESC', value: 'desc', }, ], diff --git a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts index 5f353d0bd5..1b3466d078 100644 --- a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts +++ b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts @@ -687,7 +687,7 @@ export const customerFields = [ value: 'asc', }, { - name: 'Desc', + name: 'DESC', value: 'desc', }, ], diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts index ab933856c9..76b64ed3e7 100644 --- a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -12,7 +12,6 @@ import { import { get, } from 'lodash'; -import { queryResult } from 'pg-promise'; export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any let options: OptionsWithUri = { From d207396edf41f8e9edc3693da7de4d50f63d1cbc Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 31 Mar 2020 18:54:44 +0200 Subject: [PATCH 20/20] :zap: Small improvements to Zoho CRM Node --- .../nodes-base/nodes/Zoho/LeadDescription.ts | 30 +++++++++--------- .../nodes-base/nodes/Zoho/ZohoCrm.node.ts | 31 +++++++++++++------ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/nodes-base/nodes/Zoho/LeadDescription.ts b/packages/nodes-base/nodes/Zoho/LeadDescription.ts index 577fee4aec..3b7777befd 100644 --- a/packages/nodes-base/nodes/Zoho/LeadDescription.ts +++ b/packages/nodes-base/nodes/Zoho/LeadDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const leadOperations = [ { @@ -18,6 +18,11 @@ export const leadOperations = [ value: 'create', description: 'Create a new lead', }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a lead', + }, { name: 'Get', value: 'get', @@ -38,11 +43,6 @@ export const leadOperations = [ value: 'update', description: 'Update a lead', }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a lead', - } ], default: 'create', description: 'The operation to perform.', @@ -648,6 +648,15 @@ export const leadFields = [ default: false, description: 'To include records from the child territories. True includes child territory records', }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadFields', + }, + default: [], + }, { displayName: 'Sort Order', name: 'sortOrder', @@ -665,15 +674,6 @@ export const leadFields = [ default: 'desc', description: 'Order sort attribute ascending or descending.', }, - { - displayName: 'Sort By', - name: 'sortBy', - type: 'multiOptions', - typeOptions: { - loadOptionsMethod: 'getLeadFields', - }, - default: [], - }, { displayName: 'Territory ID', name: 'territoryId', diff --git a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts index 20a04295e0..2e2a36bee6 100644 --- a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts +++ b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts @@ -4,11 +4,11 @@ import { import { IDataObject, - INodeExecutionData, - INodeTypeDescription, - INodeType, ILoadOptionsFunctions, + INodeExecutionData, INodePropertyOptions, + INodeType, + INodeTypeDescription, } from 'n8n-workflow'; import { @@ -17,13 +17,13 @@ import { } from './GenericFunctions'; import { - leadOperations, leadFields, + leadOperations, } from './LeadDescription'; import { - ILead, IAddress, + ILead, } from './LeadInterface'; export class ZohoCrm implements INodeType { @@ -92,7 +92,7 @@ export class ZohoCrm implements INodeType { qs.sort_order = 'desc'; const { data } = await zohoApiRequest.call(this, 'GET', '/accounts', {}, qs); for (const account of data) { - const accountName = account.Account_Name + const accountName = account.Account_Name; const accountId = account.id; returnData.push({ name: accountName, @@ -111,7 +111,7 @@ export class ZohoCrm implements INodeType { for (const field of fields) { if (field.api_name === 'Lead_Status') { for (const value of field.pick_list_values) { - const valueName = value.display_value + const valueName = value.display_value; const valueId = value.actual_value; returnData.push({ name: valueName, @@ -133,7 +133,7 @@ export class ZohoCrm implements INodeType { for (const field of fields) { if (field.api_name === 'Lead_Source') { for (const value of field.pick_list_values) { - const valueName = value.display_value + const valueName = value.display_value; const valueId = value.actual_value; returnData.push({ name: valueName, @@ -155,7 +155,7 @@ export class ZohoCrm implements INodeType { for (const field of fields) { if (field.api_name === 'Industry') { for (const value of field.pick_list_values) { - const valueName = value.display_value + const valueName = value.display_value; const valueId = value.actual_value; returnData.push({ name: valueName, @@ -285,6 +285,10 @@ export class ZohoCrm implements INodeType { } responseData = await zohoApiRequest.call(this, 'POST', '/leads', body); responseData = responseData.data; + + if (responseData.length) { + responseData = responseData[0].details; + } } //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html if (operation === 'update') { @@ -377,12 +381,19 @@ export class ZohoCrm implements INodeType { } responseData = await zohoApiRequest.call(this, 'PUT', `/leads/${leadId}`, body); responseData = responseData.data; + + if (responseData.length) { + responseData = responseData[0].details; + } } //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html if (operation === 'get') { const leadId = this.getNodeParameter('leadId', i) as string; responseData = await zohoApiRequest.call(this, 'GET', `/leads/${leadId}`); - responseData = responseData.data; + if (responseData !== undefined) { + responseData = responseData.data; + } + } //https://www.zoho.com/crm/developer/docs/api/get-records.html if (operation === 'getAll') {