From ad7df8ab0167a537314708d76b8b9fe242da2ad8 Mon Sep 17 00:00:00 2001 From: seynadio <79858321+seynadio@users.noreply.github.com> Date: Tue, 19 Aug 2025 08:08:00 +0200 Subject: [PATCH 1/9] Add file attachment support to Zendesk update-ticket action - Add attachments prop definition to zendesk.app.mjs for multiple file uploads - Add uploadFile() and uploadFiles() helper methods with MIME type detection - Modify update-ticket action to support file attachments via Zendesk uploads API - Update version to 0.2.0 and package version to 0.7.2 - Enhanced success message to show attachment count --- .../actions/update-ticket/update-ticket.mjs | 36 +++++++++- components/zendesk/package.json | 2 +- components/zendesk/zendesk.app.mjs | 71 +++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/components/zendesk/actions/update-ticket/update-ticket.mjs b/components/zendesk/actions/update-ticket/update-ticket.mjs index 48a5cd8110504..cbeda11547137 100644 --- a/components/zendesk/actions/update-ticket/update-ticket.mjs +++ b/components/zendesk/actions/update-ticket/update-ticket.mjs @@ -3,9 +3,9 @@ import app from "../../zendesk.app.mjs"; export default { key: "zendesk-update-ticket", name: "Update Ticket", - description: "Updates a ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#update-ticket).", + description: "Updates a ticket with optional file attachments. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#update-ticket).", type: "action", - version: "0.1.4", + version: "0.2.0", props: { app, ticketId: { @@ -56,6 +56,12 @@ export default { "customSubdomain", ], }, + attachments: { + propDefinition: [ + app, + "attachments", + ], + }, }, methods: { updateTicket({ @@ -77,6 +83,7 @@ export default { ticketStatus, ticketCommentPublic, customSubdomain, + attachments, } = this; const ticketComment = ticketCommentBodyIsHTML @@ -89,6 +96,24 @@ export default { ticketComment.public = ticketCommentPublic; + // Upload attachments if provided + if (attachments && attachments.length > 0) { + try { + const uploadTokens = await this.app.uploadFiles({ + attachments, + customSubdomain, + step, + }); + + if (uploadTokens.length > 0) { + ticketComment.uploads = uploadTokens; + } + } catch (error) { + step.export("$summary", `Failed to upload attachments: ${error.message}`); + throw error; + } + } + const response = await this.updateTicket({ step, ticketId, @@ -103,7 +128,12 @@ export default { }, }); - step.export("$summary", `Successfully updated ticket with ID ${response.ticket.id}`); + const attachmentCount = attachments?.length || 0; + const summary = attachmentCount > 0 + ? `Successfully updated ticket with ID ${response.ticket.id} with ${attachmentCount} attachment(s)` + : `Successfully updated ticket with ID ${response.ticket.id}`; + + step.export("$summary", summary); return response; }, diff --git a/components/zendesk/package.json b/components/zendesk/package.json index cdc67d18c656b..f3bd8778a20e4 100644 --- a/components/zendesk/package.json +++ b/components/zendesk/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/zendesk", - "version": "0.7.1", + "version": "0.7.2", "description": "Pipedream Zendesk Components", "main": "zendesk.app.mjs", "keywords": [ diff --git a/components/zendesk/zendesk.app.mjs b/components/zendesk/zendesk.app.mjs index 6ae5e33c7cd90..b7a60a3163055 100644 --- a/components/zendesk/zendesk.app.mjs +++ b/components/zendesk/zendesk.app.mjs @@ -189,6 +189,12 @@ export default { description: "For Enterprise Zendesk accounts: optionally specify the subdomain to use. This will override the subdomain that was provided when connecting your Zendesk account to Pipedream. For example, if you Zendesk URL is https://examplehelp.zendesk.com, your subdomain is `examplehelp`", optional: true, }, + attachments: { + type: "string[]", + label: "Attachments", + description: "File paths or URLs to attach to the ticket. Multiple files can be attached.", + optional: true, + }, }, methods: { getUrl(path, customSubdomain) { @@ -284,6 +290,71 @@ export default { ...args, }); }, + async uploadFile({ + filePath, filename, customSubdomain, step, + } = {}) { + const fs = await import("fs"); + const path = await import("path"); + + // If filename not provided, extract from filePath + if (!filename && filePath) { + filename = path.basename(filePath); + } + + // Read file content + const fileContent = fs.readFileSync(filePath); + + // Get file extension to determine Content-Type + const ext = path.extname(filename).toLowerCase(); + const contentTypeMap = { + ".pdf": "application/pdf", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".txt": "text/plain", + ".doc": "application/msword", + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".xls": "application/vnd.ms-excel", + ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".zip": "application/zip", + }; + const contentType = contentTypeMap[ext] || "application/octet-stream"; + + return this.makeRequest({ + step, + method: "post", + path: `/uploads?filename=${encodeURIComponent(filename)}`, + customSubdomain, + headers: { + "Content-Type": contentType, + }, + data: fileContent, + }); + }, + async uploadFiles({ + attachments, customSubdomain, step, + } = {}) { + if (!attachments || !attachments.length) { + return []; + } + + const uploadResults = []; + for (const attachment of attachments) { + try { + const result = await this.uploadFile({ + filePath: attachment, + customSubdomain, + step, + }); + uploadResults.push(result.upload.token); + } catch (error) { + step.export("$summary", `Failed to upload file ${attachment}: ${error.message}`); + throw error; + } + } + return uploadResults; + }, async *paginate({ fn, args, resourceKey, max, }) { From 7971ba87f56ab834a765776f2539d0d42d4c1d4c Mon Sep 17 00:00:00 2001 From: seynadio <79858321+seynadio@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:46:21 +0200 Subject: [PATCH 2/9] Address CodeRabbit feedback - Add guard for missing upload token and fail fast with clear error message - Remove duplicate summary emission from uploadFiles method - Use actual upload token count instead of input array length for summary --- components/zendesk/actions/update-ticket/update-ticket.mjs | 2 +- components/zendesk/zendesk.app.mjs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/zendesk/actions/update-ticket/update-ticket.mjs b/components/zendesk/actions/update-ticket/update-ticket.mjs index cbeda11547137..7d099defa7c73 100644 --- a/components/zendesk/actions/update-ticket/update-ticket.mjs +++ b/components/zendesk/actions/update-ticket/update-ticket.mjs @@ -128,7 +128,7 @@ export default { }, }); - const attachmentCount = attachments?.length || 0; + const attachmentCount = ticketComment.uploads?.length || 0; const summary = attachmentCount > 0 ? `Successfully updated ticket with ID ${response.ticket.id} with ${attachmentCount} attachment(s)` : `Successfully updated ticket with ID ${response.ticket.id}`; diff --git a/components/zendesk/zendesk.app.mjs b/components/zendesk/zendesk.app.mjs index b7a60a3163055..169aea7f9b40c 100644 --- a/components/zendesk/zendesk.app.mjs +++ b/components/zendesk/zendesk.app.mjs @@ -347,9 +347,12 @@ export default { customSubdomain, step, }); - uploadResults.push(result.upload.token); + const token = result?.upload?.token; + if (!token) { + throw new Error(`Upload API returned no token for ${attachment}`); + } + uploadResults.push(token); } catch (error) { - step.export("$summary", `Failed to upload file ${attachment}: ${error.message}`); throw error; } } From fad6f2c171675bcb53898259ebcd356c3ba225f5 Mon Sep 17 00:00:00 2001 From: seynadio <79858321+seynadio@users.noreply.github.com> Date: Wed, 27 Aug 2025 08:57:56 +0200 Subject: [PATCH 3/9] Implement comprehensive URL support and improve file handling - Add full HTTP/HTTPS URL support with axios for remote file fetching - Extract filename from Content-Disposition header or URL path for URLs - Use response Content-Type when available for better MIME detection - Replace blocking readFileSync with async fs.promises.readFile - Add proper input validation and JSDoc documentation - Implement concurrent uploads with Promise.allSettled for better performance - Add comprehensive error aggregation showing all failed uploads - Filter and trim attachment input for robustness --- components/zendesk/zendesk.app.mjs | 110 +++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 28 deletions(-) diff --git a/components/zendesk/zendesk.app.mjs b/components/zendesk/zendesk.app.mjs index 169aea7f9b40c..3ed0765acd914 100644 --- a/components/zendesk/zendesk.app.mjs +++ b/components/zendesk/zendesk.app.mjs @@ -290,22 +290,23 @@ export default { ...args, }); }, + /** + * Upload a single file (local path or http(s) URL) to Zendesk Uploads API. + * @param {Object} params + * @param {string} params.filePath - Local filesystem path or http(s) URL. + * @param {string} [params.filename] - Optional filename override for the upload. + * @param {string} [params.customSubdomain] + * @param {*} [params.step] + */ async uploadFile({ filePath, filename, customSubdomain, step, } = {}) { + if (!filePath || typeof filePath !== "string") { + throw new Error("uploadFile: 'filePath' (string) is required"); + } const fs = await import("fs"); const path = await import("path"); - - // If filename not provided, extract from filePath - if (!filename && filePath) { - filename = path.basename(filePath); - } - - // Read file content - const fileContent = fs.readFileSync(filePath); - - // Get file extension to determine Content-Type - const ext = path.extname(filename).toLowerCase(); + const contentTypeMap = { ".pdf": "application/pdf", ".png": "image/png", @@ -319,8 +320,49 @@ export default { ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".zip": "application/zip", }; - const contentType = contentTypeMap[ext] || "application/octet-stream"; - + + let fileContent; + let contentType; + + const isHttp = /^https?:\/\//i.test(filePath); + if (isHttp) { + // Fetch remote file as arraybuffer to preserve bytes + const res = await axios(step, { + method: "get", + url: filePath, + responseType: "arraybuffer", + returnFullResponse: true, + timeout: 60_000, + }); + fileContent = res.data; + + const headerCT = res.headers?.["content-type"]; + const cd = res.headers?.["content-disposition"]; + + if (!filename) { + const cdMatch = cd?.match(/filename\*?=(?:UTF-8''|")?([^\";]+)/i); + filename = cdMatch?.[1] + ? decodeURIComponent(cdMatch[1].replace(/(^"|"$)/g, "")) + : (() => { + try { + return path.basename(new URL(filePath).pathname); + } catch { + return "attachment"; + } + })(); + } + const ext = path.extname(filename || "").toLowerCase(); + contentType = headerCT || contentTypeMap[ext] || "application/octet-stream"; + } else { + // Local file: non-blocking read + if (!filename) { + filename = path.basename(filePath); + } + fileContent = await fs.promises.readFile(filePath); + const ext = path.extname(filename || "").toLowerCase(); + contentType = contentTypeMap[ext] || "application/octet-stream"; + } + return this.makeRequest({ step, method: "post", @@ -338,25 +380,37 @@ export default { if (!attachments || !attachments.length) { return []; } - - const uploadResults = []; - for (const attachment of attachments) { - try { - const result = await this.uploadFile({ - filePath: attachment, - customSubdomain, - step, - }); - const token = result?.upload?.token; + const files = attachments + .map((a) => (typeof a === "string" ? a.trim() : a)) + .filter(Boolean); + + const settled = await Promise.allSettled( + files.map((attachment) => + this.uploadFile({ filePath: attachment, customSubdomain, step }), + ), + ); + + const tokens = []; + const errors = []; + settled.forEach((res, i) => { + const attachment = files[i]; + if (res.status === "fulfilled") { + const token = res.value?.upload?.token; if (!token) { - throw new Error(`Upload API returned no token for ${attachment}`); + errors.push(`Upload API returned no token for ${attachment}`); + } else { + tokens.push(token); } - uploadResults.push(token); - } catch (error) { - throw error; + } else { + const reason = res.reason?.message || String(res.reason || "Unknown error"); + errors.push(`${attachment}: ${reason}`); } + }); + + if (errors.length) { + throw new Error(`Failed to upload ${errors.length}/${files.length} attachment(s): ${errors.join("; ")}`); } - return uploadResults; + return tokens; }, async *paginate({ fn, args, resourceKey, max, From 40771b9a4a58f44ea10e79dba6405bc207511351 Mon Sep 17 00:00:00 2001 From: Job Nijenhuis Date: Wed, 27 Aug 2025 09:04:23 +0200 Subject: [PATCH 4/9] up versions --- components/zendesk/actions/add-ticket-tags/add-ticket-tags.mjs | 2 +- components/zendesk/actions/create-ticket/create-ticket.mjs | 2 +- components/zendesk/actions/delete-ticket/delete-ticket.mjs | 2 +- components/zendesk/actions/get-ticket-info/get-ticket-info.mjs | 2 +- components/zendesk/actions/get-user-info/get-user-info.mjs | 2 +- components/zendesk/actions/list-locales/list-locales.mjs | 2 +- components/zendesk/actions/list-macros/list-macros.mjs | 2 +- .../actions/list-ticket-comments/list-ticket-comments.mjs | 2 +- components/zendesk/actions/list-tickets/list-tickets.mjs | 2 +- .../zendesk/actions/remove-ticket-tags/remove-ticket-tags.mjs | 2 +- components/zendesk/actions/search-tickets/search-tickets.mjs | 2 +- components/zendesk/actions/set-ticket-tags/set-ticket-tags.mjs | 2 +- components/zendesk/sources/locale-updated/locale-updated.mjs | 2 +- .../new-ticket-comment-added/new-ticket-comment-added.mjs | 2 +- components/zendesk/sources/new-ticket/new-ticket.mjs | 2 +- .../sources/ticket-added-to-view/ticket-added-to-view.mjs | 2 +- components/zendesk/sources/ticket-closed/ticket-closed.mjs | 2 +- components/zendesk/sources/ticket-pended/ticket-pended.mjs | 2 +- components/zendesk/sources/ticket-solved/ticket-solved.mjs | 2 +- components/zendesk/sources/ticket-updated/ticket-updated.mjs | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/components/zendesk/actions/add-ticket-tags/add-ticket-tags.mjs b/components/zendesk/actions/add-ticket-tags/add-ticket-tags.mjs index d663c7e657d6f..c171ca1259755 100644 --- a/components/zendesk/actions/add-ticket-tags/add-ticket-tags.mjs +++ b/components/zendesk/actions/add-ticket-tags/add-ticket-tags.mjs @@ -5,7 +5,7 @@ export default { name: "Add Ticket Tags", description: "Add tags to a ticket (appends to existing tags). [See the documentation](https://developer.zendesk.com/api-reference/ticketing/ticket-management/tags/#add-tags).", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, ticketId: { diff --git a/components/zendesk/actions/create-ticket/create-ticket.mjs b/components/zendesk/actions/create-ticket/create-ticket.mjs index 65d06b6480984..a0e396a0be85a 100644 --- a/components/zendesk/actions/create-ticket/create-ticket.mjs +++ b/components/zendesk/actions/create-ticket/create-ticket.mjs @@ -5,7 +5,7 @@ export default { name: "Create Ticket", description: "Creates a ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#create-ticket).", type: "action", - version: "0.1.6", + version: "0.1.7", props: { app, ticketCommentBody: { diff --git a/components/zendesk/actions/delete-ticket/delete-ticket.mjs b/components/zendesk/actions/delete-ticket/delete-ticket.mjs index 6c6de189374df..bef6c225f95f0 100644 --- a/components/zendesk/actions/delete-ticket/delete-ticket.mjs +++ b/components/zendesk/actions/delete-ticket/delete-ticket.mjs @@ -5,7 +5,7 @@ export default { name: "Delete Ticket", description: "Deletes a ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#delete-ticket).", type: "action", - version: "0.1.6", + version: "0.1.7", props: { app, ticketId: { diff --git a/components/zendesk/actions/get-ticket-info/get-ticket-info.mjs b/components/zendesk/actions/get-ticket-info/get-ticket-info.mjs index fc9e0d2adbfcc..4d841013e3fe7 100644 --- a/components/zendesk/actions/get-ticket-info/get-ticket-info.mjs +++ b/components/zendesk/actions/get-ticket-info/get-ticket-info.mjs @@ -5,7 +5,7 @@ export default { name: "Get Ticket Info", description: "Retrieves information about a specific ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#show-ticket).", type: "action", - version: "0.0.4", + version: "0.0.5", props: { app, ticketId: { diff --git a/components/zendesk/actions/get-user-info/get-user-info.mjs b/components/zendesk/actions/get-user-info/get-user-info.mjs index 8f6e03b94a6f5..c0dd22f32a66e 100644 --- a/components/zendesk/actions/get-user-info/get-user-info.mjs +++ b/components/zendesk/actions/get-user-info/get-user-info.mjs @@ -4,7 +4,7 @@ export default { key: "zendesk-get-user-info", name: "Get User Info", description: "Retrieves information about a specific user. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/users/users/#show-user).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { zendesk, diff --git a/components/zendesk/actions/list-locales/list-locales.mjs b/components/zendesk/actions/list-locales/list-locales.mjs index 035f86e3d56b5..e6e0050650a43 100644 --- a/components/zendesk/actions/list-locales/list-locales.mjs +++ b/components/zendesk/actions/list-locales/list-locales.mjs @@ -4,7 +4,7 @@ export default { key: "zendesk-list-locales", name: "List Locales", description: "Retrieves all locales. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/account-configuration/locales/).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { zendesk, diff --git a/components/zendesk/actions/list-macros/list-macros.mjs b/components/zendesk/actions/list-macros/list-macros.mjs index d5b4ae4f412cb..2d2d3cb1cac79 100644 --- a/components/zendesk/actions/list-macros/list-macros.mjs +++ b/components/zendesk/actions/list-macros/list-macros.mjs @@ -4,7 +4,7 @@ export default { key: "zendesk-list-macros", name: "List Macros", description: "Retrieves all macros. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/business-rules/macros/#list-macros).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { zendesk, diff --git a/components/zendesk/actions/list-ticket-comments/list-ticket-comments.mjs b/components/zendesk/actions/list-ticket-comments/list-ticket-comments.mjs index 2554eef62d59c..be82b6c26e703 100644 --- a/components/zendesk/actions/list-ticket-comments/list-ticket-comments.mjs +++ b/components/zendesk/actions/list-ticket-comments/list-ticket-comments.mjs @@ -4,7 +4,7 @@ export default { key: "zendesk-list-ticket-comments", name: "List Ticket Comments", description: "Retrieves all comments for a specific ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_comments/#list-comments).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { zendesk, diff --git a/components/zendesk/actions/list-tickets/list-tickets.mjs b/components/zendesk/actions/list-tickets/list-tickets.mjs index 03355f0b3088e..26ef649461123 100644 --- a/components/zendesk/actions/list-tickets/list-tickets.mjs +++ b/components/zendesk/actions/list-tickets/list-tickets.mjs @@ -5,7 +5,7 @@ export default { name: "List Tickets", description: "Retrieves a list of tickets. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#list-tickets).", type: "action", - version: "0.0.4", + version: "0.0.5", props: { app, sortBy: { diff --git a/components/zendesk/actions/remove-ticket-tags/remove-ticket-tags.mjs b/components/zendesk/actions/remove-ticket-tags/remove-ticket-tags.mjs index b1ebf6e18b302..17b81b1626c4d 100644 --- a/components/zendesk/actions/remove-ticket-tags/remove-ticket-tags.mjs +++ b/components/zendesk/actions/remove-ticket-tags/remove-ticket-tags.mjs @@ -5,7 +5,7 @@ export default { name: "Remove Ticket Tags", description: "Remove specific tags from a ticket. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/ticket-management/tags/#remove-tags).", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, ticketId: { diff --git a/components/zendesk/actions/search-tickets/search-tickets.mjs b/components/zendesk/actions/search-tickets/search-tickets.mjs index c64d59beceaa3..a4bc5c4981490 100644 --- a/components/zendesk/actions/search-tickets/search-tickets.mjs +++ b/components/zendesk/actions/search-tickets/search-tickets.mjs @@ -5,7 +5,7 @@ export default { name: "Search Tickets", description: "Searches for tickets using Zendesk's search API. [See the documentation](https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/#search-tickets).", type: "action", - version: "0.0.5", + version: "0.0.6", props: { app, query: { diff --git a/components/zendesk/actions/set-ticket-tags/set-ticket-tags.mjs b/components/zendesk/actions/set-ticket-tags/set-ticket-tags.mjs index 5f6c50746f457..2a584a38128d8 100644 --- a/components/zendesk/actions/set-ticket-tags/set-ticket-tags.mjs +++ b/components/zendesk/actions/set-ticket-tags/set-ticket-tags.mjs @@ -5,7 +5,7 @@ export default { name: "Set Ticket Tags", description: "Set tags on a ticket (replaces all existing tags). [See the documentation](https://developer.zendesk.com/api-reference/ticketing/ticket-management/tags/#set-tags).", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, ticketId: { diff --git a/components/zendesk/sources/locale-updated/locale-updated.mjs b/components/zendesk/sources/locale-updated/locale-updated.mjs index 855aade43155f..edf4439a54a4f 100644 --- a/components/zendesk/sources/locale-updated/locale-updated.mjs +++ b/components/zendesk/sources/locale-updated/locale-updated.mjs @@ -6,7 +6,7 @@ export default { name: "Locale Updated", type: "source", description: "Emit new event when a locale has been updated", - version: "0.0.1", + version: "0.0.2", dedupe: "unique", async run() { const lastTs = this._getLastTs(); diff --git a/components/zendesk/sources/new-ticket-comment-added/new-ticket-comment-added.mjs b/components/zendesk/sources/new-ticket-comment-added/new-ticket-comment-added.mjs index eb9cfdafb6876..b760f5e740890 100644 --- a/components/zendesk/sources/new-ticket-comment-added/new-ticket-comment-added.mjs +++ b/components/zendesk/sources/new-ticket-comment-added/new-ticket-comment-added.mjs @@ -7,7 +7,7 @@ export default { key: "zendesk-new-ticket-comment-added", type: "source", description: "Emit new event when a ticket comment has been added", - version: "0.0.1", + version: "0.0.2", dedupe: "unique", props: { app, diff --git a/components/zendesk/sources/new-ticket/new-ticket.mjs b/components/zendesk/sources/new-ticket/new-ticket.mjs index 8e00bac4ca314..afbefcb407383 100644 --- a/components/zendesk/sources/new-ticket/new-ticket.mjs +++ b/components/zendesk/sources/new-ticket/new-ticket.mjs @@ -6,7 +6,7 @@ export default { key: "zendesk-new-ticket", type: "source", description: "Emit new event when a ticket is created", - version: "0.2.6", + version: "0.2.7", dedupe: "unique", methods: { ...common.methods, diff --git a/components/zendesk/sources/ticket-added-to-view/ticket-added-to-view.mjs b/components/zendesk/sources/ticket-added-to-view/ticket-added-to-view.mjs index ae785d533baec..0dc8ce8ace2c0 100644 --- a/components/zendesk/sources/ticket-added-to-view/ticket-added-to-view.mjs +++ b/components/zendesk/sources/ticket-added-to-view/ticket-added-to-view.mjs @@ -5,7 +5,7 @@ export default { key: "zendesk-ticket-added-to-view", name: "New Ticket Added to View (Instant)", description: "Emit new event when a ticket is added to the specified view", - version: "0.0.6", + version: "0.0.7", type: "source", dedupe: "unique", props: { diff --git a/components/zendesk/sources/ticket-closed/ticket-closed.mjs b/components/zendesk/sources/ticket-closed/ticket-closed.mjs index 9f50d86ebf726..f77aa1444b38e 100644 --- a/components/zendesk/sources/ticket-closed/ticket-closed.mjs +++ b/components/zendesk/sources/ticket-closed/ticket-closed.mjs @@ -6,7 +6,7 @@ export default { key: "zendesk-ticket-closed", type: "source", description: "Emit new event when a ticket has changed to closed status", - version: "0.2.6", + version: "0.2.7", dedupe: "unique", methods: { ...common.methods, diff --git a/components/zendesk/sources/ticket-pended/ticket-pended.mjs b/components/zendesk/sources/ticket-pended/ticket-pended.mjs index 8b0bd503bc9b1..48a1f0b901980 100644 --- a/components/zendesk/sources/ticket-pended/ticket-pended.mjs +++ b/components/zendesk/sources/ticket-pended/ticket-pended.mjs @@ -6,7 +6,7 @@ export default { key: "zendesk-ticket-pended", type: "source", description: "Emit new event when a ticket has changed to pending status", - version: "0.2.6", + version: "0.2.7", dedupe: "unique", methods: { ...common.methods, diff --git a/components/zendesk/sources/ticket-solved/ticket-solved.mjs b/components/zendesk/sources/ticket-solved/ticket-solved.mjs index 6d87a69d986ec..206007e0390b6 100644 --- a/components/zendesk/sources/ticket-solved/ticket-solved.mjs +++ b/components/zendesk/sources/ticket-solved/ticket-solved.mjs @@ -6,7 +6,7 @@ export default { key: "zendesk-ticket-solved", type: "source", description: "Emit new event when a ticket has changed to solved status", - version: "0.2.6", + version: "0.2.7", dedupe: "unique", methods: { ...common.methods, diff --git a/components/zendesk/sources/ticket-updated/ticket-updated.mjs b/components/zendesk/sources/ticket-updated/ticket-updated.mjs index 253d5e92d6233..24a0c3edc7780 100644 --- a/components/zendesk/sources/ticket-updated/ticket-updated.mjs +++ b/components/zendesk/sources/ticket-updated/ticket-updated.mjs @@ -6,7 +6,7 @@ export default { key: "zendesk-ticket-updated", type: "source", description: "Emit new event when a ticket has been updated", - version: "0.2.6", + version: "0.2.7", dedupe: "unique", methods: { ...common.methods, From a2f00ce824acbd716eb9f53e0be5305a8c5d712f Mon Sep 17 00:00:00 2001 From: Job Nijenhuis Date: Wed, 27 Aug 2025 09:05:24 +0200 Subject: [PATCH 5/9] lock change for some reason --- pnpm-lock.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7845895295a9..647baeec08282 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3453,8 +3453,7 @@ importers: components/danny_test_app: {} - components/dante_ai: - specifiers: {} + components/dante_ai: {} components/dappier: {} From b8b602931f610a40e22e2db8bc3583fc6614095c Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 29 Aug 2025 15:12:09 -0400 Subject: [PATCH 6/9] updates --- .../actions/update-ticket/update-ticket.mjs | 6 +- components/zendesk/package.json | 3 +- components/zendesk/zendesk.app.mjs | 93 +++++++------------ 3 files changed, 37 insertions(+), 65 deletions(-) diff --git a/components/zendesk/actions/update-ticket/update-ticket.mjs b/components/zendesk/actions/update-ticket/update-ticket.mjs index 35a256cc1103c..75708ec495e3f 100644 --- a/components/zendesk/actions/update-ticket/update-ticket.mjs +++ b/components/zendesk/actions/update-ticket/update-ticket.mjs @@ -133,7 +133,7 @@ export default { customSubdomain, step, }); - + if (uploadTokens.length > 0) { ticketComment.uploads = uploadTokens; } @@ -158,10 +158,10 @@ export default { }); const attachmentCount = ticketComment.uploads?.length || 0; - const summary = attachmentCount > 0 + const summary = attachmentCount > 0 ? `Successfully updated ticket with ID ${response.ticket.id} with ${attachmentCount} attachment(s)` : `Successfully updated ticket with ID ${response.ticket.id}`; - + step.export("$summary", summary); // Handle tag operations if tags are provided diff --git a/components/zendesk/package.json b/components/zendesk/package.json index 094def4ce27be..a6c9774064cc8 100644 --- a/components/zendesk/package.json +++ b/components/zendesk/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@pipedream/platform": "^3.0.3", - "crypto": "^1.0.1" + "crypto": "^1.0.1", + "path": "^0.12.7" } } diff --git a/components/zendesk/zendesk.app.mjs b/components/zendesk/zendesk.app.mjs index 2117cf03c3d3e..40c1ab4d8bc6f 100644 --- a/components/zendesk/zendesk.app.mjs +++ b/components/zendesk/zendesk.app.mjs @@ -1,5 +1,7 @@ import { axios } from "@pipedream/platform"; import constants from "./common/constants.mjs"; +import { getFileStreamAndMetadata } from "@pipedream/platform"; +import path from "path"; export default { type: "app", @@ -264,6 +266,8 @@ export default { type: "string[]", label: "Attachments", description: "File paths or URLs to attach to the ticket. Multiple files can be attached.", + optional: true, + }, ticketTags: { type: "string[]", label: "Tags", @@ -373,6 +377,14 @@ export default { ...args, }); }, + streamToBuffer(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on("data", (chunk) => chunks.push(chunk)); + stream.on("end", () => resolve(Buffer.concat(chunks))); + stream.on("error", reject); + }); + }, /** * Upload a single file (local path or http(s) URL) to Zendesk Uploads API. * @param {Object} params @@ -383,67 +395,18 @@ export default { */ async uploadFile({ filePath, filename, customSubdomain, step, - } = {}) { + }) { if (!filePath || typeof filePath !== "string") { throw new Error("uploadFile: 'filePath' (string) is required"); } - const fs = await import("fs"); - const path = await import("path"); - - const contentTypeMap = { - ".pdf": "application/pdf", - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".txt": "text/plain", - ".doc": "application/msword", - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ".xls": "application/vnd.ms-excel", - ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ".zip": "application/zip", - }; - let fileContent; - let contentType; - - const isHttp = /^https?:\/\//i.test(filePath); - if (isHttp) { - // Fetch remote file as arraybuffer to preserve bytes - const res = await axios(step, { - method: "get", - url: filePath, - responseType: "arraybuffer", - returnFullResponse: true, - timeout: 60_000, - }); - fileContent = res.data; - - const headerCT = res.headers?.["content-type"]; - const cd = res.headers?.["content-disposition"]; + const { + stream, metadata, + } = await getFileStreamAndMetadata(filePath); + const fileBinary = await this.streamToBuffer(stream); - if (!filename) { - const cdMatch = cd?.match(/filename\*?=(?:UTF-8''|")?([^\";]+)/i); - filename = cdMatch?.[1] - ? decodeURIComponent(cdMatch[1].replace(/(^"|"$)/g, "")) - : (() => { - try { - return path.basename(new URL(filePath).pathname); - } catch { - return "attachment"; - } - })(); - } - const ext = path.extname(filename || "").toLowerCase(); - contentType = headerCT || contentTypeMap[ext] || "application/octet-stream"; - } else { - // Local file: non-blocking read - if (!filename) { - filename = path.basename(filePath); - } - fileContent = await fs.promises.readFile(filePath); - const ext = path.extname(filename || "").toLowerCase(); - contentType = contentTypeMap[ext] || "application/octet-stream"; + if (!filename) { + filename = path.basename(filePath); } return this.makeRequest({ @@ -452,9 +415,11 @@ export default { path: `/uploads?filename=${encodeURIComponent(filename)}`, customSubdomain, headers: { - "Content-Type": contentType, + "Content-Type": metadata.contentType, + "Content-Length": metadata.size, + "Accept": "application/json", }, - data: fileContent, + data: Buffer.from(fileBinary, "binary"), }); }, async uploadFiles({ @@ -464,13 +429,18 @@ export default { return []; } const files = attachments - .map((a) => (typeof a === "string" ? a.trim() : a)) + .map((a) => (typeof a === "string" + ? a.trim() + : a)) .filter(Boolean); const settled = await Promise.allSettled( files.map((attachment) => - this.uploadFile({ filePath: attachment, customSubdomain, step }), - ), + this.uploadFile({ + filePath: attachment, + customSubdomain, + step, + })), ); const tokens = []; @@ -494,6 +464,7 @@ export default { throw new Error(`Failed to upload ${errors.length}/${files.length} attachment(s): ${errors.join("; ")}`); } return tokens; + }, listTicketComments({ ticketId, ...args } = {}) { From 235f0c05de25e4bf1e6c71f7a10d82b49bcb445f Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 29 Aug 2025 15:12:53 -0400 Subject: [PATCH 7/9] pnpm-lock.yaml --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 647baeec08282..edfe062bbbab6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16100,6 +16100,9 @@ importers: crypto: specifier: ^1.0.1 version: 1.0.1 + path: + specifier: ^0.12.7 + version: 0.12.7 components/zendesk_sell: dependencies: From 597a8b7f536541999edbc2f5f29924ccb6865a21 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 29 Aug 2025 15:22:53 -0400 Subject: [PATCH 8/9] update @pipedream/platform dependency --- components/zendesk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zendesk/package.json b/components/zendesk/package.json index a6c9774064cc8..4c1206e7c0f68 100644 --- a/components/zendesk/package.json +++ b/components/zendesk/package.json @@ -14,7 +14,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.0.3", + "@pipedream/platform": "^3.1.0", "crypto": "^1.0.1", "path": "^0.12.7" } From f549ea4ec04be8a5f6ee635fc062a23d6fd67511 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 29 Aug 2025 15:23:21 -0400 Subject: [PATCH 9/9] pnpm-lock.yaml --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index edfe062bbbab6..3e716d5e9c8fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16095,8 +16095,8 @@ importers: components/zendesk: dependencies: '@pipedream/platform': - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 crypto: specifier: ^1.0.1 version: 1.0.1