diff --git a/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.json b/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.json new file mode 100644 index 0000000000000..192e07bb4237e --- /dev/null +++ b/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.json @@ -0,0 +1,25 @@ +{ + "node": "n8n-nodes-base.eventbrite", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": ["Sales"], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/eventbrite" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.eventbrite/" + } + ], + "generic": [ + { + "label": "Hey founders! Your business doesn't need you to operate", + "icon": " 🖥️", + "url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.ts b/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.ts new file mode 100644 index 0000000000000..118704678ca52 --- /dev/null +++ b/packages/nodes-base/nodes/Eventbrite/Eventbrite.node.ts @@ -0,0 +1,378 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { eventbriteApiRequest, eventbriteApiRequestAllItems } from './GenericFunctions'; + +export class Eventbrite implements INodeType { + description: INodeTypeDescription = { + displayName: 'Eventbrite', + name: 'eventbrite', + icon: 'file:eventbrite.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Eventbrite REST API', + defaults: { + name: 'Eventbrite', + color: '#dc5237', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'eventbriteApi', + required: true, + displayOptions: { + show: { + authentication: ['privateKey'], + }, + }, + }, + { + name: 'eventbriteOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['oAuth2'], + }, + }, + }, + ], + properties: [ + // ---------------------------------- + // Authentication select + // ---------------------------------- + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Private Key', + value: 'privateKey', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'privateKey', + description: 'The authentification method to use.', + }, + // ---------------------------------- + // resources + // ---------------------------------- + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Attendee', + value: 'attendee', + }, + ], + default: 'attendee', + description: 'The resource to operate on.', + }, + // ---------------------------------- + // operations + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['attendee'], + }, + }, + options: [ + { + name: 'Retrieve By Attendee ID', + value: 'retrieve', + description: 'Retrieve an Attendee by Attendee ID.', + }, + { + name: 'List By Event ID', + value: 'listByEvent', + description: 'List Attendees by Event ID. Returns a paginated response.', + }, + { + name: 'List by Organization ID', + value: 'listByOrganization', + description: + 'List Attendees of an Organizationss Events by Organization ID. Returns a paginated response.', + }, + ], + default: 'retrieve', + description: 'The operation to perform.', + }, + // ---------------------------------- + // fields + // ---------------------------------- + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getOrganizations', + }, + default: '', + required: true, + displayOptions: { + show: { + operation: ['listByOrganization'], + resource: ['attendee'], + }, + }, + description: 'Organization ID to query for.', + }, + // for the load options query + { + displayName: 'Organization ID', + name: 'organizationIdForEventsQuery', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getOrganizations', + }, + default: '', + required: false, + displayOptions: { + show: { + operation: ['listByEvent', 'retrieve'], + resource: ['attendee'], + }, + }, + description: + 'Organization ID to query for. ID used to query events for selection in the event ID field.', + }, + { + displayName: 'Event ID', + name: 'eventId', + type: 'options', + typeOptions: { + loadOptionsDependsOn: ['organizationIdForEventsQuery'], + loadOptionsMethod: 'getEvents', + }, + default: '', + required: true, + displayOptions: { + show: { + operation: ['listByEvent', 'retrieve'], + resource: ['attendee'], + }, + }, + description: 'Event ID to query for.', + }, + { + displayName: 'Attendee ID', + name: 'attendeeId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['retrieve'], + resource: ['attendee'], + }, + }, + description: 'Attendee ID to query for.', + }, + { + displayName: 'Get All Entries', + name: 'getAll', + type: 'boolean', + default: true, + required: true, + displayOptions: { + show: { + operation: ['listByEvent', 'listByOrganization'], + resource: ['attendee'], + }, + }, + description: + 'Choose with paginated queries to request all pages. Might take a long time if you request a big data set.', + }, + ], + }; + + methods = { + loadOptions: { + // Get all the available organizations to display them to user so that he can + // select them easily + async getOrganizations(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const organizations = await eventbriteApiRequestAllItems.call( + this, + 'organizations', + 'GET', + '/users/me/organizations', + ); + for (const organization of organizations) { + const organizationName = organization.name; + const organizationId = organization.id; + if (organizationName !== '') { + returnData.push({ + name: organizationName as string, + value: organizationId as number, + }); + } else { + returnData.push({ + name: organizationId!.toString() as string, + value: organizationId as number, + }); + } + } + + return returnData; + }, + // Get all the available events to display them to user so that he can + // select them easily + async getEvents(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const organization = this.getCurrentNodeParameter('organizationIdForEventsQuery'); + const events = await eventbriteApiRequestAllItems.call( + this, + 'events', + 'GET', + `/organizations/${organization}/events`, + ); + for (const event of events) { + // @ts-ignore + const eventName = event!.name!.text; + const eventId = event.id; + if (eventName !== '') { + returnData.push({ + name: eventName as string, + value: eventId as number, + }); + } else { + returnData.push({ + name: eventId!.toString() as string, + value: eventId as number, + }); + } + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + let endpoint = ''; + let requestMethod = ''; + + // tslint:disable-next-line: prefer-const -> This is disabled because it is not an issue currently with only GET requests, but might be needed as non-constant in future with Create or Update requests. + let body: IDataObject = {}; + let qs: IDataObject = {}; + let responseData; + + for (let i = 0; i < items.length; i++) { + try { + if (resource === 'attendee') { + // ---------------------------------- + // attendee: Retrieve + // ---------------------------------- + if (operation === 'retrieve') { + requestMethod = 'GET'; + const eventId = this.getNodeParameter('eventId', i) as string; + const attendeeId = this.getNodeParameter('attendeeId', i) as string; + endpoint = `/events/${eventId}/attendees/${attendeeId}/`; + + qs = {} as IDataObject; + + responseData = await eventbriteApiRequest.call(this, requestMethod, endpoint, body, qs); + } + // ---------------------------------- + // attendee:listByEvent + // ---------------------------------- + else if (operation === 'listByEvent') { + requestMethod = 'GET'; + const eventId = this.getNodeParameter('eventId', i) as string; + const propertyName = 'attendees'; + endpoint = `/events/${eventId}/attendees/`; + + qs = {} as IDataObject; + + const getAll = this.getNodeParameter('getAll', i) as boolean; + if (getAll) { + responseData = await eventbriteApiRequestAllItems.call( + this, + propertyName, + requestMethod, + endpoint, + body, + qs, + ); + } else { + responseData = await eventbriteApiRequest.call( + this, + requestMethod, + endpoint, + body, + qs, + ); + } + } + // ---------------------------------- + // attendee:listByOrganization + // ---------------------------------- + else if (operation === 'listByOrganization') { + requestMethod = 'GET'; + const organizationId = this.getNodeParameter('organizationId', i) as string; + const propertyName = 'attendees'; + endpoint = `/organizations/${organizationId}/attendees/`; + + qs = {} as IDataObject; + + const getAll = this.getNodeParameter('getAll', i) as boolean; + if (getAll) { + responseData = await eventbriteApiRequestAllItems.call( + this, + propertyName, + requestMethod, + endpoint, + body, + qs, + ); + } else { + responseData = await eventbriteApiRequest.call( + this, + requestMethod, + endpoint, + body, + qs, + ); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData); + } + } catch (error) { + if (this.continueOnFail()) { + // @ts-ignore:next-line + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.json b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.json index 626eec3c78788..f1070afe5bb2c 100644 --- a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.json +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.json @@ -2,9 +2,7 @@ "node": "n8n-nodes-base.eventbriteTrigger", "nodeVersion": "1.0", "codexVersion": "1.0", - "categories": [ - "Sales" - ], + "categories": ["Sales"], "resources": { "credentialDocumentation": [ { @@ -24,4 +22,4 @@ } ] } -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts index 4685c98e61a84..57a2fa8a8586d 100644 --- a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts @@ -1,7 +1,4 @@ -import { - IHookFunctions, - IWebhookFunctions, -} from 'n8n-core'; +import { IHookFunctions, IWebhookFunctions } from 'n8n-core'; import { IDataObject, @@ -13,10 +10,7 @@ import { NodeApiError, } from 'n8n-workflow'; -import { - eventbriteApiRequest, - eventbriteApiRequestAllItems, -} from './GenericFunctions'; +import { eventbriteApiRequest, eventbriteApiRequestAllItems } from './GenericFunctions'; export class EventbriteTrigger implements INodeType { description: INodeTypeDescription = { @@ -28,6 +22,7 @@ export class EventbriteTrigger implements INodeType { description: 'Handle Eventbrite events via webhooks', defaults: { name: 'Eventbrite Trigger', + color: '#dc5237', }, inputs: [], outputs: ['main'], @@ -37,9 +32,7 @@ export class EventbriteTrigger implements INodeType { required: true, displayOptions: { show: { - authentication: [ - 'privateKey', - ], + authentication: ['privateKey'], }, }, }, @@ -48,9 +41,7 @@ export class EventbriteTrigger implements INodeType { required: true, displayOptions: { show: { - authentication: [ - 'oAuth2', - ], + authentication: ['oAuth2'], }, }, }, @@ -98,9 +89,7 @@ export class EventbriteTrigger implements INodeType { type: 'options', required: true, typeOptions: { - loadOptionsDependsOn: [ - 'organization', - ], + loadOptionsDependsOn: ['organization'], loadOptionsMethod: 'getEvents', }, default: '', @@ -192,15 +181,28 @@ export class EventbriteTrigger implements INodeType { // select them easily async getOrganizations(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const organizations = await eventbriteApiRequestAllItems.call(this, 'organizations', 'GET', '/users/me/organizations'); + const organizations = await eventbriteApiRequestAllItems.call( + this, + 'organizations', + 'GET', + '/users/me/organizations', + ); for (const organization of organizations) { const organizationName = organization.name; const organizationId = organization.id; - returnData.push({ - name: organizationName, - value: organizationId, - }); + if (organizationName !== '') { + returnData.push({ + name: organizationName as string, + value: organizationId as number, + }); + } else { + returnData.push({ + name: organizationId!.toString() as string, + value: organizationId as number, + }); + } } + return returnData; }, // Get all the available events to display them to user so that he can @@ -208,14 +210,27 @@ export class EventbriteTrigger implements INodeType { async getEvents(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; const organization = this.getCurrentNodeParameter('organization'); - const events = await eventbriteApiRequestAllItems.call(this, 'events', 'GET', `/organizations/${organization}/events`); + const events = await eventbriteApiRequestAllItems.call( + this, + 'events', + 'GET', + `/organizations/${organization}/events`, + ); for (const event of events) { - const eventName = event.name.text; + // @ts-ignore (because of different property name) + const eventName = event!.name!.text; const eventId = event.id; - returnData.push({ - name: eventName, - value: eventId, - }); + if (eventName !== '') { + returnData.push({ + name: eventName as string, + value: eventId as number, + }); + } else { + returnData.push({ + name: eventId!.toString() as string, + value: eventId as number, + }); + } } return returnData; }, @@ -276,7 +291,7 @@ export class EventbriteTrigger implements INodeType { const endpoint = `/webhooks/${webhookData.webhookId}/`; try { responseData = await eventbriteApiRequest.call(this, 'DELETE', endpoint); - } catch(error) { + } catch (error) { return false; } if (!responseData.success) { @@ -292,7 +307,9 @@ export class EventbriteTrigger implements INodeType { const req = this.getRequestObject(); if (req.body.api_url === undefined) { - throw new NodeApiError(this.getNode(), req.body, { message: 'The received data does not contain required "api_url" property!' }); + throw new NodeApiError(this.getNode(), req.body, { + message: 'The received data does not contain required "api_url" property!', + }); } const resolveData = this.getNodeParameter('resolveData', false) as boolean; @@ -300,9 +317,7 @@ export class EventbriteTrigger implements INodeType { if (resolveData === false) { // Return the data as it got received return { - workflowData: [ - this.helpers.returnJsonArray(req.body), - ], + workflowData: [this.helpers.returnJsonArray(req.body)], }; } @@ -310,18 +325,24 @@ export class EventbriteTrigger implements INodeType { return { workflowData: [ this.helpers.returnJsonArray({ - placeholder: 'Test received. To display actual data of object get the webhook triggered by performing the action which triggers it.', + placeholder: + 'Test received. To display actual data of object get the webhook triggered by performing the action which triggers it.', }), ], }; } - const responseData = await eventbriteApiRequest.call(this, 'GET', '', {}, undefined, req.body.api_url); + const responseData = await eventbriteApiRequest.call( + this, + 'GET', + '', + {}, + undefined, + req.body.api_url, + ); return { - workflowData: [ - this.helpers.returnJsonArray(responseData), - ], + workflowData: [this.helpers.returnJsonArray(responseData)], }; } } diff --git a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts index ef1f623b9396d..85f2fa4862000 100644 --- a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts @@ -1,6 +1,4 @@ -import { - OptionsWithUri, -} from 'request'; +import { OptionsWithUri } from 'request'; import { IExecuteFunctions, @@ -10,17 +8,28 @@ import { IWebhookFunctions, } from 'n8n-core'; -import { - IDataObject, NodeApiError, NodeOperationError, -} from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError } from 'n8n-workflow'; -export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function eventbriteApiRequest( + this: + | IHookFunctions + | IExecuteFunctions + | IExecuteSingleFunctions + | ILoadOptionsFunctions + | IWebhookFunctions, + method: string, + resource: string, + body: IDataObject = {}, + qs: IDataObject = {}, + uri?: string, + option: IDataObject = {}, +) { let options: OptionsWithUri = { headers: {}, method, qs, body, - uri: uri ||`https://www.eventbriteapi.com/v3${resource}`, + uri: uri || `https://www.eventbriteapi.com/v3${resource}`, json: true, }; options = Object.assign({}, options, option); @@ -44,6 +53,7 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti return await this.helpers.requestOAuth2!.call(this, 'eventbriteOAuth2Api', options); } } catch (error) { + // @ts-ignore throw new NodeApiError(this.getNode(), error); } } @@ -52,8 +62,14 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti * Make an API request to paginated flow endpoint * and return all results */ -export async function eventbriteApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any - +export async function eventbriteApiRequestAllItems( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + propertyName: string, + method: string, + resource: string, + body: IDataObject = {}, + query: IDataObject = {}, +) { const returnData: IDataObject[] = []; let responseData; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ac787b601a93d..ce010a8264b4d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -400,6 +400,7 @@ "dist/nodes/ERPNext/ERPNext.node.js", "dist/nodes/ErrorTrigger/ErrorTrigger.node.js", "dist/nodes/Eventbrite/EventbriteTrigger.node.js", + "dist/nodes/Eventbrite/Eventbrite.node.js", "dist/nodes/ExecuteCommand/ExecuteCommand.node.js", "dist/nodes/ExecuteWorkflow/ExecuteWorkflow.node.js", "dist/nodes/Facebook/FacebookGraphApi.node.js",