From 82408e030a9d88010b99f0cd43edeff7149d4953 Mon Sep 17 00:00:00 2001 From: trojanh Date: Mon, 20 Jan 2020 18:40:19 +0530 Subject: [PATCH 01/17] Add Trello Checklist create, get, getAll and delete api --- .../nodes-base/nodes/Trello/Trello.node.ts | 323 +++++++++++++++++- 1 file changed, 320 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 5c242b47f..13144678b 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -51,6 +51,10 @@ export class Trello implements INodeType { name: 'Card', value: 'card', }, + { + name: "Checklist", + value: "checklist", + }, { name: 'List', value: 'list', @@ -1235,12 +1239,12 @@ export class Trello implements INodeType { { name: 'Create', value: 'create', - description: 'Create a new board', + description: 'Create a new attachment for a card', }, { name: 'Delete', value: 'delete', - description: 'Delete a board', + description: 'Delete an attachment', }, { name: 'Get', @@ -1350,7 +1354,7 @@ export class Trello implements INodeType { ], }, }, - description: 'The ID of the card to get delete.', + description: 'The ID of the card that attachment belongs to.', }, { displayName: 'Attachment ID', @@ -1485,7 +1489,259 @@ export class Trello implements INodeType { ], }, + // ---------------------------------- + // checklist + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'checklist', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new checklist', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a board', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a checklist', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Returns all checklists for the card', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // checklist:create + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card to add checklist to.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The URL of the checklist to add.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Id Of Checklist Source', + name: 'idChecklistSource', + type: 'string', + default: '', + description: 'The ID of a source checklist to copy into the new one.', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: '', + description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', + }, + ], + }, + + // ---------------------------------- + // checklist:delete + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'Checklist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to delete.', + }, + + + // ---------------------------------- + // checklist:getAll + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card to get checklists.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // checklist:get + // ---------------------------------- + { + displayName: 'Checklist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + ], + + }; @@ -1751,6 +2007,67 @@ export class Trello implements INodeType { endpoint = `cards/${cardId}/attachments`; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + } else if (resource === 'checklist') { + + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const name = this.getNodeParameter('name', i) as string; + + Object.assign(qs, { name }); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + endpoint = `cards/${cardId}/checklists`; + + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + endpoint = `cards/${cardId}/checklists/${id}`; + + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `checklists/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + + endpoint = `cards/${cardId}/checklists`; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); } else { From 29cb669a6b42fee49cea5d59f7c722915e85a6fb Mon Sep 17 00:00:00 2001 From: trojanh Date: Mon, 20 Jan 2020 19:07:27 +0530 Subject: [PATCH 02/17] Add checkItem API to get, update and delete --- .../nodes-base/nodes/Trello/Trello.node.ts | 249 +++++++++++++++++- 1 file changed, 248 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 13144678b..436455287 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -1512,17 +1512,32 @@ export class Trello implements INodeType { { name: 'Delete', value: 'delete', - description: 'Delete a board', + description: 'Delete a checklist', + }, + { + name: 'Delete CheckItem', + value: 'deleteCheckItem', + description: 'Delete a checklist item', }, { name: 'Get', value: 'get', description: 'Get the data of a checklist', }, + { + name: 'Get CheckItem', + value: 'getCheckItem', + description: 'Get a specific checkItem on a card', + }, { name: 'Get All', value: 'getAll', description: 'Returns all checklists for the card', + }, + { + name: 'Update CheckItem', + value: 'updateCheckItem', + description: 'Update an item in a checklist on a card.', } ], default: 'getAll', @@ -1739,6 +1754,199 @@ export class Trello implements INodeType { ], }, + // ---------------------------------- + // checklist:deleteCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'deleteCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'deleteCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to delete.', + }, + + // ---------------------------------- + // checklist:getCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // checklist:updateCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist item to update.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The new name for the checklist item.', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'One of: complete, incomplete', + }, + { + displayName: 'Checklist ID', + name: 'checklistId', + type: 'string', + default: '', + description: 'The ID of the checklist this item is in', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: '', + description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', + }, + ], + }, + ], @@ -2068,6 +2276,45 @@ export class Trello implements INodeType { endpoint = `cards/${cardId}/checklists`; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getCheckItem') { + // ---------------------------------- + // getCheckItem + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'deleteCheckItem') { + // ---------------------------------- + // deleteCheckItem + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + } else if (operation === 'updateCheckItem') { + // ---------------------------------- + // updateCheckItem + // ---------------------------------- + + requestMethod = 'PUT'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); } else { From d28a3f7387a7cdcbbc4d49769abdaf482f731b78 Mon Sep 17 00:00:00 2001 From: trojanh Date: Mon, 20 Jan 2020 19:41:23 +0530 Subject: [PATCH 03/17] Add checkItemStates API --- .../nodes-base/nodes/Trello/Trello.node.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 436455287..a3953b41b 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -1504,6 +1504,11 @@ export class Trello implements INodeType { }, }, options: [ + { + name: "Completed CheckItems", + value: "completedCheckItems", + description: "Get the completed checklist items on a card" + }, { name: 'Create', value: 'create', @@ -1947,6 +1952,54 @@ export class Trello implements INodeType { ], }, + // ---------------------------------- + // checklist:completedCheckItems + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'completedCheckItems', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card for checkItems.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'completedCheckItems', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of: "idCheckItem", "state".', + }, + ], + }, + ], @@ -2317,6 +2370,20 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); + } else if(operation ==="completedCheckItems") { + // ---------------------------------- + // updateCheckItem + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + + endpoint = `cards/${cardId}/checkItemStates`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else { throw new Error(`The operation "${operation}" is not known!`); } From b944ed5f6b6273fca23b3fbe94dfce4b9df93eb5 Mon Sep 17 00:00:00 2001 From: trojanh Date: Mon, 20 Jan 2020 20:58:51 +0530 Subject: [PATCH 04/17] Add label API --- .../nodes-base/nodes/Trello/Trello.node.ts | 476 +++++++++++++++++- 1 file changed, 475 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index a3953b41b..6b5217acc 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -55,6 +55,10 @@ export class Trello implements INodeType { name: "Checklist", value: "checklist", }, + { + name: "Label", + value: "label" + } { name: 'List', value: 'list', @@ -2000,8 +2004,373 @@ export class Trello implements INodeType { ], }, - ], + // ---------------------------------- + // label + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'label', + ], + }, + }, + options: [ + { + name: 'Add to Card', + value: 'addLabel', + description: 'Add a label to a card.', + }, + { + name: 'Create', + value: 'create', + description: 'Create a new label', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a label', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a label', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Returns all label for the board', + }, + { + name: 'Remove From Card', + value: 'removeLabel', + description: 'Remove a label from a card.', + }, + { + name: 'Update', + value: 'update', + description: 'Update a label.', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // label:create + // ---------------------------------- + { + displayName: 'Board ID', + name: 'boardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the board to create the label on.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + description: 'Name for the label.', + }, + { + displayName: 'Color', + name: 'color', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The color for the label. See [fields](https://developers.trello.com/reference#label-object) for color options.', + }, + + // ---------------------------------- + // label:delete + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to delete.', + }, + + // ---------------------------------- + // label:getAll + // ---------------------------------- + { + displayName: 'Board ID', + name: 'boardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the board to get label.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // label:get + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'label', + ], + }, + }, + description: 'Get information about a label by ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // label:addLabel + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'addLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the card to get label.', + }, + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'addLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to add.', + }, + + // ---------------------------------- + // label:removeLabel + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'removeLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the card to remove label from.', + }, + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'removeLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to remove.', + }, + + // ---------------------------------- + // label:update + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the label.', + }, + { + displayName: 'Color', + name: 'color', + type: 'string', + default: '', + description: 'The color for the label. See [fields](https://developers.trello.com/reference#label-object) for color options.', + } + ], + }, + ], }; @@ -2384,6 +2753,111 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + } else if (resource === 'label') { + + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + const idBoard = this.getNodeParameter('boardId', i) as string; + const name = this.getNodeParameter('name', i) as string; + const color = this.getNodeParameter('color', i) as string; + + + Object.assign(qs, { + idBoard, + name, + color + }); + + + endpoint = "labels"; + + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + const idBoard = this.getNodeParameter('boardId', i) as string; + + endpoint = `board/${idBoard}/labels`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + + } else if (operation === 'addLabel') { + // ---------------------------------- + // addLabel + // ---------------------------------- + + requestMethod = 'POST'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + qs.value = id; + + endpoint = `/cards/${cardId}/idLabels`; + + } else if (operation === 'removeLabel') { + // ---------------------------------- + // addLabel + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + endpoint = `/cards/${cardId}/idLabels/${id}`; + } else { throw new Error(`The operation "${operation}" is not known!`); } From c8c9ba08917599bfa0c4e145b62f98940a01607a Mon Sep 17 00:00:00 2001 From: trojanh Date: Mon, 20 Jan 2020 21:10:17 +0530 Subject: [PATCH 05/17] Fix card fields url --- packages/nodes-base/nodes/Trello/Trello.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 6b5217acc..b95025ae8 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -2115,7 +2115,7 @@ export class Trello implements INodeType { ], }, }, - description: 'The color for the label. See [fields](https://developers.trello.com/reference#label-object) for color options.', + description: 'The color for the label. See fields for color options.', }, // ---------------------------------- @@ -2366,7 +2366,7 @@ export class Trello implements INodeType { name: 'color', type: 'string', default: '', - description: 'The color for the label. See [fields](https://developers.trello.com/reference#label-object) for color options.', + description: 'The color for the label. See fields for color options.', } ], }, From 14ab90791e682ba4bb56ff14df593cc71009ba70 Mon Sep 17 00:00:00 2001 From: trojanh Date: Tue, 21 Jan 2020 19:23:54 +0530 Subject: [PATCH 06/17] Add Disqus Forums GET apis --- .../credentials/DisqusApi.credentials.ts | 18 + .../nodes-base/nodes/Disqus/Disqus.node.ts | 626 ++++++++++++++++++ packages/nodes-base/nodes/Disqus/disqus.png | Bin 0 -> 1142 bytes packages/nodes-base/package.json | 2 + 4 files changed, 646 insertions(+) create mode 100644 packages/nodes-base/credentials/DisqusApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Disqus/Disqus.node.ts create mode 100644 packages/nodes-base/nodes/Disqus/disqus.png diff --git a/packages/nodes-base/credentials/DisqusApi.credentials.ts b/packages/nodes-base/credentials/DisqusApi.credentials.ts new file mode 100644 index 000000000..fbf98c92a --- /dev/null +++ b/packages/nodes-base/credentials/DisqusApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class DisqusApi implements ICredentialType { + name = 'disqusApi'; + displayName = 'Disqus API'; + properties = [ + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + description: 'Visit your account details page, and grab the Access Token. See Disqus auth.' + }, + ]; +} diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts new file mode 100644 index 000000000..401aabb16 --- /dev/null +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -0,0 +1,626 @@ +import { + BINARY_ENCODING, + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; + +import { OptionsWithUri } from 'request'; + + +export class Disqus implements INodeType { + description: INodeTypeDescription = { + displayName: 'Disqus', + name: 'disqus', + icon: 'file:disqus.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Access data on Disqus', + defaults: { + name: 'Disqus', + color: '#22BB44', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'disqusApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Forum', + value: 'forum', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'forum', + description: 'The resource to operate on.', + }, + + // ---------------------------------- + // forum + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'forum', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Returns forum details.', + }, + { + name: 'Get All Categories', + value: 'getCategories', + description: 'Returns a list of categories within a forum.', + }, + { + name: 'Get All Threads', + value: 'getThreads', + description: 'Returns a list of threads within a forum.', + }, + { + name: 'Get All Posts', + value: 'getPosts', + description: 'Returns a list of posts within a forum.', + } + ], + default: 'get', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // forum:get + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'attach', + name: 'attach', + type: 'string', + default: '[]', + description: 'Choices: followsForum, forumCanDisableAds, forumForumCategory, counters, forumDaysAlive, forumFeatures, forumIntegration, forumNewPolicy, forumPermissions', + }, + { + displayName: 'related', + name: 'related', + type: 'string', + default: false, + description: 'You may specify relations to include with your response. Choices `author`', + }, + ], + }, + + // ---------------------------------- + // forum:getPosts + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getPosts', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getPosts', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Since', + name: 'since', + type: 'string', + default: `[]`, + description: 'Unix timestamp (or ISO datetime standard)', + }, + { + displayName: 'Related', + name: 'related', + type: 'string', + default: '[]', + description: 'You may specify relations to include with your response. Choices `author`', + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: '', + description: 'You may specify cursor for your response.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'string', + default: 25, + description: 'You may specify relations maximum number of posts to return. Maximum value is 100', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'string', + default: '[]', + description: 'You may specify filters for your response. Choices: `Is_Anonymous`, `Has_Link`, `Has_Low_Rep_Author`, `Has_Bad_Word`, `Is_Flagged`, `No_Issue`, `Is_Toxic`, `Modified_By_Rule`, `Shadow_Banned`, `Has_Media`, `Is_At_Flag_Limit`', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + description: 'You may specify query for your response.', + }, + { + displayName: 'Include', + name: 'include', + type: 'string', + default: false, + description: 'You may specify relations to include with your response. Choices `author`', + }, + { + displayName: 'Order', + name: 'order', + type: 'string', + default: 'asc', + description: 'You may specify order to sort your response.Choices: asc, desc', + }, + ], + }, + + // ---------------------------------- + // forum:getCategories + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCategories', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get Categories.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getCategories', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Since ID', + name: 'sinceId', + type: 'string', + default: `[]`, + description: 'You may specify cursor since_id for your response.', + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: '', + description: 'You may specify cursor for your response.', + }, + { + displayName: 'Order', + name: 'order', + type: 'string', + default: 'asc', + description: 'You may specify order to sort your response.Choices: asc, desc', + }, + ], + }, + + // ---------------------------------- + // forum:getThreads + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getThreads', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get Threads.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getThreads', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Thread', + name: 'threadId', + type: 'string', + default: '', + description: 'Looks up a thread by ID. You may pass us the "ident" query type instead of an ID by including "forum". You may pass us the "link" query type to filter by URL. You must pass the "forum" if you do not have the Pro API Access addon.', + }, + { + displayName: 'Since', + name: 'since', + type: 'string', + default: '', + description: 'Unix timestamp (or ISO datetime standard)', + }, + { + displayName: 'Related', + name: 'related', + type: 'string', + default: '[]', + description: 'You may specify relations to include with your response. Choices `author`', + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: '', + description: 'You may specify cursor for your response.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'string', + default: 25, + description: 'You may specify relations maximum number of posts to return. Maximum value is 100', + }, + { + displayName: 'Include', + name: 'include', + type: 'string', + default: '', + description: 'You may specify relations to include with your response. Choices: open, closed, killed', + }, + { + displayName: 'Order', + name: 'order', + type: 'string', + default: 'desc', + description: 'You may specify order to sort your response.Choices: asc, desc', + }, + ], + }, + + // // ---------------------------------- + // // forum:getThreads + // // ---------------------------------- + // { + // displayName: 'Forum name', + // name: 'id', + // type: 'string', + // default: '', + // required: true, + // displayOptions: { + // show: { + // operation: [ + // 'getThreads', + // ], + // resource: [ + // 'forum', + // ], + // }, + // }, + // description: 'The short name(aka ID) of the forum to get Threads.', + // }, + // { + // displayName: 'Additional Fields', + // name: 'additionalFields', + // type: 'collection', + // placeholder: 'Add Field', + // displayOptions: { + // show: { + // operation: [ + // 'getCategories', + // ], + // resource: [ + // 'forum', + // ], + // }, + // }, + // default: {}, + // options: [ + // { + // displayName: 'Thread', + // name: 'threadId', + // type: 'string', + // default: '', + // description: 'Looks up a thread by ID. You may pass us the "ident" query type instead of an ID by including "forum". You may pass us the "link" query type to filter by URL. You must pass the "forum" if you do not have the Pro API Access addon.', + // }, + // { + // displayName: 'Since', + // name: 'since', + // type: 'string', + // default: '', + // description: 'Unix timestamp (or ISO datetime standard)', + // }, + // { + // displayName: 'Related', + // name: 'related', + // type: 'string', + // default: '[]', + // description: 'You may specify relations to include with your response. Choices `author`', + // }, + // { + // displayName: 'Cursor', + // name: 'cursor', + // type: 'string', + // default: '', + // description: 'You may specify cursor for your response.', + // }, + // { + // displayName: 'Limit', + // name: 'limit', + // type: 'string', + // default: 25, + // description: 'You may specify relations maximum number of posts to return. Maximum value is 100', + // }, + // { + // displayName: 'Include', + // name: 'include', + // type: 'string', + // default: '', + // description: 'You may specify relations to include with your response. Choices: open, closed, killed', + // }, + // { + // displayName: 'Order', + // name: 'order', + // type: 'string', + // default: 'desc', + // description: 'You may specify order to sort your response.Choices: asc, desc', + // }, + // ], + // }, + ], + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const credentials = this.getCredentials('disqusApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let endpoint = ''; + let requestMethod = ''; + let body: IDataObject | Buffer; + let qs: IDataObject; + + + for (let i = 0; i < items.length; i++) { + body = {}; + qs = {}; + + if (resource === 'forum') { + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/details.json'; + + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + } else if (operation === 'getPosts') { + // ---------------------------------- + // getPosts + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listPosts.json'; + + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + } else if (operation === 'getCategories') { + // ---------------------------------- + // getCategories + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listCategories.json'; + + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + } else if (operation === 'getThreads') { + // ---------------------------------- + // getThreads + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listThreads.json'; + + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + + } else { + throw new Error(`The resource "${resource}" is not known!`); + } + + qs.api_key = credentials.accessToken; + endpoint = `https://disqus.com/api/3.0/${endpoint}`; + + const options: OptionsWithUri = { + method: requestMethod, + qs, + uri: endpoint, + json: true, + }; + + + let responseData; + try { + responseData = await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Dropbox credentials are not valid!'); + } + + if (error.error && error.error.error_summary) { + // Try to return the error prettier + throw new Error(`Dropbox error response [${error.statusCode}]: ${error.error.error_summary}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } + + if (responseData && 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/Disqus/disqus.png b/packages/nodes-base/nodes/Disqus/disqus.png new file mode 100644 index 0000000000000000000000000000000000000000..2b3b5cf3a898d43abfde7409f90d6e2c844a03ea GIT binary patch literal 1142 zcmV-+1d02JP)Px#8&FJCMJk~G|NsB;`TvN?|Fqu!XS)AHtpCmF|J(2X zSe;t>000B|NklP{?ZmIfroehFtOB^7qpA;Guo#Q?>QrmIX^mv^|Xn z_T!W>8sD<;jSSXgxd(TcY-=_Ws)CvV`h2dDf?Dojok621BaaM3EBa$ic|A4Ug!@;k z;beUu>*QXx59l>y3zloc_met@x|6lfu@#sFhO!XuE>!iJQ% z>D1f+VM9$<;b#KzU!{-~>LQfCmo;4_p9wq|UMSS3R6;c3YT0y`FA^vgMhM*rFa)-$ zg7}7l#m0wQ2)sJsE7=KBfDzbdP>#~;Py}`vl->Ba7e^$4U3rx2#R4U;XVb#H8MkFb zn+fc!Xi>+<2`HApPFyT|*>D7QGUu5}UiKDv zg_~AkC$=idQ9yPVmRij6Mv#zE2)s=0Dm}T&O|MrJfJm=XA2@3N9P(Jtd}0!VCnn}2 zUknS~1(?sQRqF&j)w3SDQ}rK%iX~X324YoeKq~eSvtkcQxlK#S6Rc{EQ1H|uKd|Nu zUUJsdiV3Xx0~h_ZHBuvs{-6X}rwAa+E`cW0Jc0sQ_=7JHwbZ*_Qma=HxwsG>oJ+r+D^Jh4T-K zyi7l?^D_Uy)XVfEYY%8)l%c_0IrAUJGuY(msRw6waTs%S`ThN3=Hc<*&lnSr?|)!% z`vHaZ`Zv`NDE#0t?N3-Z91e%W;cz${4u`|xa5x+ehr{t2ANt8T>t{X?82|tP07*qo IM6N<$f~#U7mH+?% literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index df87d196c..c985f4bf9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -37,6 +37,7 @@ "dist/credentials/ClickUpApi.credentials.js", "dist/credentials/CodaApi.credentials.js", "dist/credentials/CopperApi.credentials.js", + "dist/credentials/DisqusApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/EventbriteApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", @@ -104,6 +105,7 @@ "dist/nodes/Copper/CopperTrigger.node.js", "dist/nodes/Cron.node.js", "dist/nodes/Discord/Discord.node.js", + "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Dropbox/Dropbox.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/EmailReadImap.node.js", From 4977c0b1630dc4f1dbc9c06e724903267cc42fa3 Mon Sep 17 00:00:00 2001 From: trojanh Date: Tue, 21 Jan 2020 19:33:57 +0530 Subject: [PATCH 07/17] Remove Users API from UI --- .../nodes-base/nodes/Disqus/Disqus.node.ts | 98 +------------------ 1 file changed, 2 insertions(+), 96 deletions(-) diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 401aabb16..5e6561a6f 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -42,11 +42,7 @@ export class Disqus implements INodeType { { name: 'Forum', value: 'forum', - }, - { - name: 'User', - value: 'user', - }, + } ], default: 'forum', description: 'The resource to operate on.', @@ -394,97 +390,7 @@ export class Disqus implements INodeType { description: 'You may specify order to sort your response.Choices: asc, desc', }, ], - }, - - // // ---------------------------------- - // // forum:getThreads - // // ---------------------------------- - // { - // displayName: 'Forum name', - // name: 'id', - // type: 'string', - // default: '', - // required: true, - // displayOptions: { - // show: { - // operation: [ - // 'getThreads', - // ], - // resource: [ - // 'forum', - // ], - // }, - // }, - // description: 'The short name(aka ID) of the forum to get Threads.', - // }, - // { - // displayName: 'Additional Fields', - // name: 'additionalFields', - // type: 'collection', - // placeholder: 'Add Field', - // displayOptions: { - // show: { - // operation: [ - // 'getCategories', - // ], - // resource: [ - // 'forum', - // ], - // }, - // }, - // default: {}, - // options: [ - // { - // displayName: 'Thread', - // name: 'threadId', - // type: 'string', - // default: '', - // description: 'Looks up a thread by ID. You may pass us the "ident" query type instead of an ID by including "forum". You may pass us the "link" query type to filter by URL. You must pass the "forum" if you do not have the Pro API Access addon.', - // }, - // { - // displayName: 'Since', - // name: 'since', - // type: 'string', - // default: '', - // description: 'Unix timestamp (or ISO datetime standard)', - // }, - // { - // displayName: 'Related', - // name: 'related', - // type: 'string', - // default: '[]', - // description: 'You may specify relations to include with your response. Choices `author`', - // }, - // { - // displayName: 'Cursor', - // name: 'cursor', - // type: 'string', - // default: '', - // description: 'You may specify cursor for your response.', - // }, - // { - // displayName: 'Limit', - // name: 'limit', - // type: 'string', - // default: 25, - // description: 'You may specify relations maximum number of posts to return. Maximum value is 100', - // }, - // { - // displayName: 'Include', - // name: 'include', - // type: 'string', - // default: '', - // description: 'You may specify relations to include with your response. Choices: open, closed, killed', - // }, - // { - // displayName: 'Order', - // name: 'order', - // type: 'string', - // default: 'desc', - // description: 'You may specify order to sort your response.Choices: asc, desc', - // }, - // ], - // }, + } ], }; From 0c1428471dd7faa2769115255cb5bce5d42db41c Mon Sep 17 00:00:00 2001 From: trojanh Date: Tue, 21 Jan 2020 19:40:20 +0530 Subject: [PATCH 08/17] Fix error messages --- packages/nodes-base/nodes/Disqus/Disqus.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 5e6561a6f..dde4b3396 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -508,12 +508,12 @@ export class Disqus implements INodeType { } catch (error) { if (error.statusCode === 401) { // Return a clear error - throw new Error('The Dropbox credentials are not valid!'); + throw new Error('The Disqus credentials are not valid!'); } if (error.error && error.error.error_summary) { // Try to return the error prettier - throw new Error(`Dropbox error response [${error.statusCode}]: ${error.error.error_summary}`); + throw new Error(`Disqus error response [${error.statusCode}]: ${error.error.error_summary}`); } // If that data does not exist for some reason return the actual error From c0aacbed746e8630ec065fd0ebd5a869245e59b3 Mon Sep 17 00:00:00 2001 From: trojanh Date: Tue, 21 Jan 2020 19:42:28 +0530 Subject: [PATCH 09/17] Update space indentation to tab --- .../credentials/DisqusApi.credentials.ts | 2 +- .../nodes-base/nodes/Disqus/Disqus.node.ts | 110 +++++++++--------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/nodes-base/credentials/DisqusApi.credentials.ts b/packages/nodes-base/credentials/DisqusApi.credentials.ts index fbf98c92a..c4b2f33ae 100644 --- a/packages/nodes-base/credentials/DisqusApi.credentials.ts +++ b/packages/nodes-base/credentials/DisqusApi.credentials.ts @@ -11,7 +11,7 @@ export class DisqusApi implements ICredentialType { displayName: 'Access Token', name: 'accessToken', type: 'string' as NodePropertyTypes, - default: '', + default: '', description: 'Visit your account details page, and grab the Access Token. See Disqus auth.' }, ]; diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index dde4b3396..85eb133de 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -63,16 +63,16 @@ export class Disqus implements INodeType { }, }, options: [ - { + { name: 'Get', value: 'get', description: 'Returns forum details.', - }, - { + }, + { name: 'Get All Categories', value: 'getCategories', description: 'Returns a list of categories within a forum.', - }, + }, { name: 'Get All Threads', value: 'getThreads', @@ -194,43 +194,43 @@ export class Disqus implements INodeType { type: 'string', default: '[]', description: 'You may specify relations to include with your response. Choices `author`', - }, - { + }, + { displayName: 'Cursor', name: 'cursor', - type: 'string', + type: 'string', default: '', description: 'You may specify cursor for your response.', - }, - { + }, + { displayName: 'Limit', name: 'limit', type: 'string', default: 25, description: 'You may specify relations maximum number of posts to return. Maximum value is 100', - }, - { + }, + { displayName: 'Filters', name: 'filters', type: 'string', default: '[]', - description: 'You may specify filters for your response. Choices: `Is_Anonymous`, `Has_Link`, `Has_Low_Rep_Author`, `Has_Bad_Word`, `Is_Flagged`, `No_Issue`, `Is_Toxic`, `Modified_By_Rule`, `Shadow_Banned`, `Has_Media`, `Is_At_Flag_Limit`', - }, - { + description: 'You may specify filters for your response. Choices: `Is_Anonymous`, `Has_Link`, `Has_Low_Rep_Author`, `Has_Bad_Word`, `Is_Flagged`, `No_Issue`, `Is_Toxic`, `Modified_By_Rule`, `Shadow_Banned`, `Has_Media`, `Is_At_Flag_Limit`', + }, + { displayName: 'Query', name: 'query', type: 'string', default: '', description: 'You may specify query for your response.', - }, - { + }, + { displayName: 'Include', name: 'include', type: 'string', default: false, description: 'You may specify relations to include with your response. Choices `author`', - }, - { + }, + { displayName: 'Order', name: 'order', type: 'string', @@ -238,9 +238,9 @@ export class Disqus implements INodeType { description: 'You may specify order to sort your response.Choices: asc, desc', }, ], - }, + }, - // ---------------------------------- + // ---------------------------------- // forum:getCategories // ---------------------------------- { @@ -285,14 +285,14 @@ export class Disqus implements INodeType { default: `[]`, description: 'You may specify cursor since_id for your response.', }, - { + { displayName: 'Cursor', name: 'cursor', - type: 'string', + type: 'string', default: '', description: 'You may specify cursor for your response.', - }, - { + }, + { displayName: 'Order', name: 'order', type: 'string', @@ -300,9 +300,9 @@ export class Disqus implements INodeType { description: 'You may specify order to sort your response.Choices: asc, desc', }, ], - }, + }, - // ---------------------------------- + // ---------------------------------- // forum:getThreads // ---------------------------------- { @@ -339,8 +339,8 @@ export class Disqus implements INodeType { }, }, default: {}, - options: [ - { + options: [ + { displayName: 'Thread', name: 'threadId', type: 'string', @@ -360,29 +360,29 @@ export class Disqus implements INodeType { type: 'string', default: '[]', description: 'You may specify relations to include with your response. Choices `author`', - }, - { + }, + { displayName: 'Cursor', name: 'cursor', - type: 'string', + type: 'string', default: '', description: 'You may specify cursor for your response.', - }, - { + }, + { displayName: 'Limit', name: 'limit', type: 'string', default: 25, description: 'You may specify relations maximum number of posts to return. Maximum value is 100', - }, - { + }, + { displayName: 'Include', name: 'include', type: 'string', default: '', description: 'You may specify relations to include with your response. Choices: open, closed, killed', - }, - { + }, + { displayName: 'Order', name: 'order', type: 'string', @@ -390,7 +390,7 @@ export class Disqus implements INodeType { description: 'You may specify order to sort your response.Choices: asc, desc', }, ], - } + } ], }; @@ -415,7 +415,7 @@ export class Disqus implements INodeType { for (let i = 0; i < items.length; i++) { - body = {}; + body = {}; qs = {}; if (resource === 'forum') { @@ -426,10 +426,10 @@ export class Disqus implements INodeType { requestMethod = 'GET'; - endpoint = 'forums/details.json'; + endpoint = 'forums/details.json'; - const id = this.getNodeParameter('id', i) as string; - qs.forum = id; + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; @@ -442,10 +442,10 @@ export class Disqus implements INodeType { requestMethod = 'GET'; - endpoint = 'forums/listPosts.json'; + endpoint = 'forums/listPosts.json'; - const id = this.getNodeParameter('id', i) as string; - qs.forum = id; + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; @@ -458,10 +458,10 @@ export class Disqus implements INodeType { requestMethod = 'GET'; - endpoint = 'forums/listCategories.json'; + endpoint = 'forums/listCategories.json'; - const id = this.getNodeParameter('id', i) as string; - qs.forum = id; + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; @@ -474,10 +474,10 @@ export class Disqus implements INodeType { requestMethod = 'GET'; - endpoint = 'forums/listThreads.json'; + endpoint = 'forums/listThreads.json'; - const id = this.getNodeParameter('id', i) as string; - qs.forum = id; + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; @@ -485,14 +485,14 @@ export class Disqus implements INodeType { } else { throw new Error(`The operation "${operation}" is not known!`); - } + } } else { throw new Error(`The resource "${resource}" is not known!`); } - qs.api_key = credentials.accessToken; - endpoint = `https://disqus.com/api/3.0/${endpoint}`; + qs.api_key = credentials.accessToken; + endpoint = `https://disqus.com/api/3.0/${endpoint}`; const options: OptionsWithUri = { method: requestMethod, @@ -527,6 +527,6 @@ export class Disqus implements INodeType { } } - return [this.helpers.returnJsonArray(returnData)]; + return [this.helpers.returnJsonArray(returnData)]; } } From 23e844398b8371094a1a981bae4eb1548e504c0a Mon Sep 17 00:00:00 2001 From: trojanh Date: Thu, 23 Jan 2020 20:43:57 +0530 Subject: [PATCH 10/17] Add returnAll and refactoring --- .../nodes-base/nodes/Disqus/Disqus.node.ts | 233 ++++++++++++++---- .../nodes/Disqus/GenericFunctions.ts | 87 +++++++ 2 files changed, 273 insertions(+), 47 deletions(-) create mode 100644 packages/nodes-base/nodes/Disqus/GenericFunctions.ts diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 85eb133de..213da8ef4 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -9,7 +9,7 @@ import { INodeType, } from 'n8n-workflow'; -import { OptionsWithUri } from 'request'; +import { disqusApiRequest, disqusApiRequestAllItems } from './GenericFunctions'; export class Disqus implements INodeType { @@ -164,6 +164,47 @@ export class Disqus implements INodeType { }, description: 'The short name(aka ID) of the forum to get.', }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getPosts', + ], + }, + }, + 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: [ + 'forum', + ], + operation: [ + 'getPosts', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -261,6 +302,47 @@ export class Disqus implements INodeType { }, description: 'The short name(aka ID) of the forum to get Categories.', }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getCategories', + ], + }, + }, + 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: [ + 'forum', + ], + operation: [ + 'getCategories', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -323,6 +405,47 @@ export class Disqus implements INodeType { }, description: 'The short name(aka ID) of the forum to get Threads.', }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getThreads', + ], + }, + }, + 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: [ + 'forum', + ], + operation: [ + 'getThreads', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -399,11 +522,6 @@ export class Disqus implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; - const credentials = this.getCredentials('disqusApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; @@ -435,6 +553,13 @@ export class Disqus implements INodeType { Object.assign(qs, additionalFields); + try { + const responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + returnData.push(responseData.response); + } catch (error) { + throw error; + } + } else if (operation === 'getPosts') { // ---------------------------------- // getPosts @@ -445,12 +570,28 @@ export class Disqus implements INodeType { endpoint = 'forums/listPosts.json'; const id = this.getNodeParameter('id', i) as string; - qs.forum = id; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + qs.forum = id; + qs.limit = 100; + + try { + let responseData: IDataObject = {}; + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + } + returnData.push.apply(returnData, responseData.response as IDataObject[]); + } catch (error) { + throw error; + } + } else if (operation === 'getCategories') { // ---------------------------------- // getCategories @@ -461,12 +602,28 @@ export class Disqus implements INodeType { endpoint = 'forums/listCategories.json'; const id = this.getNodeParameter('id', i) as string; - qs.forum = id; - + const returnAll = this.getNodeParameter('returnAll', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); + qs.forum = id; + qs.limit = 100; + + try { + let responseData: IDataObject = {}; + + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint) as IDataObject; + } + returnData.push.apply(returnData, responseData.response as IDataObject[]) ; + } catch (error) { + throw error; + } + } else if (operation === 'getThreads') { // ---------------------------------- // getThreads @@ -477,12 +634,29 @@ export class Disqus implements INodeType { endpoint = 'forums/listThreads.json'; const id = this.getNodeParameter('id', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + qs.forum = id; + qs.limit = 100; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); + try { + let responseData: IDataObject = {}; + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + } + returnData.push.apply(returnData, responseData.response as IDataObject[]); + } catch (error) { + throw error; + } + } else { throw new Error(`The operation "${operation}" is not known!`); } @@ -490,41 +664,6 @@ export class Disqus implements INodeType { } else { throw new Error(`The resource "${resource}" is not known!`); } - - qs.api_key = credentials.accessToken; - endpoint = `https://disqus.com/api/3.0/${endpoint}`; - - const options: OptionsWithUri = { - method: requestMethod, - qs, - uri: endpoint, - json: true, - }; - - - let responseData; - try { - responseData = await this.helpers.request(options); - } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Disqus credentials are not valid!'); - } - - if (error.error && error.error.error_summary) { - // Try to return the error prettier - throw new Error(`Disqus error response [${error.statusCode}]: ${error.error.error_summary}`); - } - - // If that data does not exist for some reason return the actual error - throw error; - } - - if (responseData && 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/Disqus/GenericFunctions.ts b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts new file mode 100644 index 000000000..e5e5c1b7d --- /dev/null +++ b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts @@ -0,0 +1,87 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function disqusApiRequest( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + qs: IDataObject = {}, + uri?: string, + body: any = {}, + option: IDataObject = {} + ): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('disqusApi') as IDataObject; + qs.api_key = credentials.accessToken; + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + let options: OptionsWithUri = { + method, + qs, + body, + uri: `https://disqus.com/api/3.0/${uri}`, + json: true + }; + + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + try { + const result = await this.helpers.request!(options) + return result; + } catch (error) { + console.log(error) + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Disqus credentials are not valid!'); + } + + if (error.error && error.error.error_summary) { + // Try to return the error prettier + throw new Error(`Disqus error response [${error.statusCode}]: ${error.error.error_summary}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} + +/** + * Make an API request to paginated flow endpoint + * and return all results + */ +export async function disqusApiRequestAllItems( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + qs: IDataObject = {}, + uri?: string, + body: any = {}, + option: IDataObject = {} + ): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + try { + do { + responseData = await disqusApiRequest.call(this, method, qs, uri, body, option); + qs.cursor = responseData.cursor.id; + returnData.push.apply(returnData, responseData.response); + } while ( + responseData.cursor.more === true && + responseData.cursor.hasNext === true + ); + return returnData; + } catch(error) { + throw error; + } +} From 70e26681a483181ff2c8daa7378724f4ef721cf3 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 23 Jan 2020 12:27:28 -0500 Subject: [PATCH 11/17] :sparkles: segment node --- .../credentials/SegmentApi.credentials.ts | 17 + .../nodes/Segment/GenericFunctions.ts | 36 + .../nodes/Segment/IdentifyDescription.ts | 508 ++++++++ .../nodes/Segment/IdentifyInterface.ts | 10 + .../nodes-base/nodes/Segment/Segment.node.ts | 784 +++++++++++ .../nodes/Segment/TrackDescription.ts | 1155 +++++++++++++++++ .../nodes/Segment/TrackInterface.ts | 13 + packages/nodes-base/nodes/Segment/segment.png | Bin 0 -> 1144 bytes packages/nodes-base/package.json | 4 + 9 files changed, 2527 insertions(+) create mode 100644 packages/nodes-base/credentials/SegmentApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Segment/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Segment/IdentifyDescription.ts create mode 100644 packages/nodes-base/nodes/Segment/IdentifyInterface.ts create mode 100644 packages/nodes-base/nodes/Segment/Segment.node.ts create mode 100644 packages/nodes-base/nodes/Segment/TrackDescription.ts create mode 100644 packages/nodes-base/nodes/Segment/TrackInterface.ts create mode 100644 packages/nodes-base/nodes/Segment/segment.png diff --git a/packages/nodes-base/credentials/SegmentApi.credentials.ts b/packages/nodes-base/credentials/SegmentApi.credentials.ts new file mode 100644 index 000000000..45858d327 --- /dev/null +++ b/packages/nodes-base/credentials/SegmentApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SegmentApi implements ICredentialType { + name = 'segmentApi'; + displayName = 'Segment API'; + properties = [ + { + displayName: 'Write Key', + name: 'writekey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Segment/GenericFunctions.ts b/packages/nodes-base/nodes/Segment/GenericFunctions.ts new file mode 100644 index 000000000..bd3ba6b87 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/GenericFunctions.ts @@ -0,0 +1,36 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function segmentApiRequest(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('segmentApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const base64Key = Buffer.from(`${credentials.writekey}:`).toString('base64'); + const options: OptionsWithUri = { + headers: { + Authorization: `Basic ${base64Key}`, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://api.segment.io/v1${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.body; + } + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('Segment Error: ' + error); + } +} diff --git a/packages/nodes-base/nodes/Segment/IdentifyDescription.ts b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts new file mode 100644 index 000000000..750a4d03d --- /dev/null +++ b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts @@ -0,0 +1,508 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const identifyOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'identify', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an identity', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const identifyFields = [ + +/* -------------------------------------------------------------------------- */ +/* identify:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + required: false, + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'emaiL', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first created', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Segment/IdentifyInterface.ts b/packages/nodes-base/nodes/Segment/IdentifyInterface.ts new file mode 100644 index 000000000..ae3d1e4c7 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/IdentifyInterface.ts @@ -0,0 +1,10 @@ +import { IDataObject } from "n8n-workflow"; + +export interface IIdentify { + userId?: string; + anonymousId?: string; + traits?: IDataObject; + context?: IDataObject; + integrations?: IDataObject; + timestamp?: string; +} diff --git a/packages/nodes-base/nodes/Segment/Segment.node.ts b/packages/nodes-base/nodes/Segment/Segment.node.ts new file mode 100644 index 000000000..f34f1c266 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/Segment.node.ts @@ -0,0 +1,784 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { + segmentApiRequest, +} from './GenericFunctions'; +import { + identifyFields, + identifyOperations, +} from './IdentifyDescription'; +import { + IIdentify, +} from './IdentifyInterface'; +import { + trackOperations, + trackFields, +} from './TrackDescription'; +import { ITrack } from './TrackInterface'; +import * as uuid from 'uuid/v4'; + +export class Segment implements INodeType { + description: INodeTypeDescription = { + displayName: 'Segment', + name: 'segment', + icon: 'file:segment.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', + description: 'Consume Segment API', + defaults: { + name: 'Segment', + color: '#6ebb99', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'segmentApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Identify', + value: 'identify', + description: 'Identify lets you tie a user to their actions.' + }, + { + name: 'Track', + value: 'track', + description: 'Track lets you record events', + }, + ], + default: 'identify', + description: 'Resource to consume.', + }, + ...identifyOperations, + ...trackOperations, + ...identifyFields, + ...trackFields, + ], + }; + + 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 === 'identify') { + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#identify + if (operation === 'create') { + const userId = this.getNodeParameter('userId', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const body: IIdentify = { + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/identify', body); + } + } + if (resource === 'track') { + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#track + if (operation === 'event') { + const userId = this.getNodeParameter('userId', i) as string; + const event = this.getNodeParameter('event', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const properties = (this.getNodeParameter('properties', i) as IDataObject).propertiesUi as IDataObject; + const body: ITrack = { + event, + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + properties: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + if (properties) { + if (properties.revenue) { + body.properties!.revenue = properties.revenue as number; + } + if (properties.currency) { + body.properties!.currency = properties.currency as string; + } + if (properties.value) { + body.properties!.value = properties.value as string; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/track', body); + } + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#page + if (operation === 'page') { + const userId = this.getNodeParameter('userId', i) as string; + const event = this.getNodeParameter('event', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const properties = (this.getNodeParameter('properties', i) as IDataObject).propertiesUi as IDataObject; + const body: ITrack = { + event, + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + properties: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + if (properties) { + if (properties.name) { + body.properties!.name = properties.name as number; + } + if (properties.path) { + body.properties!.path = properties.path as string; + } + if (properties.referrer) { + body.properties!.referrer = properties.referrer as string; + } + if (properties.search) { + body.properties!.search = properties.search as string; + } + if (properties.title) { + body.properties!.title = properties.title as string; + } + if (properties.url) { + body.properties!.url = properties.url as string; + } + if (properties.keywords) { + body.properties!.keywords = properties.keywords as string; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/page', 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/Segment/TrackDescription.ts b/packages/nodes-base/nodes/Segment/TrackDescription.ts new file mode 100644 index 000000000..cd03f9064 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/TrackDescription.ts @@ -0,0 +1,1155 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const trackOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'track', + ], + }, + }, + options: [ + { + name: 'Event', + value: 'event', + description: 'lets you record the actions your users perform.Every action triggers what we call an “event”, which can also have associated properties.', + }, + { + name: 'Page', + value: 'page', + description: ' lets you record page views on your website, along with optional extra information about the page being viewed.', + }, + ], + default: 'event', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const trackFields = [ + +/* -------------------------------------------------------------------------- */ +/* track:event */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + required: false, + }, + { + displayName: 'Event', + name: 'event', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + description: 'Name of the action that a user has performed.', + required: true, + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'emaiL', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'eventd At', + name: 'eventdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first eventd', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'properties', + placeholder: 'Add Properties', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'propertiesUi', + displayName: 'Properties', + values: [ + { + displayName: 'Revenue', + name: 'revenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 1, + description: 'Amount of revenue an event resulted in. This should be a decimal value, so a shirt worth $19.99 would result in a revenue of 19.99.' + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + description: 'Currency of the revenue an event resulted in

