diff --git a/packages/nodes-base/credentials/AgileCrmApi.credentials.ts b/packages/nodes-base/credentials/AgileCrmApi.credentials.ts new file mode 100644 index 00000000000..d1892709327 --- /dev/null +++ b/packages/nodes-base/credentials/AgileCrmApi.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class AgileCrmApi implements ICredentialType { + name = 'agileCrmApi'; + displayName = 'AgileCRM API'; + properties = [ + { + displayName: 'Email', + name: 'email', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts new file mode 100644 index 00000000000..6090aa7796a --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts @@ -0,0 +1,575 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription +} from 'n8n-workflow'; + +import { + contactFields, + contactOperations +} from './ContactDescription'; + +import { + companyFields, + companyOperations +} from './CompanyDescription'; + +import { + dealFields, + dealOperations +} from './DealDescription'; + +import { IContact, IContactUpdate } from './ContactInterface'; +import { agileCrmApiRequest, agileCrmApiRequestUpdate, validateJSON } from './GenericFunctions'; +import { IDeal } from './DealInterface'; + + +export class AgileCrm implements INodeType { + description: INodeTypeDescription = { + displayName: 'AgileCRM', + name: 'agileCrm', + icon: 'file:agilecrm.png', + group: ['transform'], + version: 1, + description: 'Consume AgileCRM API', + defaults: { + name: 'AgileCRM', + color: '#772244', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'agileCrmApi', + required: true, + } + ], + properties: [ + // Node properties which the user gets displayed and + // can change on the node. + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Company', + value: 'company' + }, + { + name: 'Contact', + value: 'contact' + }, + { + name: 'Deal', + value: 'deal' + }, + ], + default: 'contact', + description: 'Resource to consume.', + }, + // CONTACT + ...contactOperations, + ...contactFields, + + // COMPANY + ...companyOperations, + ...companyFields, + + // DEAL + ...dealOperations, + ...dealFields + ], + + }; + + + async execute(this: IExecuteFunctions): Promise { + + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + + if (resource === 'contact' || resource === 'company') { + const idGetter = resource === 'contact' ? 'contactId' : 'companyId'; + + if (operation === 'get') { + const contactId = this.getNodeParameter(idGetter, i) as string; + + const endpoint = `api/contacts/${contactId}`; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + + } else if (operation === 'delete') { + const contactId = this.getNodeParameter(idGetter, i) as string; + + const endpoint = `api/contacts/${contactId}`; + responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {}); + + } else if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (resource === 'contact') { + if (returnAll) { + const endpoint = 'api/contacts'; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + } else { + const limit = this.getNodeParameter('limit', i) as number; + const endpoint = `api/contacts?page_size=${limit}`; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + } + } else { + if (returnAll) { + const endpoint = 'api/contacts/companies/list'; + responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {}); + } else { + const limit = this.getNodeParameter('limit', i) as number; + const endpoint = `api/contacts/companies/list?page_size=${limit}`; + responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {}); + } + } + + } else if (operation === 'create') { + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + const body: IContact = {}; + const properties: IDataObject[] = []; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + + if (validateJSON(additionalFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(additionalFieldsJson)); + + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + // if company, add 'company' as type. default is person + if (resource === 'company') { + body.type = 'COMPANY'; + } + if (additionalFields.starValue) { + body.star_value = additionalFields.starValue as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string[]; + } + + // Contact specific properties + if (resource === 'contact') { + if (additionalFields.firstName) { + properties.push({ + type: 'SYSTEM', + name: 'first_name', + value: additionalFields.firstName as string + } as IDataObject); + } + if (additionalFields.lastName) { + properties.push({ + type: 'SYSTEM', + name: 'last_name', + value: additionalFields.lastName as string + } as IDataObject); + } + if (additionalFields.company) { + properties.push({ + type: 'SYSTEM', + name: 'company', + value: additionalFields.company as string + } as IDataObject); + } + if (additionalFields.title) { + properties.push({ + type: 'SYSTEM', + name: 'title', + value: additionalFields.title as string + } as IDataObject); + } + if (additionalFields.emailOptions) { + //@ts-ignore + additionalFields.emailOptions.emailProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'email', + value: property.email as string + } as IDataObject); + }); + } + if (additionalFields.addressOptions) { + //@ts-ignore + additionalFields.addressOptions.addressProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'address', + value: property.address as string + } as IDataObject); + }); + } + + if (additionalFields.phoneOptions) { + //@ts-ignore + additionalFields.phoneOptions.phoneProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'phone', + value: property.number as string + } as IDataObject); + }); + } + } else if (resource === 'company') { + if (additionalFields.email) { + properties.push({ + type: 'SYSTEM', + name: 'email', + value: additionalFields.email as string + } as IDataObject); + } + + if (additionalFields.address) { + properties.push({ + type: 'SYSTEM', + name: 'address', + value: additionalFields.address as string + } as IDataObject); + } + + if (additionalFields.phone) { + properties.push({ + type: 'SYSTEM', + name: 'phone', + value: additionalFields.phone as string + } as IDataObject); + } + + } + + if (additionalFields.websiteOptions) { + //@ts-ignore + additionalFields.websiteOptions.websiteProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'webiste', + value: property.url as string + } as IDataObject); + }); + } + + if (additionalFields.customProperties) { + //@ts-ignore + additionalFields.customProperties.customProperty.map(property => { + properties.push({ + type: 'CUSTOM', + subtype: property.subtype as string, + name: property.name, + value: property.value as string + } as IDataObject); + }); + } + body.properties = properties; + + } + const endpoint = 'api/contacts'; + responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, body); + + } else if (operation === 'update') { + const contactId = this.getNodeParameter(idGetter, i) as string; + const contactUpdatePayload: IContactUpdate = { id: contactId }; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + const body: IContact = {}; + const properties: IDataObject[] = []; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + + if (validateJSON(additionalFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(additionalFieldsJson)); + + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + } else { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.starValue) { + body.star_value = additionalFields.starValue as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string[]; + } + + // Contact specific properties + if (resource === 'contact') { + + if (additionalFields.leadScore) { + body.lead_score = additionalFields.leadScore as string; + } + + if (additionalFields.firstName) { + properties.push({ + type: 'SYSTEM', + name: 'first_name', + value: additionalFields.firstName as string + } as IDataObject); + } + if (additionalFields.lastName) { + properties.push({ + type: 'SYSTEM', + name: 'last_name', + value: additionalFields.lastName as string + } as IDataObject); + } + if (additionalFields.company) { + properties.push({ + type: 'SYSTEM', + name: 'company', + value: additionalFields.company as string + } as IDataObject); + } + if (additionalFields.title) { + properties.push({ + type: 'SYSTEM', + name: 'title', + value: additionalFields.title as string + } as IDataObject); + } + if (additionalFields.emailOptions) { + //@ts-ignore + additionalFields.emailOptions.emailProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'email', + value: property.email as string + } as IDataObject); + }); + } + if (additionalFields.addressOptions) { + //@ts-ignore + additionalFields.addressOptions.addressProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'address', + value: property.address as string + } as IDataObject); + }); + } + + if (additionalFields.phoneOptions) { + //@ts-ignore + additionalFields.phoneOptions.phoneProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'phone', + value: property.number as string + } as IDataObject); + }); + } + } else if (resource === 'company') { + if (additionalFields.email) { + properties.push({ + type: 'SYSTEM', + name: 'email', + value: additionalFields.email as string + } as IDataObject); + } + + if (additionalFields.address) { + properties.push({ + type: 'SYSTEM', + name: 'address', + value: additionalFields.address as string + } as IDataObject); + } + + if (additionalFields.phone) { + properties.push({ + type: 'SYSTEM', + name: 'phone', + value: additionalFields.phone as string + } as IDataObject); + } + + } + + if (additionalFields.websiteOptions) { + //@ts-ignore + additionalFields.websiteOptions.websiteProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'webiste', + value: property.url as string + } as IDataObject); + }); + } + if (additionalFields.customProperties) { + //@ts-ignore + additionalFields.customProperties.customProperty.map(property => { + properties.push({ + type: 'CUSTOM', + subtype: property.subtype as string, + name: property.name, + value: property.value as string + } as IDataObject); + }); + } + body.properties = properties; + } + + Object.assign(contactUpdatePayload, body); + + responseData = await agileCrmApiRequestUpdate.call(this, 'PUT', '', contactUpdatePayload); + + } + + } else if (resource === 'deal') { + + if (operation === 'get') { + const dealId = this.getNodeParameter('dealId', i) as string; + + const endpoint = `api/opportunity/${dealId}`; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + + } else if (operation === 'delete') { + const contactId = this.getNodeParameter('dealId', i) as string; + + const endpoint = `api/opportunity/${contactId}`; + responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {}); + + } else if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll) { + const endpoint = 'api/opportunity'; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + } else { + const limit = this.getNodeParameter('limit', i) as number; + const endpoint = `api/opportunity?page_size=${limit}`; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + } + + } else if (operation === 'create') { + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + const body: IDeal = {}; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + if (validateJSON(additionalFieldsJson) !== undefined) { + Object.assign(body, JSON.parse(additionalFieldsJson)); + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body.close_date = new Date(this.getNodeParameter('closeDate', i) as string).getTime(); + body.expected_value = this.getNodeParameter('expectedValue', i) as number; + body.milestone = this.getNodeParameter('milestone', i) as string; + body.probability = this.getNodeParameter('probability', i) as number; + body.name = this.getNodeParameter('name', i) as string; + + if (additionalFields.contactIds) { + body.contactIds = additionalFields.contactIds as string[]; + } + + if (additionalFields.customData) { + // @ts-ignore + body.customData = additionalFields.customData.customProperty as IDealCustomProperty[]; + } + + } + + const endpoint = 'api/opportunity'; + responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, body); + + } else if (operation === 'update') { + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + const body: IDeal = {}; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + + if (validateJSON(additionalFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(additionalFieldsJson)); + + } else { + throw new Error('Additional fields must be valid JSON'); + } + } + + } else { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + body.id = this.getNodeParameter('dealId', i) as number; + + if (additionalFields.expectedValue) { + body.expected_value = additionalFields.expectedValue as number; + } + + if (additionalFields.name) { + body.name = additionalFields.name as string; + } + + if (additionalFields.probability) { + body.probability = additionalFields.probability as number; + } + + if (additionalFields.contactIds) { + body.contactIds = additionalFields.contactIds as string[]; + } + + if (additionalFields.customData) { + // @ts-ignore + body.customData = additionalFields.customData.customProperty as IDealCustomProperty[]; + } + + } + + const endpoint = 'api/opportunity/partial-update'; + responseData = await agileCrmApiRequest.call(this, 'PUT', endpoint, body); + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + + } + + return [this.helpers.returnJsonArray(returnData)]; + } + +} diff --git a/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts b/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts new file mode 100644 index 00000000000..08b45d20900 --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts @@ -0,0 +1,660 @@ +import { + INodeProperties, +} from 'n8n-workflow'; +export const companyOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'company', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new company', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a company', + }, + { + name: 'Get', + value: 'get', + description: 'Get a company', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all companies', + }, + { + name: 'Update', + value: 'update', + description: 'Update company properties', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; +export const companyFields = [ + /* -------------------------------------------------------------------------- */ + /* company:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Company ID', + name: 'companyId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular company', + }, + + /* -------------------------------------------------------------------------- */ + /* company:get all */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 20, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + } + }, + /* -------------------------------------------------------------------------- */ + /* company:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + description: 'Object of values to set as described here.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'email', + type: 'string', + default: '', + description: 'Company address.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Company email.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Company name.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Company phone.', + }, + { + displayName: 'Star Value', + name: 'starValue', + type: 'options', + default: '', + description: 'Rating of company (Max value 5). This is not applicable for companies.', + options: [ + { + name: '0', + value: 0 + }, + { + name: '1', + value: 1 + }, + { + name: '2', + value: 2 + }, + { + name: '3', + value: 3 + }, + { + name: '4', + value: 4 + }, + { + name: '5', + value: 5 + }, + ] + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + default: [], + description: 'Unique identifiers added to company, for easy management of companys. This is not applicable for companies.', + }, + { + displayName: 'Website', + name: 'websiteOptions', + type: 'fixedCollection', + description: 'Companies websites.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Website properties.', + name: 'websiteProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of website.', + options: [ + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Feed', + value: 'feed', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Github', + value: 'github', + }, + { + name: 'Google Plus', + value: 'googlePlus', + }, + { + name: 'LinkedIn', + value: 'linkedin', + }, + { + name: 'Skype', + value: 'skype', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'URL', + value: 'url', + }, + { + name: 'Xing', + value: 'xing', + }, + { + name: 'YouTube', + value: 'youtube', + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, + { + displayName: 'Custom Properties', + name: 'customProperties', + type: 'fixedCollection', + description: 'Custom Properties', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.' + }, + { + displayName: 'Sub Type', + name: 'subtype', + type: 'string', + default: '', + description: 'Property sub type.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ], + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* company:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'company ID', + name: 'companyId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'ID of company to delete', + }, + + /* -------------------------------------------------------------------------- */ + /* company:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Company ID', + name: 'companyId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular company', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + description: 'Object of values to set as described here.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'email', + type: 'string', + default: '', + description: 'Company address.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Company email.', + }, + { + displayName: 'Star Value', + name: 'starValue', + type: 'options', + default: '', + description: 'Rating of company (Max value 5). This is not applicable for companies.', + options: [ + { + name: '0', + value: 0, + }, + { + name: '1', + value: 1, + }, + { + name: '2', + value: 2, + }, + { + name: '3', + value: 3, + }, + { + name: '4', + value: 4, + }, + { + name: '5', + value: 5, + }, + ], + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + default: [], + description: 'Unique identifiers added to company, for easy management of companys. This is not applicable for companies.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Company name.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Company phone.', + }, + { + displayName: 'Website', + name: 'websiteOptions', + type: 'fixedCollection', + description: 'Companys websites.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Website properties.', + name: 'websiteProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of website.', + options: [ + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Feed', + value: 'feed', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Github', + value: 'github', + }, + { + name: 'Google Plus', + value: 'googlePlus', + }, + { + name: 'LinkedIn', + value: 'linkedin', + }, + { + name: 'Skype', + value: 'skype', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'URL', + value: 'url', + }, + { + name: 'Xing', + value: 'xing', + }, + { + name: 'YouTube', + value: 'youtube', + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, + { + displayName: 'Custom Properties', + name: 'customProperties', + type: 'fixedCollection', + description: 'Custom Properties', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.', + }, + { + displayName: 'Sub Type', + name: 'subtype', + type: 'string', + default: '', + description: 'Property sub type.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ] + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts new file mode 100644 index 00000000000..5043e05b4a5 --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts @@ -0,0 +1,991 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new contact', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Get a contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all contacts', + }, + { + name: 'Update', + value: 'update', + description: 'Update contact properties', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const contactFields = [ + /* -------------------------------------------------------------------------- */ + /* contact:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular contact', + }, + /* -------------------------------------------------------------------------- */ + /* contact:get all */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 20, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + + /* -------------------------------------------------------------------------- */ + /* contact:create */ + /* -------------------------------------------------------------------------- */ + + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + + description: `Object of values to set as described here.`, + }, + + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'addressOptions', + type: 'fixedCollection', + description: 'Contacts address.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Address Properties', + name: 'addressProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of address.', + options: [ + { + name: 'Home', + value: 'home', + }, + { + name: 'Postal', + value: 'postal', + }, + { + name: 'Office', + value: 'office' + }, + ], + }, + { + displayName: 'Address', + name: 'address', + type: 'string', + required: true, + default: '', + description: 'Full address.', + }, + ], + }, + ], + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + default: '', + description: 'Company Name.', + }, + { + displayName: 'Email', + name: 'emailOptions', + type: 'fixedCollection', + description: 'Contact email.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Email Properties', + name: 'emailProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of Email', + options: [ + { + name: 'Work', + value: 'work', + }, + { + name: 'Personal', + value: 'personal', + }, + ], + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + description: 'Email', + }, + ], + }, + ], + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: 'Contact first name.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Contact last name.', + }, + { + displayName: 'Lead Score', + name: 'leadScore', + type: 'number', + default: '', + description: 'Lead score of contact', + typeOptions: { + minValue: 0, + }, + }, + { + displayName: 'Star Value', + name: 'starValue', + type: 'options', + default: '', + description: 'Rating of contact (Max value 5). This is not applicable for companies.', + options: [ + { + name: '0', + value: 0, + }, + { + name: '1', + value: 1, + }, + { + name: '2', + value: 2, + }, + { + name: '3', + value: 3, + }, + { + name: '4', + value: 4, + }, + { + name: '5', + value: 5, + }, + ], + }, + { + displayName: 'Phone', + name: 'phoneOptions', + type: 'fixedCollection', + description: 'Contacts phone.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Phone properties', + name: 'phoneProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of phone number.', + options: [ + { + name: 'Home', + value: 'home', + }, + { + name: 'Home Fax', + value: 'homeFax', + }, + { + name: 'Main', + value: 'main', + }, + { + name: 'Mobile', + value: 'mobile', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Work Fax', + value: 'workFax', + }, + { + name: 'Work', + value: 'work', + }, + ], + }, + { + displayName: 'Number', + name: 'number', + type: 'string', + required: true, + default: '', + description: 'Phone number.', + }, + ], + }, + ], + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + default: [], + description: 'Unique identifiers added to contact, for easy management of contacts. This is not applicable for companies.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Professional title.', + }, + { + displayName: 'Website', + name: 'websiteOptions', + type: 'fixedCollection', + description: 'Contacts websites.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Website properties.', + name: 'websiteProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of website.', + options: [ + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Feed', + value: 'feed', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Github', + value: 'github', + }, + { + name: 'Google Plus', + value: 'googlePlus', + }, + { + name: 'LinkedIn', + value: 'linkedin', + }, + { + name: 'Skype', + value: 'skype', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'URL', + value: 'url', + }, + { + name: 'Xing', + value: 'xing', + }, + { + name: 'YouTube', + value: 'youtube', + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, + { + displayName: 'Custom Properties', + name: 'customProperties', + type: 'fixedCollection', + description: 'Custom Properties', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.', + }, + { + displayName: 'Sub Type', + name: 'subtype', + type: 'string', + default: '', + description: 'Property sub type.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ], + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* contact:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Id of contact to delete.', + }, + + /* -------------------------------------------------------------------------- */ + /* contact:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular contact', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Object of values to set as described here.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'addressOptions', + type: 'fixedCollection', + description: 'Contacts address.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Address Properties', + name: 'addressProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of address.', + options: [ + { + name: 'Home', + value: 'home' + }, + { + name: 'Office', + value: 'office' + }, + { + name: 'Postal', + value: 'postal' + }, + ], + }, + { + displayName: 'Address', + name: 'address', + type: 'string', + required: true, + default: '', + description: 'Full address.', + }, + ], + }, + ], + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + default: '', + description: 'Company Name.', + }, + { + displayName: 'Email', + name: 'emailOptions', + type: 'fixedCollection', + description: 'Contact email.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Email Properties', + name: 'emailProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of Email', + options: [ + { + name: 'Work', + value: 'work', + }, + { + name: 'Personal', + value: 'personal', + } + ] + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + description: 'Email', + }, + ], + }, + ], + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: 'Contact first name.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Contact last name.', + }, + { + displayName: 'Lead Score', + name: 'leadScore', + type: 'number', + default: '', + description: 'Lead score of contact', + typeOptions: { + minValue: 0, + } + }, + { + displayName: 'Star Value', + name: 'starValue', + type: 'options', + default: '', + description: 'Rating of contact (Max value 5). This is not applicable for companies.', + options: [ + { + name: '0', + value: 0, + }, + { + name: '1', + value: 1, + }, + { + name: '2', + value: 2, + }, + { + name: '3', + value: 3, + }, + { + name: '4', + value: 4, + }, + { + name: '5', + value: 5, + }, + ], + }, + { + displayName: 'Phone', + name: 'phoneOptions', + type: 'fixedCollection', + description: 'Contacts phone.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Phone properties', + name: 'phoneProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of phone number.', + options: [ + { + name: 'Home', + value: 'home', + }, + { + name: 'Home Fax', + value: 'homeFax', + }, + { + name: 'Main', + value: 'main', + }, + { + name: 'Mobile', + value: 'mobile', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Work Fax', + value: 'workFax', + }, + { + name: 'Work', + value: 'work', + }, + ], + }, + { + displayName: 'Number', + name: 'number', + type: 'string', + required: true, + default: '', + description: 'Phone number.', + }, + ], + }, + ], + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + default: [], + description: 'Unique identifiers added to contact, for easy management of contacts. This is not applicable for companies.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Professional title.', + }, + { + displayName: 'Website', + name: 'websiteOptions', + type: 'fixedCollection', + description: 'Contacts websites.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Website properties.', + name: 'websiteProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: '', + description: 'Type of website.', + options: [ + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Feed', + value: 'feed', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Github', + value: 'github', + }, + { + name: 'Google Plus', + value: 'googlePlus', + }, + { + name: 'LinkedIn', + value: 'linkedin', + }, + { + name: 'Skype', + value: 'skype', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'URL', + value: 'url', + }, + { + name: 'Xing', + value: 'xing', + }, + { + name: 'YouTube', + value: 'youtube', + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, + { + displayName: 'Custom Properties', + name: 'customProperties', + type: 'fixedCollection', + description: 'Custom Properties', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.' + }, + { + displayName: 'Sub Type', + name: 'subtype', + type: 'string', + default: '', + description: 'Property sub type.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts b/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts new file mode 100644 index 00000000000..3a18ed3e154 --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts @@ -0,0 +1,26 @@ +import { + IDataObject, +} from 'n8n-workflow'; + +export interface IProperty { + type: string; + name: string; + subtype?: string; + value?: string; +} + +export interface IContact { + type?: string; + star_value?: string; + lead_score?: string; + tags?: string[]; + properties?: IDataObject[]; +} + +export interface IContactUpdate { + id: string; + properties?: IDataObject[]; + star_value?: string; + lead_score?: string; + tags?: string[]; +} diff --git a/packages/nodes-base/nodes/AgileCrm/DealDescription.ts b/packages/nodes-base/nodes/AgileCrm/DealDescription.ts new file mode 100644 index 00000000000..443017cfd6e --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/DealDescription.ts @@ -0,0 +1,515 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const dealOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'deal', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new deal', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a deal', + }, + { + name: 'Get', + value: 'get', + description: 'Get a deal', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all deals', + }, + { + name: 'Update', + value: 'update', + description: 'Update deal properties', + }, + + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const dealFields = [ + /* -------------------------------------------------------------------------- */ + /* deal:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular deal', + }, + + + /* -------------------------------------------------------------------------- */ + /* deal:get all */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 20, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + + + /* -------------------------------------------------------------------------- */ + /* deal:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Close Date', + name: 'closeDate', + type: 'dateTime', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Closing date of deal.', + }, + { + displayName: 'Expected Value', + name: 'expectedValue', + type: 'number', + required: true, + typeOptions: { + minValue: 0, + maxValue: 1000000000000 + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: 1, + description: 'Expected Value of deal.', + }, + { + displayName: 'Milestone', + name: 'milestone', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Milestone of deal.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Name of deal.', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + required: true, + typeOptions: { + minValue: 0, + maxValue: 100 + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: 50, + description: 'Expected probability.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Object of values to set as described here.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Contact Ids', + name: 'contactIds', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add ID', + }, + default: [], + description: 'Unique contact identifiers.', + }, + { + displayName: 'Custom Data', + name: 'customData', + type: 'fixedCollection', + description: 'Custom Data', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.' + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ], + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* deal:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'ID of deal to delete', + }, + + /* -------------------------------------------------------------------------- */ + /* deal:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Id of deal to update', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + + description: `Object of values to set as described here.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Expected Value', + name: 'expectedValue', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 10000 + }, + default: '', + description: 'Expected Value of deal.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of deal.', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 100 + }, + default: 50, + description: 'Expected Value of deal.', + }, + { + displayName: 'Contact Ids', + name: 'contactIds', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add ID', + }, + default: [], + description: 'Unique contact identifiers.', + }, + { + displayName: 'Custom Data', + name: 'customData', + type: 'fixedCollection', + description: 'Custom Data', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Property name.' + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Property value.', + }, + ], + }, + ], + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/DealInterface.ts b/packages/nodes-base/nodes/AgileCrm/DealInterface.ts new file mode 100644 index 00000000000..5b34f9fb7de --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/DealInterface.ts @@ -0,0 +1,15 @@ +export interface IDealCustomProperty { + name: string; + value: string; +} + +export interface IDeal { + id?: number; + expected_value?: number; + probability?: number; + name?: string; + close_date?: number; + milestone?: string; + contactIds?: string[]; + customData?: IDealCustomProperty[]; +} diff --git a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts new file mode 100644 index 00000000000..8be59de7f55 --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts @@ -0,0 +1,132 @@ +import { + OptionsWithUri +} from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; +import { IContactUpdate } from './ContactInterface'; + + +export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('agileCrmApi'); + const options: OptionsWithUri = { + method, + headers: { + 'Accept': 'application/json', + }, + auth: { + username: credentials!.email as string, + password: credentials!.apiKey as string + }, + uri: uri || `https://n8nio.agilecrm.com/dev/${endpoint}`, + json: true, + }; + + // Only add Body property if method not GET or DELETE to avoid 400 response + if (method !== 'GET' && method !== 'DELETE') { + options.body = body; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error(`AgileCRM error response: ${error.message}`); + } + +} + +export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any + const baseUri = 'https://n8nio.agilecrm.com/dev/'; + const credentials = this.getCredentials('agileCrmApi'); + const options: OptionsWithUri = { + method, + headers: { + 'Accept': 'application/json', + }, + body: { id: body.id }, + auth: { + username: credentials!.email as string, + password: credentials!.apiKey as string, + }, + uri: uri || baseUri, + json: true, + }; + + const successfulUpdates = []; + let lastSuccesfulUpdateReturn: any; // tslint:disable-line:no-any + const payload: IContactUpdate = body; + + try { + // Due to API, we must update each property separately. For user it looks like one seamless update + if (payload.properties) { + options.body.properties = payload.properties; + options.uri = baseUri + 'api/contacts/edit-properties'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + // Iterate trough properties and show them as individial updates instead of only vague "properties" + payload.properties?.map((property: any) => { // tslint:disable-line:no-any + successfulUpdates.push(`${property.name}`); + }); + + delete options.body.properties; + } + if (payload.lead_score) { + options.body.lead_score = payload.lead_score; + options.uri = baseUri + 'api/contacts/edit/lead-score'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + successfulUpdates.push('lead_score'); + + delete options.body.lead_score; + } + if (body.tags) { + options.body.tags = payload.tags; + options.uri = baseUri + 'api/contacts/edit/tags'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + payload.tags?.map((tag: string) => { + successfulUpdates.push(`(Tag) ${tag}`); + }); + + delete options.body.tags; + } + if (body.star_value) { + options.body.star_value = payload.star_value; + options.uri = baseUri + 'api/contacts/edit/add-star'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + successfulUpdates.push('star_value'); + + delete options.body.star_value; + } + + return lastSuccesfulUpdateReturn; + + } catch (error) { + if (successfulUpdates.length === 0) { + throw new Error(`AgileCRM error response: ${error.message}`); + } else { + throw new Error(`Not all properties updated. Updated properties: ${successfulUpdates.join(', ')} \n \nAgileCRM error response: ${error.message}`); + } + } + +} + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/AgileCrm/agilecrm.png b/packages/nodes-base/nodes/AgileCrm/agilecrm.png new file mode 100644 index 00000000000..711d2dc3b14 Binary files /dev/null and b/packages/nodes-base/nodes/AgileCrm/agilecrm.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 8efbb1bbd26..81da8bb4b75 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -27,6 +27,7 @@ "n8n": { "credentials": [ "dist/credentials/ActiveCampaignApi.credentials.js", + "dist/credentials/AgileCrmApi.credentials.js", "dist/credentials/AcuitySchedulingApi.credentials.js", "dist/credentials/AirtableApi.credentials.js", "dist/credentials/Amqp.credentials.js", @@ -116,6 +117,7 @@ "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", + "dist/nodes/AgileCrm/AgileCrm.node.js", "dist/nodes/Airtable/Airtable.node.js", "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", "dist/nodes/Amqp/Amqp.node.js",