From 63c96497be9f4ea88f8a20104a880d1bb5534042 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 20 Jan 2020 22:00:27 -0500 Subject: [PATCH 1/3] :sparkles: salesmate node --- .../credentials/SalesmateApi.credentials.ts | 24 + .../nodes/Salesmate/CompanyDescription.ts | 724 ++++++++++++++++++ .../nodes/Salesmate/CompanyInterface.ts | 22 + .../nodes/Salesmate/GenericFunctions.ts | 71 ++ .../nodes/Salesmate/Salesmate.node.ts | 307 ++++++++ .../nodes-base/nodes/Salesmate/salesmate.png | Bin 0 -> 1538 bytes packages/nodes-base/package.json | 2 + 7 files changed, 1150 insertions(+) create mode 100644 packages/nodes-base/credentials/SalesmateApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Salesmate/CompanyDescription.ts create mode 100644 packages/nodes-base/nodes/Salesmate/CompanyInterface.ts create mode 100644 packages/nodes-base/nodes/Salesmate/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Salesmate/Salesmate.node.ts create mode 100644 packages/nodes-base/nodes/Salesmate/salesmate.png diff --git a/packages/nodes-base/credentials/SalesmateApi.credentials.ts b/packages/nodes-base/credentials/SalesmateApi.credentials.ts new file mode 100644 index 0000000000..a041f9e41c --- /dev/null +++ b/packages/nodes-base/credentials/SalesmateApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SalesmateApi implements ICredentialType { + name = 'salesmateApi'; + displayName = 'Salesmate API'; + properties = [ + { + displayName: 'Session Token', + name: 'sessionToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'n8n.salesmate.io', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts b/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts new file mode 100644 index 0000000000..a2dbc2e762 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts @@ -0,0 +1,724 @@ +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 company', + }, + { + name: 'Update', + value: 'update', + description: 'Update a company', + }, + { + name: 'Get', + value: 'get', + description: 'Get a company', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all companies', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a company', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const companyFields = [ + +/* -------------------------------------------------------------------------- */ +/* company:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Other Phone', + name: 'otherPhone', + type: 'string', + default: '', + }, + { + displayName: 'Facebook Handle', + name: 'facebookHandle', + type: 'string', + default: '', + }, + { + displayName: 'Google Plus Handle', + name: 'googlePlusHandle', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn Handle', + name: 'linkedInHandle', + type: 'string', + default: '', + }, + { + displayName: 'Skype ID', + name: 'skypeId', + type: 'string', + default: '', + }, + { + displayName: 'Twitter Handle', + name: 'twitterHandle', + type: 'string', + default: '', + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + }, + { + displayName: 'Billing Address Line 1', + name: 'billingAddressLine1', + type: 'string', + default: '', + }, + { + displayName: 'Billing Address Line 2', + name: 'billingAddressLine2', + type: 'string', + default: '', + }, + { + displayName: 'Billing City', + name: 'billingCity', + type: 'string', + default: '', + }, + { + displayName: 'Billing Zip Code', + name: 'billingZipCode', + type: 'string', + default: '', + }, + { + displayName: 'Billing State', + name: 'billingState', + type: 'string', + default: '', + }, + { + displayName: 'Billing Country', + name: 'billingState', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* company:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Company ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + }, + }, + description: 'company ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Other Phone', + name: 'otherPhone', + type: 'string', + default: '', + }, + { + displayName: 'Facebook Handle', + name: 'facebookHandle', + type: 'string', + default: '', + }, + { + displayName: 'Google Plus Handle', + name: 'googlePlusHandle', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn Handle', + name: 'linkedInHandle', + type: 'string', + default: '', + }, + { + displayName: 'Skype ID', + name: 'skypeId', + type: 'string', + default: '', + }, + { + displayName: 'Twitter Handle', + name: 'twitterHandle', + type: 'string', + default: '', + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + }, + { + displayName: 'Billing Address Line 1', + name: 'billingAddressLine1', + type: 'string', + default: '', + }, + { + displayName: 'Billing Address Line 2', + name: 'billingAddressLine2', + type: 'string', + default: '', + }, + { + displayName: 'Billing City', + name: 'billingCity', + type: 'string', + default: '', + }, + { + displayName: 'Billing Zip Code', + name: 'billingZipCode', + type: 'string', + default: '', + }, + { + displayName: 'Billing State', + name: 'billingState', + type: 'string', + default: '', + }, + { + displayName: 'Billing Country', + name: 'billingState', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + }, + ], + + }, +/* -------------------------------------------------------------------------- */ +/* company:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Company ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'get', + ], + }, + }, + description: 'company ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, +/* -------------------------------------------------------------------------- */ +/* company:getAll */ +/* -------------------------------------------------------------------------- */ + { + 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', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 25, + }, + default: 10, + description: 'How many results to return.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'company', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'string', + default: '', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'Asc', + value: 'asc', + }, + { + name: 'Desc', + value: 'desc', + }, + ], + default: 'desc', + description: 'Sort order', + } + ], + }, + { + displayName: 'Filters', + name: 'filtersJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'company', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + placeholder: 'Add filter', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + false, + ], + }, + }, + default: {}, + options: [ + { + name: 'filtersUi', + displayName: 'Filters', + values: [ + { + displayName: 'Operator', + name: 'operator', + type: 'options', + options: [ + { + name: 'And', + value: 'AND', + }, + { + name: 'Or', + value: 'OR', + }, + ], + default: 'AND', + }, + { + displayName: 'Conditions', + name: 'conditions', + placeholder: 'Add Condition', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'conditionsUi', + displayName: 'Conditions', + values: [ + { + displayName: 'Field', + name: 'field', + type: 'options', + options: [ + { + name: 'Name', + value: 'name', + }, + { + name: 'Email', + value: 'email', + }, + { + name: 'Phone', + value: 'phone', + }, + ], + default: 'name', + }, + { + displayName: 'Condition', + name: 'condition', + type: 'options', + options: [ + { + name: 'Equals', + value: 'EQUALS', + }, + { + name: 'Not Equals', + value: 'NOT_EQUALS', + }, + { + name: 'Empty', + value: 'EMPTY', + }, + { + name: 'Not Empty', + value: 'NOT_EMPTY', + }, + { + name: 'CONTAINS', + value: 'Contains', + }, + { + name: 'Does Not Contains', + value: 'DOES_NOT_CONTAINS', + }, + { + name: 'Starts With', + value: 'STARTS_WITH', + }, + { + name: 'Ends With', + value: 'ENDS_WITH', + }, + ], + default: 'EQUALS', + description: 'Value of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + } + ] + }, + ], + }, + ] + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* company:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Company ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'If more than one company add them separated by ,', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Salesmate/CompanyInterface.ts b/packages/nodes-base/nodes/Salesmate/CompanyInterface.ts new file mode 100644 index 0000000000..52368cb116 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/CompanyInterface.ts @@ -0,0 +1,22 @@ +export interface ICompany { + name?: string; + owner?: number; + website?: string; + phone?: string; + otherPhone?: string; + googlePlusHandle?: string; + linkedInHandle?: string; + facebookHandle?: string; + linkedinHandle?: string; + skypeId?: string; + twitterHandle?: string; + currency?: string; + billingAddressLine1?: string; + billingAddressLine2?: string; + billingCity?: string; + billingZipCode?: string; + billingCountry?: string; + billingState?: string; + description?: string; + tags?: string; +} diff --git a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts new file mode 100644 index 0000000000..12199621f1 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts @@ -0,0 +1,71 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function salesmateApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('salesmateApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const options: OptionsWithUri = { + headers: { + 'sessionToken': credentials.sessionToken, + 'x-linkname': credentials.url, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://apis.salesmate.io${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.body; + } + console.log(JSON.stringify(options.body)) + // console.log(options.body.query.group.rules) + // console.log(options.body.query.group.operator) + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('Salesmate Error: ' + error); + } +} + +export async function salesmateApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.pageNo = 1; + query.rows = 25; + do { + responseData = await salesmateApiRequest.call(this, method, resource, body, query); + returnData.push.apply(returnData, responseData[propertyName].data); + query.pageNo++; + } while ( + responseData[propertyName].totalPages !== undefined && + query.pageNo <= responseData[propertyName].totalPages + ); + + return returnData; +} + + +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/Salesmate/Salesmate.node.ts b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts new file mode 100644 index 0000000000..f5c7b1d810 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts @@ -0,0 +1,307 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { + salesmateApiRequest, + salesmateApiRequestAllItems, + validateJSON, +} from './GenericFunctions'; +import { + companyFields, + companyOperations, +} from './CompanyDescription'; +import { + ICompany, + } from './CompanyInterface'; + +export class Salesmate implements INodeType { + description: INodeTypeDescription = { + displayName: 'Salesmate', + name: 'salesmate', + icon: 'file:salesmate.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', + description: 'Consume Salesmate API', + defaults: { + name: 'Salesmate', + color: '#004ef6', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'salesmateApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Company', + value: 'company', + }, + ], + default: 'company', + description: 'Resource to consume.', + }, + ...companyOperations, + ...companyFields, + ], + }; + + 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 salesmateApiRequest.call(this, 'GET', '/v1/users/active'); + for (const user of users.Data) { + const userName = user.nickname; + const userId = user.id; + returnData.push({ + name: userName, + value: userId, + }); + } + 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 === 'company') { + if (operation === 'create') { + const owner = this.getNodeParameter('owner', i) as number; + const name = this.getNodeParameter('name', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: ICompany = { + name, + owner, + }; + if (additionalFields.website) { + body.website = additionalFields.website as string; + } + if (additionalFields.phone) { + body.phone = additionalFields.phone as string; + } + if (additionalFields.otherPhone) { + body.otherPhone = additionalFields.otherPhone as string; + } + if (additionalFields.facebookHandle) { + body.facebookHandle = additionalFields.facebookHandle as string; + } + if (additionalFields.googlePlusHandle) { + body.googlePlusHandle = additionalFields.googlePlusHandle as string; + } + if (additionalFields.linkedInHandle) { + body.linkedInHandle = additionalFields.linkedInHandle as string; + } + if (additionalFields.skypeId) { + body.skypeId = additionalFields.skypeId as string; + } + if (additionalFields.twitterHandle) { + body.twitterHandle = additionalFields.twitterHandle as string; + } + if (additionalFields.currency) { + body.currency = additionalFields.currency as string; + } + if (additionalFields.billingAddressLine1) { + body.billingAddressLine1 = additionalFields.billingAddressLine1 as string; + } + if (additionalFields.billingAddressLine2) { + body.billingAddressLine2 = additionalFields.billingAddressLine2 as string; + } + if (additionalFields.billingCity) { + body.billingCity = additionalFields.billingCity as string; + } + if (additionalFields.billingZipCode) { + body.billingZipCode = additionalFields.billingZipCode as string; + } + if (additionalFields.billingState) { + body.billingState = additionalFields.billingState as string; + } + if (additionalFields.description) { + body.description = additionalFields.description as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string; + } + responseData = await salesmateApiRequest.call(this, 'POST', '/v1/companies', body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'update') { + const companyId = this.getNodeParameter('id', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const body: ICompany = {}; + if (updateFields.owner) { + body.owner = updateFields.owner as number; + } + if (updateFields.name) { + body.name = updateFields.name as string; + } + if (updateFields.website) { + body.website = updateFields.website as string; + } + if (updateFields.phone) { + body.phone = updateFields.phone as string; + } + if (updateFields.otherPhone) { + body.otherPhone = updateFields.otherPhone as string; + } + if (updateFields.facebookHandle) { + body.facebookHandle = updateFields.facebookHandle as string; + } + if (updateFields.googlePlusHandle) { + body.googlePlusHandle = updateFields.googlePlusHandle as string; + } + if (updateFields.linkedInHandle) { + body.linkedInHandle = updateFields.linkedInHandle as string; + } + if (updateFields.skypeId) { + body.skypeId = updateFields.skypeId as string; + } + if (updateFields.twitterHandle) { + body.twitterHandle = updateFields.twitterHandle as string; + } + if (updateFields.currency) { + body.currency = updateFields.currency as string; + } + if (updateFields.billingAddressLine1) { + body.billingAddressLine1 = updateFields.billingAddressLine1 as string; + } + if (updateFields.billingAddressLine2) { + body.billingAddressLine2 = updateFields.billingAddressLine2 as string; + } + if (updateFields.billingCity) { + body.billingCity = updateFields.billingCity as string; + } + if (updateFields.billingZipCode) { + body.billingZipCode = updateFields.billingZipCode as string; + } + if (updateFields.billingState) { + body.billingState = updateFields.billingState as string; + } + if (updateFields.description) { + body.description = updateFields.description as string; + } + if (updateFields.tags) { + body.tags = updateFields.tags as string; + } + responseData = await salesmateApiRequest.call(this, 'PUT', `/v1/companies/${companyId}`, body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'get') { + const companyId = this.getNodeParameter('id', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + responseData = await salesmateApiRequest.call(this, 'GET', `/v1/companies/${companyId}`); + responseData = responseData.Data; + if (!rawData) { + responseData = responseData.map((company: IDataObject) => { + const aux: IDataObject = {}; + aux[company.fieldName as string] = company.value; + return aux; + }); + } + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + let body: IDataObject = { + query: { + group: { + }, + }, + }; + if (options.sortBy) { + qs.sortBy = options.sortBy as string; + } + if (options.sortOrder) { + qs.sortOrder = options.sortOrder as string; + } + if (options.fields) { + body.fields = (options.fields as string).split(',') as string[]; + } else { + throw new Error('You have to add at least one field'); + } + if (!jsonActive) { + const filters: IDataObject[] = []; + const filtersUi = (this.getNodeParameter('filters', i) as IDataObject).filtersUi as IDataObject; + if (filtersUi.conditions) { + const conditions = filtersUi.conditions as IDataObject; + if (conditions.conditionsUi) { + for (const condition of conditions.conditionsUi as IDataObject[]) { + console.log(condition) + const filter: IDataObject = {}; + filter.moduleName = 'Company'; + filter.field = { + fieldName: condition.field, + }; + filter.condition = condition.condition; + filter.data = condition.value; + filters.push(filter) + } + } + } + //@ts-ignore + body.query.group = { + operator: filtersUi.operator, + rules: filters, + }; + } else { + const json = validateJSON(this.getNodeParameter('filtersJson', i) as string); + body = json; + } + if (returnAll) { + responseData = await salesmateApiRequestAllItems.call(this, 'Data', 'POST', '/v2/companies/search', body, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.rows = limit; + responseData = await salesmateApiRequest.call(this, 'POST', '/v2/companies/search', body, qs); + responseData = responseData.Data.data; + } + } + if (operation === 'delete') { + const companyId = parseInt(this.getNodeParameter('id', i) as string, 10); + responseData = await salesmateApiRequest.call(this, 'DELETE', `/v1/companies/${companyId}`); + } + } + 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/Salesmate/salesmate.png b/packages/nodes-base/nodes/Salesmate/salesmate.png new file mode 100644 index 0000000000000000000000000000000000000000..3307335968ae09d1948ab8ff11cf6f188294766b GIT binary patch literal 1538 zcmZ`(dr;J66up3m@>UU+5*2J0h()&*0l|%B*@ayeHbC+PtFXYL1cJcwN)wP^DjmTJ zb;SlvEE#lSOvg-o0zLpyQ6U6%G#_JF{bMzj=l1i@{^*=O=^Sg0oP68hZMfmVo@>9gE9@+;@#h3O^m(gbH$GS8#SYuPWq+ zkNCzgC#^+R8JjGu%>>QIq$s>?Kwu*5qd*G~p9OOzr{v-KFfY$%oe3m^M+|1iVelp= zuf>hqtX=~Wg94y3;_49Vvq6h+Yn0oLu(b_k_4sHTTE4;V11N7m$rkK6h_=Jr+s}p7 zymCET>T&HBH|<6G24t4vz!5I2;f`ZW?x4l!>gMp}ASa;>{CJ*e7HB$-9Ot*<;hz9U zCF*uC&B20X%!$JsEo8zRT=CrrmLx!|$D6S@d7dSDh}00rgM!g_ohb^wIuzIOr9mbM zPIt4v9-eB5H1O4e7NV$@yDqR#ECLeXB8+q{$PF&h==h#bUEr`~AZO%M^5J8=Vm)8K z#f>eJsk!L2wZ}N8`#h9=pdgV({=W-?`7i8BsS+2=25gO))Gb<>L|aYhu#JsmF_Q zkNOJ4p;VWsaroKB!_&vFsq1Ok>wICcA=PGZf4Nogw_c}?PSsw_$`ILqo#C<7HqS0r zV)mTdbEUuDJuO56dFb4CcBu4s%={n69($~gQbT> z9R>N%Cw#hR?QJo3r;kg=6id82PEAal+O+Z2#(z5Vnp2ydr>H}v0V|@^Q8%aP0;FTR zqHOovz4YxP2#yxi=4*3pwM>w6EHvpYSXx4*m{sz~j6 ze$J=GwAVPU(3+yVYE7n1=2u$Pj0)wI2#G;ux!QesX?J+qwx%JshW7h64-Fk0R8E(N z{JKm2<gOnq-Ube7*jFXX*2G`uuCRDAg7^C*%!yUyNGJTr2m+W6wz?N2)& z`WtMWwk&O!Fupe&S(jNJes6c~*TIr#Q=Po7`()5!Xv z@T;-s0z~%nrV;ILYfk7N*F5fl_=uQONvzrgnk*M31jI*mbnM3qtdFE};TnE(I) literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index df87d196ce..9cd7f397c5 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -73,6 +73,7 @@ "dist/credentials/SlackApi.credentials.js", "dist/credentials/Smtp.credentials.js", "dist/credentials/StripeApi.credentials.js", + "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", @@ -170,6 +171,7 @@ "dist/nodes/Start.node.js", "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Switch.node.js", + "dist/nodes/Salesmate/Salesmate.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", "dist/nodes/Todoist/Todoist.node.js", From 434da06fa734ebd8fc453cdebbac074dc118a415 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 21 Jan 2020 08:31:30 -0500 Subject: [PATCH 2/3] :zap: small fix --- packages/nodes-base/nodes/Salesmate/GenericFunctions.ts | 5 ----- packages/nodes-base/nodes/Salesmate/Salesmate.node.ts | 1 - 2 files changed, 6 deletions(-) diff --git a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts index 12199621f1..4546113164 100644 --- a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts @@ -27,12 +27,7 @@ export async function salesmateApiRequest(this: IHookFunctions | IExecuteFunctio json: true }; if (!Object.keys(body).length) { - delete options.body; } - console.log(JSON.stringify(options.body)) - // console.log(options.body.query.group.rules) - // console.log(options.body.query.group.operator) - try { return await this.helpers.request!(options); } catch (error) { diff --git a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts index f5c7b1d810..62be08e7e7 100644 --- a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts +++ b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts @@ -261,7 +261,6 @@ export class Salesmate implements INodeType { const conditions = filtersUi.conditions as IDataObject; if (conditions.conditionsUi) { for (const condition of conditions.conditionsUi as IDataObject[]) { - console.log(condition) const filter: IDataObject = {}; filter.moduleName = 'Company'; filter.field = { From 5d89998eb129f990468c4adaf4e43b701edc5f87 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 21 Jan 2020 11:00:40 -0500 Subject: [PATCH 3/3] :zap: added lead and activity resources --- .../nodes/Salesmate/ActivityDescription.ts | 627 ++++++++++++ .../nodes/Salesmate/ActivityInterface.ts | 11 + .../nodes/Salesmate/DealDescription.ts | 895 ++++++++++++++++++ .../nodes/Salesmate/DealInterface.ts | 16 + .../nodes/Salesmate/GenericFunctions.ts | 1 + .../nodes/Salesmate/Salesmate.node.ts | 408 +++++++- 6 files changed, 1951 insertions(+), 7 deletions(-) create mode 100644 packages/nodes-base/nodes/Salesmate/ActivityDescription.ts create mode 100644 packages/nodes-base/nodes/Salesmate/ActivityInterface.ts create mode 100644 packages/nodes-base/nodes/Salesmate/DealDescription.ts create mode 100644 packages/nodes-base/nodes/Salesmate/DealInterface.ts diff --git a/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts b/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts new file mode 100644 index 0000000000..fe737729a7 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts @@ -0,0 +1,627 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const activityOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'activity', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a activity', + }, + { + name: 'Update', + value: 'update', + description: 'Update a activity', + }, + { + name: 'Get', + value: 'get', + description: 'Get a activity', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all companies', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a activity', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const activityFields = [ + +/* -------------------------------------------------------------------------- */ +/* activity:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + description: 'This field displays activity type such as call, meeting etc.', + required: true, + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'This field contains details related to the activity.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'This field contains tags associated with an activity', + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Expiry date of an activity.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'number', + default: '', + description: 'Time duration of an activity.', + }, + { + displayName: 'Is Calendar Invite', + name: 'isCalendarInvite', + type: 'boolean', + default: false, + description: 'This field is used to send calendar invite.', + }, + { + displayName: 'Is Completed', + name: 'isCompleted', + type: 'boolean', + default: false, + description: 'This field indicates whether the activity is completed or not.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* activity:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Activity ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'update', + ], + }, + }, + description: 'activity ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'update', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Owner', + name: 'owner', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'This field contains details related to the activity.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'This field contains tags associated with an activity', + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Expiry date of an activity.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'number', + default: '', + description: 'Time duration of an activity.', + }, + { + displayName: 'Is Calendar Invite', + name: 'isCalendarInvite', + type: 'boolean', + default: false, + description: 'This field is used to send calendar invite.', + }, + { + displayName: 'Is Completed', + name: 'isCompleted', + type: 'boolean', + default: false, + description: 'This field indicates whether the activity is completed or not.', + }, + ], + + }, +/* -------------------------------------------------------------------------- */ +/* activity:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Activity ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'get', + ], + }, + }, + description: 'activity ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, +/* -------------------------------------------------------------------------- */ +/* activity:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'activity', + ], + 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: [ + 'activity', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 25, + }, + default: 10, + description: 'How many results to return.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'activity', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'string', + default: '', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'Asc', + value: 'asc', + }, + { + name: 'Desc', + value: 'desc', + }, + ], + default: 'desc', + description: 'Sort order', + } + ], + }, + { + displayName: 'Filters', + name: 'filtersJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'activity', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + placeholder: 'Add filter', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + false, + ], + }, + }, + default: {}, + options: [ + { + name: 'filtersUi', + displayName: 'Filters', + values: [ + { + displayName: 'Operator', + name: 'operator', + type: 'options', + options: [ + { + name: 'And', + value: 'AND', + }, + { + name: 'Or', + value: 'OR', + }, + ], + default: 'AND', + }, + { + displayName: 'Conditions', + name: 'conditions', + placeholder: 'Add Condition', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'conditionsUi', + displayName: 'Conditions', + values: [ + { + displayName: 'Field', + name: 'field', + type: 'options', + options: [ + { + name: 'Title', + value: 'title', + }, + { + name: 'Tags', + value: 'tags', + }, + ], + default: 'title', + }, + { + displayName: 'Condition', + name: 'condition', + type: 'options', + options: [ + { + name: 'Equals', + value: 'EQUALS', + }, + { + name: 'Not Equals', + value: 'NOT_EQUALS', + }, + { + name: 'Empty', + value: 'EMPTY', + }, + { + name: 'Not Empty', + value: 'NOT_EMPTY', + }, + { + name: 'CONTAINS', + value: 'Contains', + }, + { + name: 'Does Not Contains', + value: 'DOES_NOT_CONTAINS', + }, + { + name: 'Starts With', + value: 'STARTS_WITH', + }, + { + name: 'Ends With', + value: 'ENDS_WITH', + }, + ], + default: 'EQUALS', + description: 'Value of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + } + ] + }, + ], + }, + ] + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* activity:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Activity ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'If more than one activity add them separated by ,', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Salesmate/ActivityInterface.ts b/packages/nodes-base/nodes/Salesmate/ActivityInterface.ts new file mode 100644 index 0000000000..ab671d0b73 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/ActivityInterface.ts @@ -0,0 +1,11 @@ +export interface IActivity { + title?: string; + owner?: number; + type?: string; + description?: string; + tags?: string; + dueDate?: number; + duration?: number; + isCalendarInvite?: boolean; + isCompleted?: boolean; +} diff --git a/packages/nodes-base/nodes/Salesmate/DealDescription.ts b/packages/nodes-base/nodes/Salesmate/DealDescription.ts new file mode 100644 index 0000000000..343c02b55a --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/DealDescription.ts @@ -0,0 +1,895 @@ +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 deal', + }, + { + name: 'Update', + value: 'update', + description: 'Update a deal', + }, + { + name: 'Get', + value: 'get', + description: 'Get a deal', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all companies', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a deal', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const dealFields = [ + +/* -------------------------------------------------------------------------- */ +/* deal:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Primary Contact', + name: 'primaryContact', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getContacts', + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + description: 'Primary contact for the deal.', + required: true, + }, + { + displayName: 'Pipeline', + name: 'pipeline', + type: 'options', + options: [ + { + name: 'Sales', + value: 'Sales', + }, + ], + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + default: 'Open', + options: [ + { + name: 'Open', + value: 'Open', + }, + { + name: 'Close', + value: 'Close', + }, + { + name: 'Lost', + value: 'Lost', + }, + ], + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Stage', + name: 'stage', + type: 'options', + default: '', + options: [ + { + name: 'New (Untouched)', + value: 'New (Untouched)', + }, + { + name: 'Contacted', + value: 'Contacted', + }, + { + name: 'Qualified', + value: 'Qualified', + }, + { + name: 'Proposal Presented', + value: 'Proposal Presented', + }, + { + name: 'In Negotiation', + value: 'In Negotiation', + }, + ], + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'This field contains details related to the deal.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'This field contains tags associated with an deal', + }, + { + displayName: 'Primary Company', + name: 'primaryCompany', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCompanies', + }, + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'options', + options: [ + { + name: 'Ads', + value: 'Ads', + }, + { + name: 'Referrals', + value: 'Referrals', + }, + { + name: 'Website', + value: 'Website', + }, + { + name: 'Word of mouth', + value: 'Word of mouth', + }, + ], + default: 'Ads', + }, + { + displayName: 'Estimated Close Date', + name: 'estimatedCloseDate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Deal Value', + name: 'dealValue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + default: 'Medium', + options: [ + { + name: 'High', + value: 'High', + }, + { + name: 'Medium', + value: 'Medium', + }, + { + name: 'Low', + value: 'Low', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* deal:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + description: 'deal ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Primary Contact', + name: 'primaryContact', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getContacts', + }, + }, + { + displayName: 'Pipeline', + name: 'pipeline', + type: 'options', + options: [ + { + name: 'Sales', + value: 'Sales', + }, + ], + default: '', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + default: 'Open', + options: [ + { + name: 'Open', + value: 'Open', + }, + { + name: 'Close', + value: 'Close', + }, + { + name: 'Lost', + value: 'Lost', + }, + ], + }, + { + displayName: 'Stage', + name: 'stage', + type: 'options', + default: '', + options: [ + { + name: 'New (Untouched)', + value: 'New (Untouched)', + }, + { + name: 'Contacted', + value: 'Contacted', + }, + { + name: 'Qualified', + value: 'Qualified', + }, + { + name: 'Proposal Presented', + value: 'Proposal Presented', + }, + { + name: 'In Negotiation', + value: 'In Negotiation', + }, + ], + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'This field contains details related to the deal.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'This field contains tags associated with an deal', + }, + { + displayName: 'Primary Company', + name: 'primaryCompany', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCompanies', + }, + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'options', + options: [ + { + name: 'Ads', + value: 'Ads', + }, + { + name: 'Referrals', + value: 'Referrals', + }, + { + name: 'Website', + value: 'Website', + }, + { + name: 'Word of mouth', + value: 'Word of mouth', + }, + ], + default: 'Ads', + }, + { + displayName: 'Estimated Close Date', + name: 'estimatedCloseDate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Deal Value', + name: 'dealValue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + default: 'Medium', + options: [ + { + name: 'High', + value: 'High', + }, + { + name: 'Medium', + value: 'Medium', + }, + { + name: 'Low', + value: 'Low', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* deal:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + description: 'deal ID', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `If the data should include the fields details`, + }, +/* -------------------------------------------------------------------------- */ +/* deal:getAll */ +/* -------------------------------------------------------------------------- */ + { + 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.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 25, + }, + default: 10, + description: 'How many results to return.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'deal', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'string', + default: '', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'Asc', + value: 'asc', + }, + { + name: 'Desc', + value: 'desc', + }, + ], + default: 'desc', + description: 'Sort order', + } + ], + }, + { + displayName: 'Filters', + name: 'filtersJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'deal', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + placeholder: 'Add filter', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + false, + ], + }, + }, + default: {}, + options: [ + { + name: 'filtersUi', + displayName: 'Filters', + values: [ + { + displayName: 'Operator', + name: 'operator', + type: 'options', + options: [ + { + name: 'And', + value: 'AND', + }, + { + name: 'Or', + value: 'OR', + }, + ], + default: 'AND', + }, + { + displayName: 'Conditions', + name: 'conditions', + placeholder: 'Add Condition', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'conditionsUi', + displayName: 'Conditions', + values: [ + { + displayName: 'Field', + name: 'field', + type: 'options', + options: [ + { + name: 'Title', + value: 'title', + }, + { + name: 'Tags', + value: 'tags', + }, + { + name: 'Last Communication Mode', + value: 'lastCommunicationMode', + }, + ], + default: 'title', + }, + { + displayName: 'Condition', + name: 'condition', + type: 'options', + options: [ + { + name: 'Equals', + value: 'EQUALS', + }, + { + name: 'Not Equals', + value: 'NOT_EQUALS', + }, + { + name: 'Empty', + value: 'EMPTY', + }, + { + name: 'Not Empty', + value: 'NOT_EMPTY', + }, + { + name: 'CONTAINS', + value: 'Contains', + }, + { + name: 'Does Not Contains', + value: 'DOES_NOT_CONTAINS', + }, + { + name: 'Starts With', + value: 'STARTS_WITH', + }, + { + name: 'Ends With', + value: 'ENDS_WITH', + }, + ], + default: 'EQUALS', + description: 'Value of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + } + ] + }, + ], + }, + ] + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* deal:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Deal ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'If more than one deal add them separated by ,', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Salesmate/DealInterface.ts b/packages/nodes-base/nodes/Salesmate/DealInterface.ts new file mode 100644 index 0000000000..7e4e1df106 --- /dev/null +++ b/packages/nodes-base/nodes/Salesmate/DealInterface.ts @@ -0,0 +1,16 @@ +export interface IDeal { + title?: string; + owner?: number; + pipeline?: string; + primaryContact?: number; + primaryCompany?: number; + status?: string; + stage?: string; + source?: string; + estimatedCloseDate?: string; + dealValue?: number; + currency?: string; + priority?: string; + description?: string; + tags?: string; +} diff --git a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts index 4546113164..a84dbc95e2 100644 --- a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts @@ -27,6 +27,7 @@ export async function salesmateApiRequest(this: IHookFunctions | IExecuteFunctio json: true }; if (!Object.keys(body).length) { + delete options.body; } try { return await this.helpers.request!(options); diff --git a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts index 62be08e7e7..a57b8b7410 100644 --- a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts +++ b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts @@ -18,9 +18,23 @@ import { companyFields, companyOperations, } from './CompanyDescription'; +import { + activityFields, + activityOperations, +} from './ActivityDescription'; import { ICompany, } from './CompanyInterface'; + import { + IActivity, + } from './ActivityInterface'; + import { + IDeal, + } from './DealInterface'; +import { + dealFields, + dealOperations, + } from './DealDescription'; export class Salesmate implements INodeType { description: INodeTypeDescription = { @@ -49,16 +63,28 @@ export class Salesmate implements INodeType { name: 'resource', type: 'options', options: [ + { + name: 'Activity', + value: 'activity', + }, { name: 'Company', value: 'company', }, + { + name: 'Deal', + value: 'deal', + }, ], - default: 'company', + default: 'activity', description: 'Resource to consume.', }, ...companyOperations, + ...activityOperations, + ...dealOperations, ...companyFields, + ...activityFields, + ...dealFields, ], }; @@ -79,6 +105,44 @@ export class Salesmate implements INodeType { } return returnData; }, + // Get all the available contacs to display them to user so that he can + // select them easily + async getContacts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = { + fields: ['name', 'id'], + query: {} + }; + const contacts = await salesmateApiRequest.call(this, 'POST', '/v2/contacts/search', qs); + for (const contact of contacts.Data.data) { + const contactName = contact.name; + const contactId = contact.id; + returnData.push({ + name: contactName, + value: contactId, + }); + } + return returnData; + }, + // Get all the available companies to display them to user so that he can + // select them easily + async getCompanies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = { + fields: ['name', 'id'], + query: {} + }; + const companies = await salesmateApiRequest.call(this, 'POST', '/v2/companies/search', qs); + for (const company of companies.Data.data) { + const companyName = company.name; + const companyId = company.id; + returnData.push({ + name: companyName, + value: companyId, + }); + } + return returnData; + }, }, }; @@ -257,7 +321,7 @@ export class Salesmate implements INodeType { if (!jsonActive) { const filters: IDataObject[] = []; const filtersUi = (this.getNodeParameter('filters', i) as IDataObject).filtersUi as IDataObject; - if (filtersUi.conditions) { + if (filtersUi && filtersUi.conditions) { const conditions = filtersUi.conditions as IDataObject; if (conditions.conditionsUi) { for (const condition of conditions.conditionsUi as IDataObject[]) { @@ -272,11 +336,13 @@ export class Salesmate implements INodeType { } } } - //@ts-ignore - body.query.group = { - operator: filtersUi.operator, - rules: filters, - }; + if (filtersUi && filtersUi.operator) { + //@ts-ignore + body.query.group = { + operator: filtersUi.operator, + rules: filters, + }; + } } else { const json = validateJSON(this.getNodeParameter('filtersJson', i) as string); body = json; @@ -295,6 +361,334 @@ export class Salesmate implements INodeType { responseData = await salesmateApiRequest.call(this, 'DELETE', `/v1/companies/${companyId}`); } } + if (resource === 'activity') { + if (operation === 'create') { + const owner = this.getNodeParameter('owner', i) as number; + const title = this.getNodeParameter('title', i) as string; + const type = this.getNodeParameter('type', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IActivity = { + title, + owner, + type, + }; + if (additionalFields.dueDate) { + body.dueDate = new Date(additionalFields.dueDate as string).getTime(); + } + if (additionalFields.duration) { + body.duration = additionalFields.duration as number; + } + if (additionalFields.isCalendarInvite) { + body.isCalendarInvite = additionalFields.isCalendarInvite as boolean; + } + if (additionalFields.isCompleted) { + body.isCompleted = additionalFields.isCompleted as boolean; + } + if (additionalFields.description) { + body.description = additionalFields.description as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string; + } + responseData = await salesmateApiRequest.call(this, 'POST', '/v1/activities', body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'update') { + const activityId = this.getNodeParameter('id', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IActivity = {}; + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.type) { + body.type = updateFields.type as string; + } + if (updateFields.owner) { + body.owner = updateFields.owner as number; + } + if (updateFields.dueDate) { + body.dueDate = new Date(updateFields.dueDate as string).getTime(); + } + if (updateFields.duration) { + body.duration = updateFields.duration as number; + } + if (updateFields.isCalendarInvite) { + body.isCalendarInvite = updateFields.isCalendarInvite as boolean; + } + if (updateFields.isCompleted) { + body.isCompleted = updateFields.isCompleted as boolean; + } + if (updateFields.description) { + body.description = updateFields.description as string; + } + if (updateFields.tags) { + body.tags = updateFields.tags as string; + } + responseData = await salesmateApiRequest.call(this, 'PUT', `/v1/activities/${activityId}`, body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'get') { + const activityId = this.getNodeParameter('id', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + responseData = await salesmateApiRequest.call(this, 'GET', `/v1/activities/${activityId}`); + responseData = responseData.Data; + if (!rawData) { + responseData = responseData.map((activity: IDataObject) => { + const aux: IDataObject = {}; + aux[activity.fieldName as string] = activity.value; + return aux; + }); + } + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + let body: IDataObject = { + query: { + group: { + }, + }, + }; + if (options.sortBy) { + qs.sortBy = options.sortBy as string; + } + if (options.sortOrder) { + qs.sortOrder = options.sortOrder as string; + } + if (options.fields) { + body.fields = (options.fields as string).split(',') as string[]; + } else { + throw new Error('You have to add at least one field'); + } + if (!jsonActive) { + const filters: IDataObject[] = []; + const filtersUi = (this.getNodeParameter('filters', i) as IDataObject).filtersUi as IDataObject; + if (filtersUi && filtersUi.conditions) { + const conditions = filtersUi.conditions as IDataObject; + if (conditions.conditionsUi) { + for (const condition of conditions.conditionsUi as IDataObject[]) { + const filter: IDataObject = {}; + filter.moduleName = 'Task'; + filter.field = { + fieldName: condition.field, + }; + filter.condition = condition.condition; + filter.data = condition.value; + filters.push(filter) + } + } + } + if (filtersUi && filtersUi.operator) { + //@ts-ignore + body.query.group = { + operator: filtersUi.operator, + rules: filters, + }; + } + } else { + const json = validateJSON(this.getNodeParameter('filtersJson', i) as string); + body = json; + } + if (returnAll) { + responseData = await salesmateApiRequestAllItems.call(this, 'Data', 'POST', '/v2/activities/search', body, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.rows = limit; + responseData = await salesmateApiRequest.call(this, 'POST', '/v2/activities/search', body, qs); + responseData = responseData.Data.data; + } + } + if (operation === 'delete') { + const activityId = this.getNodeParameter('id', i) as string; + responseData = await salesmateApiRequest.call(this, 'DELETE', `/v1/activities/${activityId}`); + } + } + if (resource === 'deal') { + if (operation === 'create') { + const title = this.getNodeParameter('title', i) as string; + const owner = this.getNodeParameter('owner', i) as number; + const primaryContact = this.getNodeParameter('primaryContact', i) as number; + const pipeline = this.getNodeParameter('pipeline', i) as string; + const status = this.getNodeParameter('status', i) as string; + const stage = this.getNodeParameter('stage', i) as string; + const currency = this.getNodeParameter('currency', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDeal = { + title, + owner, + primaryContact, + pipeline, + status, + stage, + currency, + }; + if (additionalFields.description) { + body.description = additionalFields.description as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string; + } + if (additionalFields.primaryCompany) { + body.primaryCompany = additionalFields.primaryCompany as number; + } + if (additionalFields.source) { + body.source = additionalFields.source as string; + } + if (additionalFields.estimatedCloseDate) { + body.estimatedCloseDate = additionalFields.estimatedCloseDate as string; + } + if (additionalFields.dealValue) { + body.dealValue = additionalFields.dealValue as number; + } + if (additionalFields.priority) { + body.priority = additionalFields.priority as string; + } + responseData = await salesmateApiRequest.call(this, 'POST', '/v1/deals', body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'update') { + const dealId = this.getNodeParameter('id', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDeal = {}; + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.owner) { + body.owner = updateFields.owner as number; + } + if (updateFields.primaryContact) { + body.primaryContact = updateFields.primaryContact as number; + } + if (updateFields.status) { + body.status = updateFields.status as string; + } + if (updateFields.currency) { + body.currency = updateFields.currency as string; + } + if (updateFields.stage) { + body.stage = updateFields.stage as string; + } + if (updateFields.pipeline) { + body.pipeline = updateFields.pipeline as string; + } + if (updateFields.description) { + body.description = updateFields.description as string; + } + if (updateFields.tags) { + body.tags = updateFields.tags as string; + } + if (updateFields.primaryCompany) { + body.primaryCompany = updateFields.primaryCompany as number; + } + if (updateFields.source) { + body.source = updateFields.source as string; + } + if (updateFields.estimatedCloseDate) { + body.estimatedCloseDate = updateFields.estimatedCloseDate as string; + } + if (updateFields.dealValue) { + body.dealValue = updateFields.dealValue as number; + } + if (updateFields.priority) { + body.priority = updateFields.priority as string; + } + responseData = await salesmateApiRequest.call(this, 'PUT', `/v1/deals/${dealId}`, body); + responseData = responseData.Data; + if (!rawData) { + delete responseData.detail; + } + } + if (operation === 'get') { + const dealId = this.getNodeParameter('id', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + responseData = await salesmateApiRequest.call(this, 'GET', `/v1/deals/${dealId}`); + responseData = responseData.Data; + if (!rawData) { + responseData = responseData.map((deal: IDataObject) => { + const aux: IDataObject = {}; + aux[deal.fieldName as string] = deal.value; + return aux; + }); + } + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + let body: IDataObject = { + query: { + group: { + }, + }, + }; + if (options.sortBy) { + qs.sortBy = options.sortBy as string; + } + if (options.sortOrder) { + qs.sortOrder = options.sortOrder as string; + } + if (options.fields) { + body.fields = (options.fields as string).split(',') as string[]; + } else { + throw new Error('You have to add at least one field'); + } + if (!jsonActive) { + const filters: IDataObject[] = []; + const filtersUi = (this.getNodeParameter('filters', i) as IDataObject).filtersUi as IDataObject; + if (filtersUi && filtersUi.conditions) { + const conditions = filtersUi.conditions as IDataObject; + if (conditions.conditionsUi) { + for (const condition of conditions.conditionsUi as IDataObject[]) { + const filter: IDataObject = {}; + filter.moduleName = 'Task'; + filter.field = { + fieldName: condition.field, + }; + filter.condition = condition.condition; + filter.data = condition.value; + filters.push(filter) + } + } + } + if (filtersUi && filtersUi.operator) { + //@ts-ignore + body.query.group = { + operator: filtersUi.operator, + rules: filters, + }; + } + } else { + const json = validateJSON(this.getNodeParameter('filtersJson', i) as string); + body = json; + } + if (returnAll) { + responseData = await salesmateApiRequestAllItems.call(this, 'Data', 'POST', '/v2/deals/search', body, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.rows = limit; + responseData = await salesmateApiRequest.call(this, 'POST', '/v2/deals/search', body, qs); + responseData = responseData.Data.data; + } + } + if (operation === 'delete') { + const dealId = this.getNodeParameter('id', i) as string; + responseData = await salesmateApiRequest.call(this, 'DELETE', `/v1/deals/${dealId}`); + } + } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else {