@pipedream/jira_service_desk 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ export default {
4
4
  key: "jira_service_desk-create-comment-on-request",
5
5
  name: "Create Comment on Request",
6
6
  description: "Create a comment on a customer request. [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post)",
7
- version: "0.1.1",
7
+ version: "0.1.2",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -15,7 +15,7 @@ export default {
15
15
  + " Worked example: on service desk `1`, request type `4` (\"Onboard new employees\") requires `summary` and also accepts a `duedate`, so call with Summary `Joseph Wilson starts on September 1`, Description `Needs a laptop and an email account`, and Additional Field Values `{ \"duedate\": \"2026-09-01\" }`."
16
16
  + " Returns the created request including its `issueKey` and `issueId`."
17
17
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post)",
18
- version: "1.0.1",
18
+ version: "1.0.2",
19
19
  annotations: {
20
20
  destructiveHint: false,
21
21
  openWorldHint: true,
@@ -0,0 +1,102 @@
1
+ // x-pd-ai: optimized
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import stream from "stream";
5
+ import { promisify } from "util";
6
+ import { ConfigurationError } from "@pipedream/platform";
7
+ import jiraServiceDesk from "../../jira_service_desk.app.mjs";
8
+ import constants from "../../common/constants.mjs";
9
+
10
+ const PIPELINE = promisify(stream.pipeline);
11
+
12
+ export default {
13
+ key: "jira_service_desk-download-issue-attachment",
14
+ name: "Download Issue Attachment",
15
+ description: "Download the binary content of a Jira Service Desk attachment to the file-stash directory, returning the saved path plus the attachment metadata (`filename`, `mimeType`, `size`). Run **List Issue Attachments** first to obtain the attachment `id`, then pass both it and the same `issueIdOrKey` here. Example: passing `issueIdOrKey` `IT-42` and `attachmentId` `10042` downloads `screenshot.png` to `/tmp/10042-screenshot.png` and returns `{ \"filedata\": [\"10042-screenshot.png\", \"/tmp/10042-screenshot.png\"], \"attachment\": { \"id\": \"10042\", \"filename\": \"screenshot.png\", \"mimeType\": \"image/png\", \"size\": 84213 } }`. [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-attachment-attachmentid-get)",
16
+ version: "0.0.1",
17
+ type: "action",
18
+ annotations: {
19
+ readOnlyHint: true,
20
+ destructiveHint: false,
21
+ openWorldHint: true,
22
+ },
23
+ props: {
24
+ jiraServiceDesk,
25
+ cloudId: {
26
+ propDefinition: [
27
+ jiraServiceDesk,
28
+ "cloudId",
29
+ ],
30
+ },
31
+ issueIdOrKey: {
32
+ propDefinition: [
33
+ jiraServiceDesk,
34
+ "issueIdOrKey",
35
+ ],
36
+ },
37
+ attachmentId: {
38
+ type: "string",
39
+ label: "Attachment ID",
40
+ description: "The numeric ID of the attachment to download, e.g. `10042`. Run **List Issue Attachments** first to obtain the ID from an attachment's `id` field.",
41
+ },
42
+ syncDir: {
43
+ type: "dir",
44
+ accessMode: "write",
45
+ sync: true,
46
+ },
47
+ },
48
+ async run({ $ }) {
49
+ if (!/^\d+$/.test(this.attachmentId)) {
50
+ throw new ConfigurationError(`Invalid attachment ID "${this.attachmentId}". Attachment IDs must be numeric.`);
51
+ }
52
+
53
+ const { attachments } = await this.jiraServiceDesk.getIssueAttachments({
54
+ $,
55
+ cloudId: this.cloudId,
56
+ issueIdOrKey: this.issueIdOrKey,
57
+ maxResults: constants.MAX_RESULTS_MAX,
58
+ });
59
+ const metadata = attachments.find(({ id }) => id === this.attachmentId);
60
+ if (!metadata) {
61
+ throw new ConfigurationError(`Attachment ID "${this.attachmentId}" was not found on issue "${this.issueIdOrKey}". Run List Issue Attachments to confirm the ID and issue match. If this connection has customer-level access, note that internal (non-public) attachments aren't visible to it even if they exist.`);
62
+ }
63
+ if (metadata.size > constants.MAX_ATTACHMENT_SIZE_BYTES) {
64
+ throw new ConfigurationError(`Attachment "${metadata.filename}" is ${metadata.size} bytes, which exceeds the ${constants.MAX_ATTACHMENT_SIZE_BYTES}-byte (2GB) /tmp disk limit for this execution, so it cannot be downloaded.`);
65
+ }
66
+
67
+ const safeFilename = path.basename(metadata.filename ?? "");
68
+ if (!safeFilename || safeFilename === "." || safeFilename === "..") {
69
+ throw new Error(`Invalid attachment filename "${metadata.filename}" returned by Jira.`);
70
+ }
71
+ const savedFilename = `${this.attachmentId}-${safeFilename}`;
72
+ const stashDir = process.env.STASH_DIR || "/tmp";
73
+ const downloadedFilepath = path.join(stashDir, savedFilename);
74
+
75
+ const contentStream = await this.jiraServiceDesk.getAttachmentContent({
76
+ $,
77
+ cloudId: this.cloudId,
78
+ issueIdOrKey: this.issueIdOrKey,
79
+ attachmentId: this.attachmentId,
80
+ });
81
+ try {
82
+ await PIPELINE(contentStream, fs.createWriteStream(downloadedFilepath));
83
+ } catch (error) {
84
+ await fs.promises.rm(downloadedFilepath, {
85
+ force: true,
86
+ }).catch(() => {});
87
+ throw error;
88
+ }
89
+
90
+ const filedata = [
91
+ savedFilename,
92
+ downloadedFilepath,
93
+ ];
94
+
95
+ $.export("$summary", `Downloaded ${metadata.filename} (${metadata.size} bytes)`);
96
+
97
+ return {
98
+ filedata,
99
+ attachment: metadata,
100
+ };
101
+ },
102
+ };
@@ -8,7 +8,7 @@ export default {
8
8
  + " Use this to identify who is logged in, or to filter requests by the current user's `account_id`."
9
9
  + " No `cloudId` required — this uses the Atlassian Identity API directly."
10
10
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/)",
11
- version: "0.0.4",
11
+ version: "0.0.5",
12
12
  type: "action",
13
13
  annotations: {
14
14
  destructiveHint: false,
@@ -10,7 +10,7 @@ export default {
10
10
  + " Use **List Sites** first to obtain the required `cloudId`."
11
11
  + " Use **List My Requests** to find the `issueKey` of a request (e.g. `IT-42`)."
12
12
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-get)",
13
- version: "0.2.1",
13
+ version: "0.2.2",
14
14
  type: "action",
15
15
  annotations: {
16
16
  destructiveHint: false,
@@ -11,7 +11,7 @@ export default {
11
11
  + " Use **List Sites** first to obtain the required `cloudId`."
12
12
  + " Use **List My Requests** to find the `issueKey` (e.g. `IT-42`)."
13
13
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-status-get)",
14
- version: "1.1.1",
14
+ version: "1.1.2",
15
15
  type: "action",
16
16
  annotations: {
17
17
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "jira_service_desk-list-cloud-id-options",
5
5
  name: "List Cloud ID Options",
6
6
  description: "Lists the Atlassian sites you can raise requests on, as `{label, value}` options, to discover the `cloudId` every other Jira Service Desk tool needs. Takes no input beyond the account. Example: returns `[{ \"label\": \"acme\", \"value\": \"822faf0d-5427-420e-9016-999d3dc76918\" }]`. Use **List Sites** instead if you want the full site records. [See the documentation](https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/#3-1-get-the-cloudid-for-your-site)",
7
- version: "0.1.1",
7
+ version: "0.1.2",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -0,0 +1,59 @@
1
+ // x-pd-ai: optimized
2
+ import { ConfigurationError } from "@pipedream/platform";
3
+ import jiraServiceDesk from "../../jira_service_desk.app.mjs";
4
+
5
+ export default {
6
+ key: "jira_service_desk-list-issue-attachments",
7
+ name: "List Issue Attachments",
8
+ description: "List metadata for every attachment on a Jira Service Desk request. `issueIdOrKey` accepts either a Jira issue key (e.g. `IT-42`) or a numeric Jira issue ID (e.g. `10001`). Results are paginated automatically up to `maxResults`. Returns `{ attachments, truncated }`, where each attachment includes `id`, `filename`, `size` (bytes), `mimeType`, and `content` (an opaque reference URL whose exact shape varies by account access level and is **not** directly fetchable through this connection's authentication), and `truncated` is `true` when more attachments remained unfetched. Use **Download Issue Attachment** with an attachment `id` and the same `issueIdOrKey` to fetch the binary content — do not call `content` directly. If this connection has customer-level access rather than agent access, only public attachments are returned; internal attachments exist but won't appear here. Returns `{ attachments: [], truncated: false }` (no error) when the request exists but has no visible attachments. Example: issue `IT-42` with one attachment returns `{ \"attachments\": [{ \"id\": \"10042\", \"filename\": \"screenshot.png\", \"size\": 84213, \"mimeType\": \"image/png\", \"content\": \"<opaque reference URL>\" }], \"truncated\": false }`. [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-attachment-get)",
9
+ version: "0.0.1",
10
+ type: "action",
11
+ annotations: {
12
+ readOnlyHint: true,
13
+ destructiveHint: false,
14
+ openWorldHint: true,
15
+ },
16
+ props: {
17
+ jiraServiceDesk,
18
+ cloudId: {
19
+ propDefinition: [
20
+ jiraServiceDesk,
21
+ "cloudId",
22
+ ],
23
+ },
24
+ issueIdOrKey: {
25
+ propDefinition: [
26
+ jiraServiceDesk,
27
+ "issueIdOrKey",
28
+ ],
29
+ },
30
+ maxResults: {
31
+ propDefinition: [
32
+ jiraServiceDesk,
33
+ "maxResults",
34
+ ],
35
+ },
36
+ },
37
+ async run({ $ }) {
38
+ if (!this.issueIdOrKey) {
39
+ throw new ConfigurationError("Issue ID or Key is required.");
40
+ }
41
+
42
+ const {
43
+ attachments, hasMore,
44
+ } = await this.jiraServiceDesk.getIssueAttachments({
45
+ $,
46
+ cloudId: this.cloudId,
47
+ issueIdOrKey: this.issueIdOrKey,
48
+ maxResults: this.maxResults,
49
+ });
50
+
51
+ $.export("$summary", `Found ${attachments.length}${hasMore
52
+ ? "+"
53
+ : ""} attachment(s) on issue ${this.issueIdOrKey}`);
54
+ return {
55
+ attachments,
56
+ truncated: hasMore,
57
+ };
58
+ },
59
+ };
@@ -13,7 +13,7 @@ export default {
13
13
  + " `requestStatus`: `OPEN_REQUESTS` (default), `CLOSED_REQUESTS`, or `ALL_REQUESTS`."
14
14
  + " `requestOwnership`: `OWNED_REQUESTS` (default) or `PARTICIPATED_REQUESTS`."
15
15
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-get)",
16
- version: "1.1.1",
16
+ version: "1.1.2",
17
17
  type: "action",
18
18
  annotations: {
19
19
  destructiveHint: false,
@@ -60,7 +60,7 @@ export default {
60
60
  app,
61
61
  "maxResults",
62
62
  ],
63
- description: "Maximum number of requests to return across all pages (1.1.10).",
63
+ description: "Maximum number of requests to return across all pages (1-1000).",
64
64
  },
65
65
  },
66
66
  async run({ $ }) {
@@ -11,7 +11,7 @@ export default {
11
11
  + " Use **List Sites** first to obtain the required `cloudId`."
12
12
  + " Use **List My Requests** or **Get Request** to find the `issueKey` (e.g. `IT-42`)."
13
13
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-transition-get)",
14
- version: "1.1.1",
14
+ version: "1.1.2",
15
15
  type: "action",
16
16
  annotations: {
17
17
  destructiveHint: false,
@@ -38,7 +38,7 @@ export default {
38
38
  "maxResults",
39
39
  ],
40
40
  label: "Max Transitions",
41
- description: "Maximum number of transitions to return across all pages (1.1.10).",
41
+ description: "Maximum number of transitions to return across all pages (1-1000).",
42
42
  },
43
43
  },
44
44
  async run({ $ }) {
@@ -12,7 +12,7 @@ export default {
12
12
  + " Also returns `canRaiseOnBehalfOf` and `canAddRequestParticipants`, which tell you whether the `raiseOnBehalfOf` and `requestParticipants` arguments of **Create Request** are usable with this account."
13
13
  + " Hidden fields are only visible to service desk administrators."
14
14
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-requesttype-requesttypeid-field-get)",
15
- version: "0.0.2",
15
+ version: "0.0.3",
16
16
  type: "action",
17
17
  annotations: {
18
18
  destructiveHint: false,
@@ -15,7 +15,7 @@ export default {
15
15
  + " Types with `canCreateRequest: false` cannot be used to raise a request."
16
16
  + " Then call **List Request Type Fields** to see what the chosen type requires."
17
17
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-requesttype-get)",
18
- version: "0.0.2",
18
+ version: "0.0.3",
19
19
  type: "action",
20
20
  annotations: {
21
21
  destructiveHint: false,
@@ -12,7 +12,7 @@ export default {
12
12
  + " Returns `{ serviceDesks, truncated }`, where `truncated` is `true` when more desks remained unfetched."
13
13
  + " Example: a site with one desk returns `{ \"serviceDesks\": [{ \"id\": \"1\", \"projectName\": \"Support\", \"projectKey\": \"SUP\" }], \"truncated\": false }`."
14
14
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-get)",
15
- version: "0.0.2",
15
+ version: "0.0.3",
16
16
  type: "action",
17
17
  annotations: {
18
18
  destructiveHint: false,
@@ -8,7 +8,7 @@ export default {
8
8
  + " **Call this tool first** to obtain the `cloudId` (returned as `id`) required by every other Jira Service Desk tool."
9
9
  + " Each site includes its `id` (cloudId), `name`, and `url`."
10
10
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/#3-1-get-the-cloudid-for-your-site)",
11
- version: "0.0.4",
11
+ version: "0.0.5",
12
12
  type: "action",
13
13
  annotations: {
14
14
  destructiveHint: false,
@@ -10,7 +10,7 @@ export default {
10
10
  + " Use **List My Requests** or **Get Request** to find the `issueKey` (e.g. `IT-42`)."
11
11
  + " Optionally include a comment to explain the transition."
12
12
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-transition-post)",
13
- version: "0.1.1",
13
+ version: "0.1.2",
14
14
  type: "action",
15
15
  annotations: {
16
16
  destructiveHint: false,
@@ -11,7 +11,7 @@ export default {
11
11
  + " `fields` is a JSON object of field name-value pairs."
12
12
  + " Example: `{\"summary\": \"Updated title\", \"priority\": {\"name\": \"High\"}}`."
13
13
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-put)",
14
- version: "0.1.1",
14
+ version: "0.1.2",
15
15
  type: "action",
16
16
  annotations: {
17
17
  destructiveHint: false,
@@ -17,10 +17,20 @@ const REQUEST_FIELD = {
17
17
  DESCRIPTION: "description",
18
18
  };
19
19
 
20
+ // The axios responseType used when streaming binary attachment content.
21
+ const STREAM_RESPONSE_TYPE = "stream";
22
+
23
+ // Pipedream's /tmp directory (the default file-stash download target) is capped at 2GB
24
+ // per execution, so any attachment reported larger than this can never be downloaded.
25
+ // https://pipedream.com/docs/workflows/limits#disk
26
+ const MAX_ATTACHMENT_SIZE_BYTES = 2 * 1024 * 1024 * 1024;
27
+
20
28
  export default {
21
29
  PAGE_SIZE,
22
30
  MAX_RESULTS_DEFAULT,
23
31
  MAX_RESULTS_MIN,
24
32
  MAX_RESULTS_MAX,
25
33
  REQUEST_FIELD,
34
+ STREAM_RESPONSE_TYPE,
35
+ MAX_ATTACHMENT_SIZE_BYTES,
26
36
  };
@@ -247,5 +247,38 @@ export default {
247
247
  path: `/ex/jira/${cloudId}/rest/api/3/issue/${issueIdOrKey}`,
248
248
  });
249
249
  },
250
+ async getIssueAttachments({
251
+ $, cloudId, issueIdOrKey, maxResults,
252
+ }) {
253
+ const {
254
+ results, hasMore,
255
+ } = await this._paginate({
256
+ $,
257
+ path: `/ex/jira/${cloudId}/rest/servicedeskapi/request/${issueIdOrKey}/attachment`,
258
+ maxResults,
259
+ });
260
+ const attachments = results.map(({
261
+ filename, size, mimeType, _links,
262
+ }) => ({
263
+ id: _links?.jiraRest?.split("/").pop(),
264
+ filename,
265
+ size,
266
+ mimeType,
267
+ content: _links?.content,
268
+ }));
269
+ return {
270
+ attachments,
271
+ hasMore,
272
+ };
273
+ },
274
+ async getAttachmentContent({
275
+ $, cloudId, issueIdOrKey, attachmentId,
276
+ }) {
277
+ return this._makeRequest({
278
+ $,
279
+ path: `/ex/jira/${cloudId}/rest/servicedeskapi/request/${issueIdOrKey}/attachment/${attachmentId}`,
280
+ responseType: constants.STREAM_RESPONSE_TYPE,
281
+ });
282
+ },
250
283
  },
251
284
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipedream/jira_service_desk",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Pipedream Jira Service Desk Components",
5
5
  "main": "jira_service_desk.app.mjs",
6
6
  "keywords": [
@@ -13,6 +13,6 @@
13
13
  "access": "public"
14
14
  },
15
15
  "dependencies": {
16
- "@pipedream/platform": "^1.6.8"
16
+ "@pipedream/platform": "^3.4.0"
17
17
  }
18
18
  }
@@ -6,7 +6,7 @@ export default {
6
6
  name: "New Request Created",
7
7
  description:
8
8
  "Emit new event when a customer request is created. [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/#api-rest-servicedeskapi-request-get)",
9
- version: "0.1.1",
9
+ version: "0.1.2",
10
10
  type: "source",
11
11
  dedupe: "unique",
12
12
  methods: {
@@ -6,7 +6,7 @@ export default {
6
6
  name: "Request Status Updated",
7
7
  description:
8
8
  "Emit new event when a customer request is updated. [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/#api-rest-servicedeskapi-request-get)",
9
- version: "0.1.1",
9
+ version: "0.1.2",
10
10
  type: "source",
11
11
  dedupe: "unique",
12
12
  methods: {