diff --git a/components/freshdesk/actions/update-ticket/update-ticket.mjs b/components/freshdesk/actions/update-ticket/update-ticket.mjs index f1dd5c8c1e7df..641b0e3d8d047 100644 --- a/components/freshdesk/actions/update-ticket/update-ticket.mjs +++ b/components/freshdesk/actions/update-ticket/update-ticket.mjs @@ -5,7 +5,7 @@ export default { key: "freshdesk-update-ticket", name: "Update a Ticket", description: "Update status, priority, subject, description, agent, group, etc. [See the documentation](https://developers.freshdesk.com/api/#update_ticket).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { freshdesk, @@ -71,6 +71,19 @@ export default { description: "Used when creating a contact with phone but no email.", optional: true, }, + internalNote: { + type: "boolean", + label: "Internal note (private)", + description: "If enabled, the comment will be added as an internal note (not visible to requester).", + optional: true, + default: false, + }, + noteBody: { + type: "string", + label: "Note Body", + description: "The content of the internal note to add.", + optional: true, + }, type: { type: "string", label: "Type", @@ -95,6 +108,8 @@ export default { const { freshdesk, ticketId, + internalNote, + noteBody, ...fields } = this; @@ -102,21 +117,35 @@ export default { const ticketName = await freshdesk.getTicketName(ticketId); + if (internalNote && noteBody) { + const response = await freshdesk._makeRequest({ + $, + method: "POST", + url: `/tickets/${ticketId}/notes`, + data: { + body: noteBody, + private: true, + }, + }); + + $.export("$summary", `Internal note added to ticket "${ticketName}" (ID: ${ticketId})`); + return response; + } + if (!Object.keys(data).length) { throw new Error("Please provide at least one field to update."); } - + if (data.custom_fields) freshdesk.parseIfJSONString(data.custom_fields); - + const response = await freshdesk._makeRequest({ $, method: "PUT", url: `/tickets/${ticketId}`, data, }); - + $.export("$summary", `Ticket "${ticketName}" (ID: ${this.ticketId}) updated successfully`); return response; }, -}; - +} \ No newline at end of file diff --git a/components/hubspot/actions/add-conversation-comment/add-conversation-comment.mjs b/components/hubspot/actions/add-conversation-comment/add-conversation-comment.mjs new file mode 100644 index 0000000000000..c4eac8c107dc6 --- /dev/null +++ b/components/hubspot/actions/add-conversation-comment/add-conversation-comment.mjs @@ -0,0 +1,43 @@ +import hubspot from "../../hubspot.app.mjs"; + +export default { + key: "hubspot-add-conversation-comment", + name: "Add Conversation Comment (Internal Note)", + description: "Add an internal comment to a HubSpot conversation thread. Internal comments are only visible to team members. [See the documentation](https://developers.hubspot.com/docs/api/conversations/threads)", + version: "0.0.1", + type: "action", + props: { + hubspot, + threadId: { + propDefinition: [ + hubspot, + "threadId", + ], + }, + text: { + type: "string", + label: "Comment Text", + description: "The plain text content of the internal comment", + }, + richText: { + type: "string", + label: "Rich Text", + description: "The rich text/HTML content of the internal comment", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.hubspot.addConversationComment({ + threadId: this.threadId, + data: { + text: this.text, + richText: this.richText || this.text, + type: "COMMENT", + }, + $, + }); + + $.export("$summary", `Successfully added internal comment to conversation thread ${this.threadId}`); + return response; + }, +}; \ No newline at end of file diff --git a/components/hubspot/actions/send-conversation-message/send-conversation-message.mjs b/components/hubspot/actions/send-conversation-message/send-conversation-message.mjs new file mode 100644 index 0000000000000..e5844bc648960 --- /dev/null +++ b/components/hubspot/actions/send-conversation-message/send-conversation-message.mjs @@ -0,0 +1,60 @@ +import hubspot from "../../hubspot.app.mjs"; + +export default { + key: "hubspot-send-conversation-message", + name: "Send Conversation Message", + description: "Send a message to a HubSpot conversation thread. [See the documentation](https://developers.hubspot.com/docs/api/conversations/threads)", + version: "0.0.1", + type: "action", + props: { + hubspot, + threadId: { + propDefinition: [ + hubspot, + "threadId", + ], + }, + text: { + type: "string", + label: "Message Text", + description: "The plain text content of the message", + }, + richText: { + type: "string", + label: "Rich Text", + description: "The rich text/HTML content of the message", + optional: true, + }, + direction: { + type: "string", + label: "Direction", + description: "The direction of the message", + options: [ + { + label: "Outgoing", + value: "OUTGOING", + }, + { + label: "Incoming", + value: "INCOMING", + }, + ], + default: "OUTGOING", + }, + }, + async run({ $ }) { + const response = await this.hubspot.sendConversationMessage({ + threadId: this.threadId, + data: { + text: this.text, + richText: this.richText || this.text, + direction: this.direction, + type: "MESSAGE", + }, + $, + }); + + $.export("$summary", `Successfully sent message to conversation thread ${this.threadId}`); + return response; + }, +}; \ No newline at end of file diff --git a/components/hubspot/hubspot.app.mjs b/components/hubspot/hubspot.app.mjs index 06299b1ef8b48..f540dd416ecbe 100644 --- a/components/hubspot/hubspot.app.mjs +++ b/components/hubspot/hubspot.app.mjs @@ -124,6 +124,11 @@ export default { : []; }, }, + threadId: { + type: "string", + label: "Thread ID", + description: "HubSpot conversation thread ID", + }, objectIds: { type: "string[]", label: "Object", @@ -1142,5 +1147,63 @@ export default { ...opts, }); }, + /** + * Get conversation thread details + * @param {string} threadId - The ID of the conversation thread + * @param {object} opts - Additional options to pass to the request + * @returns {Promise} The conversation thread object + */ + getConversationThread({ + threadId, ...opts + }) { + return this.makeRequest({ + endpoint: `/conversations/v3/conversations/threads/${threadId}`, + ...opts, + }); + }, + /** + * Get messages from a conversation thread + * @param {string} threadId - The ID of the conversation thread + * @param {object} opts - Additional options to pass to the request + * @returns {Promise} The messages in the conversation thread + */ + getConversationMessages({ + threadId, ...opts + }) { + return this.makeRequest({ + endpoint: `/conversations/v3/conversations/threads/${threadId}/messages`, + ...opts, + }); + }, + /** + * Send a message to a conversation thread + * @param {string} threadId - The ID of the conversation thread + * @param {object} opts - Message data and request options + * @returns {Promise} The sent message object + */ + sendConversationMessage({ + threadId, ...opts + }) { + return this.makeRequest({ + endpoint: `/conversations/v3/conversations/threads/${threadId}/messages`, + method: "POST", + ...opts, + }); + }, + /** + * Add an internal comment to a conversation thread + * @param {string} threadId - The ID of the conversation thread + * @param {object} opts - Comment data and request options + * @returns {Promise} The added comment object + */ + addConversationComment({ + threadId, ...opts + }) { + return this.makeRequest({ + endpoint: `/conversations/v3/conversations/threads/${threadId}/messages`, + method: "POST", + ...opts, + }); + }, }, }; diff --git a/components/hubspot/package.json b/components/hubspot/package.json index 97609a3e714b9..04364b264bb92 100644 --- a/components/hubspot/package.json +++ b/components/hubspot/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/hubspot", - "version": "1.2.4", + "version": "1.2.5", "description": "Pipedream Hubspot Components", "main": "hubspot.app.mjs", "keywords": [ diff --git a/components/hubspot/sources/new-conversation-comment/new-conversation-comment.mjs b/components/hubspot/sources/new-conversation-comment/new-conversation-comment.mjs new file mode 100644 index 0000000000000..a88376dd683ca --- /dev/null +++ b/components/hubspot/sources/new-conversation-comment/new-conversation-comment.mjs @@ -0,0 +1,63 @@ +import common from "../common/common.mjs"; + +export default { + ...common, + key: "hubspot-new-conversation-comment", + name: "New Conversation Comment (Internal Note)", + description: "Emit new event when a new internal comment is added to a HubSpot conversation thread. [See the documentation](https://developers.hubspot.com/docs/api/conversations/threads)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + threadId: { + propDefinition: [ + common.props.hubspot, + "threadId", + ], + description: "Filter comments from a specific conversation thread", + optional: true, + }, + }, + methods: { + ...common.methods, + getTs(comment) { + return Date.parse(comment.createdAt); + }, + generateMeta(comment) { + return { + id: comment.id, + summary: `New Internal Comment: ${comment.text || comment.id}`, + ts: this.getTs(comment), + }; + }, + isRelevant(comment, createdAfter) { + const isAfterTimestamp = this.getTs(comment) > createdAfter; + const matchesThread = !this.threadId || comment.threadId === this.threadId; + const isComment = comment.type === "COMMENT"; + + return isAfterTimestamp && matchesThread && isComment; + }, + async getParams() { + return { + params: { + limit: 100, + }, + }; + }, + async processResults(after, params) { + // Note: This is a placeholder implementation + // In a real implementation, you would need to: + // 1. List conversation threads + // 2. For each thread, get messages + // 3. Filter for COMMENT type messages + // 4. Process events + + // For now, we'll use a webhook-based approach + // This source would need to be enhanced with actual API calls + // to poll for new comments if HubSpot doesn't provide webhooks + + console.log("Conversation comment polling not yet implemented - use webhooks instead"); + }, + }, +}; \ No newline at end of file diff --git a/components/hubspot/sources/new-conversation-message/new-conversation-message.mjs b/components/hubspot/sources/new-conversation-message/new-conversation-message.mjs new file mode 100644 index 0000000000000..4d3cea697a12f --- /dev/null +++ b/components/hubspot/sources/new-conversation-message/new-conversation-message.mjs @@ -0,0 +1,85 @@ +import common from "../common/common.mjs"; + +export default { + ...common, + key: "hubspot-new-conversation-message", + name: "New Conversation Message", + description: "Emit new event when a new message is added to a HubSpot conversation thread. [See the documentation](https://developers.hubspot.com/docs/api/conversations/threads)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + threadId: { + propDefinition: [ + common.props.hubspot, + "threadId", + ], + description: "Filter messages from a specific conversation thread", + optional: true, + }, + messageType: { + type: "string", + label: "Message Type", + description: "Filter by message type", + options: [ + { + label: "All Messages", + value: "", + }, + { + label: "Regular Messages", + value: "MESSAGE", + }, + { + label: "Internal Comments", + value: "COMMENT", + }, + ], + default: "", + optional: true, + }, + }, + methods: { + ...common.methods, + getTs(message) { + return Date.parse(message.createdAt); + }, + generateMeta(message) { + const messageType = message.type === "COMMENT" ? "Internal Comment" : "Message"; + return { + id: message.id, + summary: `New ${messageType}: ${message.text || message.id}`, + ts: this.getTs(message), + }; + }, + isRelevant(message, createdAfter) { + const isAfterTimestamp = this.getTs(message) > createdAfter; + const matchesThread = !this.threadId || message.threadId === this.threadId; + const matchesType = !this.messageType || message.type === this.messageType; + + return isAfterTimestamp && matchesThread && matchesType; + }, + async getParams() { + return { + params: { + limit: 100, + }, + }; + }, + async processResults(after, params) { + // Note: This is a placeholder implementation + // In a real implementation, you would need to: + // 1. List conversation threads + // 2. For each thread, get messages + // 3. Filter based on timestamp and props + // 4. Process events + + // For now, we'll use a webhook-based approach + // This source would need to be enhanced with actual API calls + // to poll for new messages if HubSpot doesn't provide webhooks + + console.log("Conversation message polling not yet implemented - use webhooks instead"); + }, + }, +}; \ No newline at end of file