diff --git a/packages/nodes-base/nodes/Hubspot/V2/EngagementDescription.ts b/packages/nodes-base/nodes/Hubspot/V2/EngagementDescription.ts index 22a746f667..ac67008a55 100644 --- a/packages/nodes-base/nodes/Hubspot/V2/EngagementDescription.ts +++ b/packages/nodes-base/nodes/Hubspot/V2/EngagementDescription.ts @@ -76,6 +76,22 @@ export const engagementFields: INodeProperties[] = [ }, default: '', }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + required: true, + description: 'The due date for the task', + displayOptions: { + show: { + resource: ['engagement'], + operation: ['create'], + type: ['task'], + '@version': [{ _cnd: { gte: 2.2 } }], + }, + }, + default: '', + }, { displayName: 'Metadata', name: 'metadata', diff --git a/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts b/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts index e863795225..f60538b075 100644 --- a/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts +++ b/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts @@ -39,6 +39,7 @@ import { } from './GenericFunctions'; import { ticketFields, ticketOperations } from './TicketDescription'; import { generatePairedItemData } from '../../../utils/utilities'; +import { parseToTimestamp } from './utils/parseToTimestamp'; export class HubspotV2 implements INodeType { description: INodeTypeDescription; @@ -1144,7 +1145,7 @@ export class HubspotV2 implements INodeType { const qs: IDataObject = {}; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); - + const version = this.getNode().typeVersion; //https://legacydocs.hubspot.com/docs/methods/lists/contact-lists-overview if (resource === 'contactList') { try { @@ -2742,7 +2743,7 @@ export class HubspotV2 implements INodeType { : undefined; const body: { - engagement: { type: string; ownerId?: number }; + engagement: { type: string; ownerId?: number; timestamp?: number }; metadata: IDataObject; associations: IDataObject; } = { @@ -2760,6 +2761,13 @@ export class HubspotV2 implements INodeType { if (type === 'task') { body.metadata = getTaskMetadata(metadata); + if (version >= 2.2) { + const dueDateParameter = this.getNodeParameter('dueDate', i, null); + if (dueDateParameter) { + const timestamp = parseToTimestamp(dueDateParameter); + body.engagement.timestamp = timestamp; + } + } } if (type === 'meeting') { diff --git a/packages/nodes-base/nodes/Hubspot/V2/utils/parseToTimestamp.ts b/packages/nodes-base/nodes/Hubspot/V2/utils/parseToTimestamp.ts new file mode 100644 index 0000000000..9298022ebc --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/V2/utils/parseToTimestamp.ts @@ -0,0 +1,12 @@ +import moment from 'moment-timezone'; + +export function parseToTimestamp(dateString: unknown): number { + if (typeof dateString !== 'string') { + throw new Error('Invalid date string'); + } + const timestamp = moment(dateString).valueOf(); + if (isNaN(timestamp)) { + throw new Error('Invalid date string'); + } + return timestamp; +} diff --git a/packages/nodes-base/nodes/Hubspot/__test__/Hubspot.node.test.ts b/packages/nodes-base/nodes/Hubspot/__test__/Hubspot.node.test.ts index 012771eb7f..1e9d81f8c9 100644 --- a/packages/nodes-base/nodes/Hubspot/__test__/Hubspot.node.test.ts +++ b/packages/nodes-base/nodes/Hubspot/__test__/Hubspot.node.test.ts @@ -356,14 +356,12 @@ describe('Hubspot Node', () => { describe('engagements', () => { beforeAll(() => { hubspotNock - .post('/engagements/v1/engagements', function checkOwnerIdIsDefined(body) { - return body.engagement.ownerId === engagements.request[0].engagement.ownerId; - }) - .reply(200, engagements.response[0]) - .post('/engagements/v1/engagements', function checkOwnerIdIsNotDefined(body) { - return body.engagement.ownerId === undefined; - }) - .reply(200, engagements.response[1]); + .post('/engagements/v1/engagements', engagements.request.createWithOwnerId) + .reply(200, engagements.response.createWithOwnerId) + .post('/engagements/v1/engagements', engagements.request.createWithoutOwnerId) + .reply(200, engagements.response.createWithoutOwnerId) + .post('/engagements/v1/engagements', engagements.request.createV2_1) + .reply(200, engagements.response.createV2_1); }); afterAll(() => hubspotNock.done()); diff --git a/packages/nodes-base/nodes/Hubspot/__test__/engagements.workflow.json b/packages/nodes-base/nodes/Hubspot/__test__/engagements.workflow.json index 856ab85dbb..810172bf72 100644 --- a/packages/nodes-base/nodes/Hubspot/__test__/engagements.workflow.json +++ b/packages/nodes-base/nodes/Hubspot/__test__/engagements.workflow.json @@ -6,6 +6,7 @@ "authentication": "appToken", "resource": "engagement", "type": "task", + "dueDate": "2025-08-31T13:12:56", "metadata": { "body": "Hello world", "subject": "Title" @@ -33,6 +34,7 @@ "authentication": "appToken", "resource": "engagement", "type": "task", + "dueDate": "2025-08-31T13:12:56", "metadata": { "body": "Hello world", "subject": "Title" @@ -51,6 +53,29 @@ } } }, + { + "parameters": { + "authentication": "appToken", + "resource": "engagement", + "type": "task", + "metadata": { + "body": "Hello world", + "subject": "Title" + }, + "additionalFields": {} + }, + "type": "n8n-nodes-base.hubspot", + "typeVersion": 2.1, + "position": [0, 400], + "id": "7fb51d0d-28b5-450d-b910-f6d91e69e935", + "name": "HubSpot - Engagement - Create 2.1", + "credentials": { + "hubspotAppToken": { + "id": "vWZfuQawMAWdxKms", + "name": "HubSpot account" + } + } + }, { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", @@ -73,7 +98,7 @@ "lastUpdated": 1756127696406, "ownerId": 123456789, "type": "TASK", - "timestamp": 1756127696406, + "timestamp": 1756645976000, "allAccessibleTeamIds": [], "bodyPreview": "Hello world", "queueMembershipIds": [], @@ -120,7 +145,54 @@ "createdAt": 1756127766844, "lastUpdated": 1756127766844, "type": "TASK", - "timestamp": 1756127766844, + "timestamp": 1756645976000, + "allAccessibleTeamIds": [], + "bodyPreview": "Hello world", + "queueMembershipIds": [], + "bodyPreviewIsTruncated": false, + "bodyPreviewHtml": "\n \n \n Hello world\n \n" + }, + "associations": { + "contactIds": [], + "companyIds": [], + "dealIds": [], + "ownerIds": [], + "workflowIds": [], + "ticketIds": [], + "contentIds": [], + "quoteIds": [], + "marketingEventIds": [], + "partnerClientIds": [], + "orderIds": [], + "cartIds": [] + }, + "attachments": [], + "scheduledTasks": [], + "metadata": { + "body": "Hello world", + "status": "NOT_STARTED", + "forObjectType": "OWNER", + "subject": "Title", + "taskType": "TODO", + "reminders": [], + "priority": "NONE", + "isAllDay": false + } + } + } + ], + "HubSpot - Engagement - Create 2.1": [ + { + "json": { + "associationCreateFailures": [], + "engagement": { + "id": 274123775164, + "portalId": 146787276, + "active": true, + "createdAt": 1756127766844, + "lastUpdated": 1756127766844, + "type": "TASK", + "timestamp": 1756645976000, "allAccessibleTeamIds": [], "bodyPreview": "Hello world", "queueMembershipIds": [], @@ -170,6 +242,11 @@ "node": "HubSpot - Engagement - Create", "type": "main", "index": 0 + }, + { + "node": "HubSpot - Engagement - Create 2.1", + "type": "main", + "index": 0 } ] ] diff --git a/packages/nodes-base/nodes/Hubspot/__test__/fixtures/engagements.json b/packages/nodes-base/nodes/Hubspot/__test__/fixtures/engagements.json index 002e3a2a76..dbc2e566db 100644 --- a/packages/nodes-base/nodes/Hubspot/__test__/fixtures/engagements.json +++ b/packages/nodes-base/nodes/Hubspot/__test__/fixtures/engagements.json @@ -1,18 +1,23 @@ { - "request": [ - { - "engagement": { "type": "TASK", "ownerId": 123456789 }, + "request": { + "createWithOwnerId": { + "engagement": { "type": "TASK", "ownerId": 123456789, "timestamp": 1756645976000 }, "metadata": { "body": "Hello world", "subject": "Title" }, "associations": {} }, - { + "createWithoutOwnerId": { + "engagement": { "type": "TASK", "timestamp": 1756645976000 }, + "metadata": { "body": "Hello world", "subject": "Title" }, + "associations": {} + }, + "createV2_1": { "engagement": { "type": "TASK" }, "metadata": { "body": "Hello world", "subject": "Title" }, "associations": {} } - ], - "response": [ - { + }, + "response": { + "createWithOwnerId": { "associationCreateFailures": [], "engagement": { "id": 274361144556, @@ -22,7 +27,7 @@ "lastUpdated": 1756127696406, "ownerId": 123456789, "type": "TASK", - "timestamp": 1756127696406, + "timestamp": 1756645976000, "allAccessibleTeamIds": [], "bodyPreview": "Hello world", "queueMembershipIds": [], @@ -56,7 +61,7 @@ "isAllDay": false } }, - { + "createWithoutOwnerId": { "associationCreateFailures": [], "engagement": { "id": 274123775164, @@ -65,7 +70,50 @@ "createdAt": 1756127766844, "lastUpdated": 1756127766844, "type": "TASK", - "timestamp": 1756127766844, + "timestamp": 1756645976000, + "allAccessibleTeamIds": [], + "bodyPreview": "Hello world", + "queueMembershipIds": [], + "bodyPreviewIsTruncated": false, + "bodyPreviewHtml": "\n \n \n Hello world\n \n" + }, + "associations": { + "contactIds": [], + "companyIds": [], + "dealIds": [], + "ownerIds": [], + "workflowIds": [], + "ticketIds": [], + "contentIds": [], + "quoteIds": [], + "marketingEventIds": [], + "partnerClientIds": [], + "orderIds": [], + "cartIds": [] + }, + "attachments": [], + "scheduledTasks": [], + "metadata": { + "body": "Hello world", + "status": "NOT_STARTED", + "forObjectType": "OWNER", + "subject": "Title", + "taskType": "TODO", + "reminders": [], + "priority": "NONE", + "isAllDay": false + } + }, + "createV2_1": { + "associationCreateFailures": [], + "engagement": { + "id": 274123775164, + "portalId": 146787276, + "active": true, + "createdAt": 1756127766844, + "lastUpdated": 1756127766844, + "type": "TASK", + "timestamp": 1756645976000, "allAccessibleTeamIds": [], "bodyPreview": "Hello world", "queueMembershipIds": [], @@ -99,5 +147,5 @@ "isAllDay": false } } - ] + } } diff --git a/packages/nodes-base/nodes/Hubspot/__test__/utils/parseToTimestamp.test.ts b/packages/nodes-base/nodes/Hubspot/__test__/utils/parseToTimestamp.test.ts new file mode 100644 index 0000000000..7e17807251 --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/__test__/utils/parseToTimestamp.test.ts @@ -0,0 +1,19 @@ +import { parseToTimestamp } from '../../V2/utils/parseToTimestamp'; + +describe('parseToTimestamp', () => { + it('should convert a valid date string to a UTC Unix timestamp', () => { + const dateString = '2023-10-05T14:48:00Z'; + const expectedTimestamp = 1696517280000; + expect(parseToTimestamp(dateString)).toBe(expectedTimestamp); + }); + + it('should throw an error for an invalid date string', () => { + const invalidDateString = 'invalid-date'; + expect(() => parseToTimestamp(invalidDateString)).toThrow('Invalid date string'); + }); + + it('should throw an error when input is not a string', () => { + const invalidDateString = 1234567890; + expect(() => parseToTimestamp(invalidDateString)).toThrow('Invalid date string'); + }); +});