This should be sent in the ISO 4127 format. If this is not set, we assume the revenue to be in US dollars.

' + }, + { + displayName: 'Value', + name: 'value', + type: 'number', + default: '', + description: 'An abstract “value” to associate with an event. This is typically used in situations where the event doesn’t generate real-dollar revenue, but has an intrinsic value to a marketing team, like newsletter signups.' + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* track:page */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + description: 'Name of the page For example, most sites have a “Signup” page that can be useful to tag, so you can see users as they move through your funnel', + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'emaiL', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'eventd At', + name: 'eventdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first eventd', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'properties', + placeholder: 'Add Properties', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'propertiesUi', + displayName: 'Properties', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the page. This is reserved for future use.' + }, + { + displayName: 'Path', + name: 'path', + type: 'string', + default: '', + description: 'Path portion of the URL of the page. Equivalent to canonical path which defaults to location.pathname from the DOM API.' + }, + { + displayName: 'Referrer', + name: 'referrer', + type: 'string', + default: '', + description: 'Full URL of the previous page. Equivalent to document.referrer from the DOM API.' + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'Query string portion of the URL of the page. Equivalent to location.search from the DOM API.' + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the page. Equivalent to document.title from the DOM API.' + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + description: 'Full URL of the page. First we look for the canonical url. If the canonical url is not provided, we use location.href from the DOM API.' + }, + { + displayName: 'Keywords', + name: 'keywords', + type: 'string', + default: '', + description: 'A list/array of keywords describing the content of the page. The keywords would most likely be the same as, or similar to, the keywords you would find in an html meta tag for SEO purposes.' + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Segment/TrackInterface.ts b/packages/nodes-base/nodes/Segment/TrackInterface.ts new file mode 100644 index 000000000..85ce0924e --- /dev/null +++ b/packages/nodes-base/nodes/Segment/TrackInterface.ts @@ -0,0 +1,13 @@ +import { IDataObject } from "n8n-workflow"; + +export interface ITrack { + event?: string; + userId?: string; + name?: string; + anonymousId?: string; + traits?: IDataObject; + context?: IDataObject; + timestamp?: string; + properties?: IDataObject; + integrations?: IDataObject; +} diff --git a/packages/nodes-base/nodes/Segment/segment.png b/packages/nodes-base/nodes/Segment/segment.png new file mode 100644 index 0000000000000000000000000000000000000000..5dbfcaf09dc9a98928bee9f20849f28b7d9113df GIT binary patch literal 1144 zcmV-;1c&>HP)cb{$Bwr$(CZQJj)XaAX5Z%#UG+9s3fnfVRcCOKblbaMl| zQEF}-8w-W;J*Hw0uHywhLBNi;cz}~wfj+2#R0s<&bD@GUQ31no5Kr(K!bNX!1&h%J zG0YY;724te-a>eg9p9QIXi{{;B?vG1goZ{3jffUF3E^eukkH7m=`a`XA$(vM0wr~^ zAr~qk8$vx9G!T_=%8gs{4)$XKMxX~eqALbsIyU1HUPJuh4T?$X69<#<1RwDbBM{va zVMFmH9zr;Sk2r-!CymrbB(h4__&5w9aS9m%tWy4X4(yV!Pf^KAVH0R{<_-)%u$2P0 z(U`0sM&~EqOHzHWfX*o*p*71k$ia%nJg9fk1Kpm^5tsS0w|WbrJ+L`GGK*aXJG>!Y&9E zSIroBtOx$2dsj9gvSQ%IlI1D~x>)L#X4??VhEVYwC6piCgD{ED=%OQVdfbLkaS*YT zALU0=G&6y^I@i!T!yyUWAAwdHY$U^ZN!S-CY^AV4=%aCU*CB$H0vE!4N!Sl)V5P8e zumwV5JEB`9Y&eX7UE{sYT2=`gg0^@EAu$i(-RawYLLbyaG!ujk#SeG|A#oL1T-|Xg zT!8q)M=U^WgWvV+g4YldZ_&cl9Y>eUIvn`Lz&CiO;3I^D9m8RBb?4df3_`^Ug!nLM z7^>nBgcC~;)18B4!XpS33lZ$2hAA(m;U$C<8<0fLv$9OmkvG+7OtK;wf^-Cpj^MXK0000< KMNUMnLSTYtR{A{v literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 9cd7f397c..392bf6171 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -74,6 +74,7 @@ "dist/credentials/Smtp.credentials.js", "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", + "dist/credentials/SegmentApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", @@ -172,6 +173,7 @@ "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Switch.node.js", "dist/nodes/Salesmate/Salesmate.node.js", + "dist/nodes/Segment/Segment.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", "dist/nodes/Todoist/Todoist.node.js", @@ -206,6 +208,7 @@ "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", + "@types/uuid": "^3.4.6", "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", @@ -237,6 +240,7 @@ "redis": "^2.8.0", "rhea": "^1.0.11", "rss-parser": "^3.7.0", + "uuid": "^3.4.0", "vm2": "^3.6.10", "xlsx": "^0.14.3", "xml2js": "^0.4.22" From 5813ec7c3f658c1403eb30420ca8843762be90b7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 23 Jan 2020 14:50:22 -0800 Subject: [PATCH 12/17] :zap: Some improvements on Disqus-Node --- .../nodes-base/nodes/Disqus/Disqus.node.ts | 321 ++++++++++++------ .../nodes/Disqus/GenericFunctions.ts | 26 +- 2 files changed, 235 insertions(+), 112 deletions(-) diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 213da8ef4..3514fac0c 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -1,5 +1,4 @@ import { - BINARY_ENCODING, IExecuteFunctions, } from 'n8n-core'; import { @@ -42,7 +41,7 @@ export class Disqus implements INodeType { { name: 'Forum', value: 'forum', - } + }, ], default: 'forum', description: 'The resource to operate on.', @@ -127,18 +126,62 @@ export class Disqus implements INodeType { default: {}, options: [ { - displayName: 'attach', + displayName: 'Attach', name: 'attach', - type: 'string', - default: '[]', - description: 'Choices: followsForum, forumCanDisableAds, forumForumCategory, counters, forumDaysAlive, forumFeatures, forumIntegration, forumNewPolicy, forumPermissions', + type: 'multiOptions', + options: [ + { + name: 'counters', + value: 'counters', + }, + { + name: 'followsForum', + value: 'followsForum', + }, + { + name: 'forumCanDisableAds', + value: 'forumCanDisableAds', + }, + { + name: 'forumDaysAlive', + value: 'forumDaysAlive', + }, + { + name: 'forumFeatures', + value: 'forumFeatures', + }, + { + name: 'forumForumCategory', + value: 'forumForumCategory', + }, + { + name: 'forumIntegration', + value: 'forumIntegration', + }, + { + name: 'forumNewPolicy', + value: 'forumNewPolicy', + }, + { + name: 'forumPermissions', + value: 'forumPermissions', + }, + ], + default: [], + description: 'The resource to operate on.', }, { - displayName: 'related', + displayName: 'Related', name: 'related', - type: 'string', - default: false, - description: 'You may specify relations to include with your response. Choices `author`', + type: 'multiOptions', + options: [ + { + name: 'author', + value: 'author', + }, + ], + default: [], + description: 'You may specify relations to include with your response', }, ], }, @@ -222,61 +265,115 @@ export class Disqus implements INodeType { }, default: {}, options: [ - { - displayName: 'Since', - name: 'since', - type: 'string', - default: `[]`, - description: 'Unix timestamp (or ISO datetime standard)', - }, - { - displayName: 'Related', - name: 'related', - type: 'string', - default: '[]', - description: 'You may specify relations to include with your response. Choices `author`', - }, - { - displayName: 'Cursor', - name: 'cursor', - type: 'string', - default: '', - description: 'You may specify cursor for your response.', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'string', - default: 25, - description: 'You may specify relations maximum number of posts to return. Maximum value is 100', - }, { displayName: 'Filters', name: 'filters', - type: 'string', - default: '[]', - description: 'You may specify filters for your response. Choices: `Is_Anonymous`, `Has_Link`, `Has_Low_Rep_Author`, `Has_Bad_Word`, `Is_Flagged`, `No_Issue`, `Is_Toxic`, `Modified_By_Rule`, `Shadow_Banned`, `Has_Media`, `Is_At_Flag_Limit`', + type: 'multiOptions', + options: [ + { + name: 'Has_Bad_Word', + value: 'Has_Bad_Word', + }, + { + name: 'Has_Link', + value: 'Has_Link', + }, + { + name: 'Has_Low_Rep_Author', + value: 'Has_Low_Rep_Author', + }, + { + name: 'Has_Media', + value: 'Has_Media', + }, + { + name: 'Is_Anonymous', + value: 'Is_Anonymous', + }, + { + name: 'Is_Flagged', + value: 'Is_Flagged', + }, + { + name: 'No_Issue', + value: 'No_Issue', + }, + { + name: 'Is_At_Flag_Limit', + value: 'Is_At_Flag_Limit', + }, + { + name: 'Is_Toxic', + value: 'Is_Toxic', + }, + { + name: 'Modified_By_Rule', + value: 'Modified_By_Rule', + }, + { + name: 'Shadow_Banned', + value: 'Shadow_Banned', + }, + ], + default: [], + description: 'You may specify filters for your response. Choices: , ``, ``, ``', + }, + { + displayName: 'Include', + name: 'include', + type: 'multiOptions', + options: [ + { + name: 'author', + value: 'author', + }, + ], + default: [], + description: 'You may specify relations to include with your response.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], + default: 'asc', + description: 'You may specify order to sort your response.', }, { displayName: 'Query', name: 'query', type: 'string', default: '', - description: 'You may specify query for your response.', + description: 'You may specify query forChoices: asc, desc your response.', }, { - displayName: 'Include', - name: 'include', - type: 'string', - default: false, - description: 'You may specify relations to include with your response. Choices `author`', + displayName: 'Related', + name: 'related', + type: 'multiOptions', + options: [ + { + name: 'thread', + value: 'thread', + }, + ], + default: [], + description: 'You may specify relations to include with your response', }, { - displayName: 'Order', - name: 'order', - type: 'string', - default: 'asc', - description: 'You may specify order to sort your response.Choices: asc, desc', + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'Unix timestamp (or ISO datetime standard)', }, ], }, @@ -360,26 +457,22 @@ export class Disqus implements INodeType { }, default: {}, options: [ - { - displayName: 'Since ID', - name: 'sinceId', - type: 'string', - default: `[]`, - description: 'You may specify cursor since_id for your response.', - }, - { - displayName: 'Cursor', - name: 'cursor', - type: 'string', - default: '', - description: 'You may specify cursor for your response.', - }, { displayName: 'Order', name: 'order', - type: 'string', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], default: 'asc', - description: 'You may specify order to sort your response.Choices: asc, desc', + description: 'You may specify order to sort your response.', }, ], }, @@ -463,54 +556,74 @@ export class Disqus implements INodeType { }, default: {}, options: [ - { - displayName: 'Thread', - name: 'threadId', - type: 'string', - default: '', - description: 'Looks up a thread by ID. You may pass us the "ident" query type instead of an ID by including "forum". You may pass us the "link" query type to filter by URL. You must pass the "forum" if you do not have the Pro API Access addon.', - }, - { - displayName: 'Since', - name: 'since', - type: 'string', - default: '', - description: 'Unix timestamp (or ISO datetime standard)', - }, { displayName: 'Related', name: 'related', - type: 'string', - default: '[]', - description: 'You may specify relations to include with your response. Choices `author`', - }, - { - displayName: 'Cursor', - name: 'cursor', - type: 'string', - default: '', - description: 'You may specify cursor for your response.', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'string', - default: 25, - description: 'You may specify relations maximum number of posts to return. Maximum value is 100', + type: 'multiOptions', + options: [ + { + name: 'author', + value: 'author', + }, + { + name: 'forum', + value: 'forum', + }, + ], + default: [], + description: 'You may specify relations to include with your response', }, { displayName: 'Include', name: 'include', - type: 'string', - default: '', - description: 'You may specify relations to include with your response. Choices: open, closed, killed', + type: 'multiOptions', + options: [ + { + name: 'closed', + value: 'closed', + }, + { + name: 'open', + value: 'open', + }, + { + name: 'killed', + value: 'killed', + }, + ], + default: [], + description: 'You may specify relations to include with your response.', }, { displayName: 'Order', name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], + default: 'asc', + description: 'You may specify order to sort your response.', + }, + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'Unix timestamp (or ISO datetime standard)', + }, + { + displayName: 'Thread', + name: 'threadId', type: 'string', - default: 'desc', - description: 'You may specify order to sort your response.Choices: asc, desc', + default: '', + description: 'Looks up a thread by ID. You may pass us the "ident"
query type instead of an ID by including "forum". You may
pass us the "link" query type to filter by URL. You must pass
the "forum" if you do not have the Pro API Access addon.', }, ], } diff --git a/packages/nodes-base/nodes/Disqus/GenericFunctions.ts b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts index e5e5c1b7d..5f5f88bfd 100644 --- a/packages/nodes-base/nodes/Disqus/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts @@ -12,8 +12,8 @@ export async function disqusApiRequest( method: string, qs: IDataObject = {}, uri?: string, - body: any = {}, - option: IDataObject = {} + body: IDataObject = {}, + option: IDataObject = {}, ): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('disqusApi') as IDataObject; @@ -22,11 +22,22 @@ export async function disqusApiRequest( throw new Error('No credentials got returned!'); } + // Convert to query string into a format the API can read + const queryStringElements: string[] = []; + for (const key of Object.keys(qs)) { + if (Array.isArray(qs[key])) { + (qs[key] as string[]).forEach(value => { + queryStringElements.push(`${key}=${value}`); + }); + } else { + queryStringElements.push(`${key}=${qs[key]}`); + } + } + let options: OptionsWithUri = { method, - qs, body, - uri: `https://disqus.com/api/3.0/${uri}`, + uri: `https://disqus.com/api/3.0/${uri}?${queryStringElements.join('&')}`, json: true }; @@ -35,10 +46,9 @@ export async function disqusApiRequest( delete options.body; } try { - const result = await this.helpers.request!(options) + const result = await this.helpers.request!(options); return result; } catch (error) { - console.log(error) if (error.statusCode === 401) { // Return a clear error throw new Error('The Disqus credentials are not valid!'); @@ -63,8 +73,8 @@ export async function disqusApiRequestAllItems( method: string, qs: IDataObject = {}, uri?: string, - body: any = {}, - option: IDataObject = {} + body: IDataObject = {}, + option: IDataObject = {}, ): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; From 62134f639b0b9d0b3614439d7dd1ba7bca097938 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 23 Jan 2020 15:57:34 -0800 Subject: [PATCH 13/17] :bug: Fix issue with deleting fixedCollection with multipleValues=false --- packages/editor-ui/src/components/NodeSettings.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 4189d78c3..ca3cfde88 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -57,7 +57,7 @@ import ParameterInputFull from '@/components/ParameterInputFull.vue'; import ParameterInputList from '@/components/ParameterInputList.vue'; import NodeCredentials from '@/components/NodeCredentials.vue'; import NodeWebhooks from '@/components/NodeWebhooks.vue'; -import { get, set } from 'lodash'; +import { get, set, unset } from 'lodash'; import { genericHelpers } from '@/components/mixins/genericHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers'; @@ -369,8 +369,11 @@ export default mixins( Vue.set(nodeParameters as object, path, data); } } else { - // For everything else - set(nodeParameters as object, parameterPath, newValue); + if (newValue === undefined) { + unset(nodeParameters as object, parameterPath); + } else { + set(nodeParameters as object, parameterPath, newValue); + } } // Get the parameters with the now new defaults according to the From de098f7da4bc5e8fd73b8e0210b9c8f15a39a97e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 23 Jan 2020 16:05:27 -0800 Subject: [PATCH 14/17] :zap: Fix issue that parameter inputs on lower levels are very small --- packages/editor-ui/src/components/ParameterInputFull.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index c7ce18c98..4d488faf9 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -30,6 +30,9 @@ export default Vue }, computed: { isMultiLineParameter () { + if (this.level > 4) { + return true; + } const rows = this.getArgument('rows'); if (rows !== undefined && rows > 1) { return true; @@ -37,6 +40,9 @@ export default Vue return false; }, + level (): number { + return this.path.split('.').length; + }, }, props: [ 'displayOptions', From 828962deebd96cf735e0efd1f5f153c285725445 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 23 Jan 2020 16:40:39 -0800 Subject: [PATCH 15/17] :zap: Some minor fixes on Segment-Node --- .../nodes/Segment/IdentifyDescription.ts | 2 +- .../nodes-base/nodes/Segment/Segment.node.ts | 10 ---------- .../nodes-base/nodes/Segment/TrackDescription.ts | 16 ++++++++-------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/packages/nodes-base/nodes/Segment/IdentifyDescription.ts b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts index 750a4d03d..f80fb2bc3 100644 --- a/packages/nodes-base/nodes/Segment/IdentifyDescription.ts +++ b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts @@ -72,7 +72,7 @@ export const identifyFields = [ values: [ { displayName: 'Email', - name: 'emaiL', + name: 'email', type: 'string', default: '', description: 'Email address of a user', diff --git a/packages/nodes-base/nodes/Segment/Segment.node.ts b/packages/nodes-base/nodes/Segment/Segment.node.ts index f34f1c266..bd281366b 100644 --- a/packages/nodes-base/nodes/Segment/Segment.node.ts +++ b/packages/nodes-base/nodes/Segment/Segment.node.ts @@ -323,11 +323,6 @@ export class Segment implements INodeType { } else { body.anonymousId = uuid(); } - if (userId) { - body.userId = userId as string; - } else { - body.anonymousId = uuid(); - } if (traits) { if (traits.email) { body.traits!.email = traits.email as string; @@ -555,11 +550,6 @@ export class Segment implements INodeType { } else { body.anonymousId = uuid(); } - if (userId) { - body.userId = userId as string; - } else { - body.anonymousId = uuid(); - } if (traits) { if (traits.email) { body.traits!.email = traits.email as string; diff --git a/packages/nodes-base/nodes/Segment/TrackDescription.ts b/packages/nodes-base/nodes/Segment/TrackDescription.ts index cd03f9064..8077f412d 100644 --- a/packages/nodes-base/nodes/Segment/TrackDescription.ts +++ b/packages/nodes-base/nodes/Segment/TrackDescription.ts @@ -95,7 +95,7 @@ export const trackFields = [ values: [ { displayName: 'Email', - name: 'emaiL', + name: 'email', type: 'string', default: '', description: 'Email address of a user', @@ -164,11 +164,11 @@ export const trackFields = [ description: 'User’s birthday', }, { - displayName: 'eventd At', - name: 'eventdAt', + displayName: 'Created At', + name: 'createdAt', type: 'dateTime', default: '', - description: 'Date the user’s account was first eventd', + description: 'Date the user’s account was first created at', }, { displayName: 'Description', @@ -642,7 +642,7 @@ export const trackFields = [ values: [ { displayName: 'Email', - name: 'emaiL', + name: 'email', type: 'string', default: '', description: 'Email address of a user', @@ -711,11 +711,11 @@ export const trackFields = [ description: 'User’s birthday', }, { - displayName: 'eventd At', - name: 'eventdAt', + displayName: 'Created At', + name: 'createdAt', type: 'dateTime', default: '', - description: 'Date the user’s account was first eventd', + description: 'Date the user’s account was first created at', }, { displayName: 'Description', From 6f4ee39a05d74086413c6bb88067ee01fc41bc60 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 23 Jan 2020 20:20:38 -0800 Subject: [PATCH 16/17] :zap: Minor improvements on Trello-Node --- .../nodes-base/nodes/Trello/Trello.node.ts | 183 ++++++++++++++---- 1 file changed, 144 insertions(+), 39 deletions(-) diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index b95025ae8..ed2e9c2ce 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -40,8 +40,8 @@ export class Trello implements INodeType { type: 'options', options: [ { - name: "Attachment", - value: "attachment" + name: 'Attachment', + value: 'attachment' }, { name: 'Board', @@ -52,13 +52,13 @@ export class Trello implements INodeType { value: 'card', }, { - name: "Checklist", - value: "checklist", + name: 'Checklist', + value: 'checklist', }, { - name: "Label", - value: "label" - } + name: 'Label', + value: 'label' + }, { name: 'List', value: 'list', @@ -1508,11 +1508,6 @@ export class Trello implements INodeType { }, }, options: [ - { - name: "Completed CheckItems", - value: "completedCheckItems", - description: "Get the completed checklist items on a card" - }, { name: 'Create', value: 'create', @@ -1524,7 +1519,7 @@ export class Trello implements INodeType { description: 'Delete a checklist', }, { - name: 'Delete CheckItem', + name: 'Delete Checklist Item', value: 'deleteCheckItem', description: 'Delete a checklist item', }, @@ -1533,21 +1528,26 @@ export class Trello implements INodeType { value: 'get', description: 'Get the data of a checklist', }, - { - name: 'Get CheckItem', - value: 'getCheckItem', - description: 'Get a specific checkItem on a card', - }, { name: 'Get All', value: 'getAll', description: 'Returns all checklists for the card', }, { - name: 'Update CheckItem', + name: 'Get Checklist Items', + value: 'getCheckItem', + description: 'Get a specific Checklist on a card', + }, + { + name: 'Get Completed Checklist Items', + value: 'completedCheckItems', + description: 'Get the completed Checklist items on a card', + }, + { + name: 'Update Checklist Item', value: 'updateCheckItem', description: 'Update an item in a checklist on a card.', - } + }, ], default: 'getAll', description: 'The operation to perform.', @@ -1800,7 +1800,7 @@ export class Trello implements INodeType { ], }, }, - description: 'The ID of the checklist to delete.', + description: 'The ID of the checklist item to delete.', }, // ---------------------------------- @@ -1840,7 +1840,7 @@ export class Trello implements INodeType { ], }, }, - description: 'The ID of the checklist to get.', + description: 'The ID of the checklist item to get.', }, { displayName: 'Additional Fields', @@ -1935,9 +1935,19 @@ export class Trello implements INodeType { { displayName: 'State', name: 'state', - type: 'string', - default: '', - description: 'One of: complete, incomplete', + type: 'options', + options: [ + { + name: 'complete', + value: 'complete' + }, + { + name: 'incomplete', + value: 'incomplete', + }, + ], + default: 'complete', + description: 'The resource to operate on.', }, { displayName: 'Checklist ID', @@ -2102,8 +2112,7 @@ export class Trello implements INodeType { { displayName: 'Color', name: 'color', - type: 'string', - default: '', + type: 'options', required: true, displayOptions: { show: { @@ -2115,9 +2124,57 @@ export class Trello implements INodeType { ], }, }, - description: 'The color for the label. See fields for color options.', + options: [ + { + name: 'black', + value: 'black', + }, + { + name: 'blue', + value: 'blue', + }, + { + name: 'green', + value: 'green' + }, + { + name: 'orange', + value: 'orange', + }, + { + name: 'lime', + value: 'lime', + }, + { + name: 'null', + value: 'null', + }, + { + name: 'pink', + value: 'pink', + }, + { + name: 'purple', + value: 'purple', + }, + { + name: 'red', + value: 'red', + }, + { + name: 'sky', + value: 'sky', + }, + { + name: 'yellow', + value: 'yellow' + }, + ], + default: 'null', + description: 'The color for the label.', }, + // ---------------------------------- // label:delete // ---------------------------------- @@ -2364,10 +2421,56 @@ export class Trello implements INodeType { { displayName: 'Color', name: 'color', - type: 'string', - default: '', - description: 'The color for the label. See fields for color options.', - } + type: 'options', + options: [ + { + name: 'black', + value: 'black', + }, + { + name: 'blue', + value: 'blue', + }, + { + name: 'green', + value: 'green' + }, + { + name: 'orange', + value: 'orange', + }, + { + name: 'lime', + value: 'lime', + }, + { + name: 'null', + value: 'null', + }, + { + name: 'pink', + value: 'pink', + }, + { + name: 'purple', + value: 'purple', + }, + { + name: 'red', + value: 'red', + }, + { + name: 'sky', + value: 'sky', + }, + { + name: 'yellow', + value: 'yellow' + }, + ], + default: 'null', + description: 'The color for the label.', + }, ], }, ], @@ -2516,7 +2619,6 @@ export class Trello implements INodeType { throw new Error(`The operation "${operation}" is not known!`); } - } else if (resource === 'list') { if (operation === 'archive') { @@ -2642,6 +2744,7 @@ export class Trello implements INodeType { } else { throw new Error(`The operation "${operation}" is not known!`); } + } else if (resource === 'checklist') { if (operation === 'create') { @@ -2700,6 +2803,7 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); + } else if (operation === 'getCheckItem') { // ---------------------------------- // getCheckItem @@ -2714,6 +2818,7 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); + } else if (operation === 'deleteCheckItem') { // ---------------------------------- // deleteCheckItem @@ -2725,6 +2830,7 @@ export class Trello implements INodeType { const checkItemId = this.getNodeParameter('checkItemId', i) as string; endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + } else if (operation === 'updateCheckItem') { // ---------------------------------- // updateCheckItem @@ -2739,9 +2845,10 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); - } else if(operation ==="completedCheckItems") { + + } else if (operation ==='completedCheckItems') { // ---------------------------------- - // updateCheckItem + // completedCheckItems // ---------------------------------- requestMethod = 'GET'; @@ -2769,15 +2876,13 @@ export class Trello implements INodeType { const name = this.getNodeParameter('name', i) as string; const color = this.getNodeParameter('color', i) as string; - Object.assign(qs, { idBoard, name, color }); - - endpoint = "labels"; + endpoint = 'labels'; } else if (operation === 'delete') { // ---------------------------------- @@ -2848,7 +2953,7 @@ export class Trello implements INodeType { } else if (operation === 'removeLabel') { // ---------------------------------- - // addLabel + // removeLabel // ---------------------------------- requestMethod = 'DELETE'; From 0507c7288410217beea700bdcbce84f22472cf0d Mon Sep 17 00:00:00 2001 From: trojanh Date: Fri, 24 Jan 2020 10:27:59 +0530 Subject: [PATCH 17/17] disqus-node: minor fixes --- packages/nodes-base/nodes/Disqus/Disqus.node.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 3514fac0c..df07e8945 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -316,7 +316,7 @@ export class Disqus implements INodeType { }, ], default: [], - description: 'You may specify filters for your response. Choices: , ``, ``, ``', + description: 'You may specify filters for your response.' }, { displayName: 'Include', @@ -324,8 +324,8 @@ export class Disqus implements INodeType { type: 'multiOptions', options: [ { - name: 'author', - value: 'author', + name: 'approved', + value: 'approved', }, ], default: [],