@pipedream/jira_service_desk 0.3.0 → 2.0.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.0.3",
7
+ version: "0.1.0",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -23,9 +23,6 @@ export default {
23
23
  propDefinition: [
24
24
  jiraServiceDesk,
25
25
  "requestId",
26
- ({ cloudId }) => ({
27
- cloudId,
28
- }),
29
26
  ],
30
27
  },
31
28
  body: {
@@ -1,11 +1,21 @@
1
+ // x-pd-ai: optimized
2
+ import { ConfigurationError } from "@pipedream/platform";
1
3
  import jiraServiceDesk from "../../jira_service_desk.app.mjs";
4
+ import constants from "../../common/constants.mjs";
2
5
 
3
6
  export default {
4
7
  key: "jira_service_desk-create-request",
5
8
  name: "Create Request",
6
9
  description:
7
- "Creates a new customer request. [See the documentation](https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/request-createCustomerRequest)",
8
- version: "0.1.2",
10
+ "Creates a customer request (ticket) in a Jira Service Management service desk."
11
+ + " This is the single tool for creating any kind of ticket (incident, service request, access request, hardware request, and so on)."
12
+ + " The kind of ticket is decided by `requestTypeId`, not by the wording of the summary, so always pick the request type deliberately."
13
+ + " Use **List Sites** to get `cloudId`, **List Service Desks** to get `serviceDeskId`, and **List Request Types** to choose the `requestTypeId` whose name and description match the user's intent."
14
+ + " Call **List Request Type Fields** to see which fields that request type requires; pass anything beyond summary and description in `additionalFieldValues`, keyed by Jira field ID."
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
+ + " Returns the created request including its `issueKey` and `issueId`."
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.0",
9
19
  annotations: {
10
20
  destructiveHint: false,
11
21
  openWorldHint: true,
@@ -24,95 +34,130 @@ export default {
24
34
  propDefinition: [
25
35
  jiraServiceDesk,
26
36
  "serviceDeskId",
27
- ({ cloudId }) => ({
28
- cloudId,
29
- }),
30
37
  ],
38
+ description: "The service desk to raise the request in. Use **List Service Desks** to find valid IDs (e.g. `1`).",
31
39
  },
32
40
  requestTypeId: {
33
41
  propDefinition: [
34
42
  jiraServiceDesk,
35
43
  "requestTypeId",
36
- ({
37
- cloudId, serviceDeskId,
38
- }) => ({
39
- cloudId,
40
- serviceDeskId,
41
- }),
42
44
  ],
43
- reloadProps: true,
45
+ description: "The request type that determines what kind of ticket this is. Use **List Request Types** to see the types this service desk offers and pick the one matching the user's intent (e.g. `8` for \"Report a system problem\").",
46
+ },
47
+ summary: {
48
+ type: "string",
49
+ label: "Summary",
50
+ description: "One-line title of the request, e.g. `Laptop won't boot after the latest update`. Required by virtually every request type.",
51
+ },
52
+ description: {
53
+ type: "string",
54
+ label: "Description",
55
+ description: "Body of the request, as plain text.",
56
+ optional: true,
57
+ },
58
+ additionalFieldValues: {
59
+ type: "object",
60
+ label: "Additional Field Values",
61
+ description:
62
+ "Any other fields the chosen request type requires or accepts, as a JSON object of Jira field ID to value, e.g. `{ \"duedate\": \"2026-09-01\", \"customfield_10052\": \"Laptop\" }`."
63
+ + " Run **List Request Type Fields** for the exact `fieldId`s, which are required, and their schemas."
64
+ + " Values that parse as JSON are converted (`[\"a\",\"b\"]` becomes a list, `5` becomes a number); to keep a numeric-looking value a string, wrap it in quotes (`\"\\\"123\\\"\"`)."
65
+ + " Keys `summary` and `description` given here override the props above.",
66
+ optional: true,
44
67
  },
45
68
  requestParticipants: {
46
69
  type: "string[]",
47
70
  label: "Request Participants",
48
- description:
49
- "Not available to users who only have the Service Desk Customer permission or if the feature is turned off for customers..",
71
+ description: "Atlassian account IDs to add as participants, e.g. `[\"5b10a2844c20165700ede21g\"]`. Not available to users who only have the Service Desk Customer permission, or if the feature is turned off for customers.",
50
72
  optional: true,
51
73
  },
52
74
  raiseOnBehalfOf: {
53
75
  type: "string",
54
76
  label: "Raise On Behalf Of",
55
- description:
56
- "Not available to users who only have the Service Desk Customer permission.",
77
+ description: "Atlassian account ID of the customer to raise this request for, e.g. `5b10a2844c20165700ede21g`. Not available to users who only have the Service Desk Customer permission.",
78
+ optional: true,
79
+ },
80
+ form: {
81
+ type: "object",
82
+ label: "Form",
83
+ description: "Answers to the form attached to the request type, as `{ \"answers\": { \"<questionId>\": { \"text\": \"...\" } } }`. Omit any Jira field from `additionalFieldValues` when it is linked to a form answer here. For answers in ADF, also set `isAdfRequest` to `true`.",
84
+ optional: true,
85
+ },
86
+ isAdfRequest: {
87
+ type: "boolean",
88
+ label: "Is ADF Request",
89
+ description: "Set to `true` to send rich-text fields (such as `description`) as Atlassian Document Format objects rather than plain text. Leave unset to send plain strings. When `true`, do not use the Description prop, which only sends plain text: pass the ADF object as a JSON string under the `description` key of `additionalFieldValues` instead. Marked experimental by Atlassian.",
90
+ optional: true,
91
+ },
92
+ channel: {
93
+ type: "string",
94
+ label: "Channel",
95
+ description: "Extra information about the channel the request came in on. Marked experimental by Atlassian.",
57
96
  optional: true,
58
97
  },
59
- },
60
- async additionalProps() {
61
- const {
62
- cloudId, serviceDeskId, requestTypeId,
63
- } = this;
64
- const types = await this.jiraServiceDesk.getRequestTypeFields({
65
- cloudId,
66
- serviceDeskId,
67
- requestTypeId,
68
- });
69
-
70
- return Object.fromEntries(
71
- types.map((field) => [
72
- field.fieldId,
73
- {
74
- type: "string",
75
- label: `Field: "${field.name}"`,
76
- description: `[See the documentation](https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#fieldformats) for info on specific fields. If the provided value is not a string, it will be parsed as JSON.${field.description
77
- ? `
78
- \\
79
- Field description: "${field.description}"`
80
- : ""}${field.jiraSchema
81
- ? `
82
- \\
83
- Field schema: \`${JSON.stringify(field.jiraSchema)}\``
84
- : ""}`,
85
- optional: !field.required,
86
- },
87
- ]),
88
- );
89
98
  },
90
99
  async run({ $ }) {
91
100
  const {
92
- // eslint-disable-next-line no-unused-vars
93
101
  jiraServiceDesk,
94
102
  cloudId,
95
103
  serviceDeskId,
96
104
  requestTypeId,
105
+ summary,
106
+ description,
107
+ additionalFieldValues,
97
108
  requestParticipants,
98
109
  raiseOnBehalfOf,
99
- ...requestFieldValues
110
+ form,
111
+ isAdfRequest,
112
+ channel,
100
113
  } = this;
101
114
 
102
- Object.entries(requestFieldValues).forEach(([
103
- key,
104
- value,
105
- ]) => {
115
+ let extraFields = additionalFieldValues;
116
+ if (typeof extraFields === "string") {
106
117
  try {
107
- const parsedValue = JSON.parse(value);
108
- requestFieldValues[key] = parsedValue;
118
+ extraFields = JSON.parse(extraFields);
119
+ } catch (error) {
120
+ throw new ConfigurationError(`Additional Field Values is not valid JSON: ${error.message}`);
109
121
  }
110
- catch (err) {
111
- // ignore non-serializable values
112
- }
113
- });
122
+ }
123
+ if (extraFields != null && (typeof extraFields !== "object" || Array.isArray(extraFields))) {
124
+ throw new ConfigurationError("Additional Field Values must be a JSON object of Jira field ID to value (not a list or a single value).");
125
+ }
114
126
 
115
- const response = await this.jiraServiceDesk.createCustomerRequest({
127
+ // Object props arrive from the UI with string values; parse the ones that carry
128
+ // non-string Jira field types (arrays, objects, numbers) and leave the rest as text.
129
+ const parsedExtraFields = Object.fromEntries(
130
+ Object.entries(extraFields ?? {}).map(([
131
+ fieldId,
132
+ value,
133
+ ]) => {
134
+ if (typeof value !== "string") {
135
+ return [
136
+ fieldId,
137
+ value,
138
+ ];
139
+ }
140
+ try {
141
+ return [
142
+ fieldId,
143
+ JSON.parse(value),
144
+ ];
145
+ } catch {
146
+ return [
147
+ fieldId,
148
+ value,
149
+ ];
150
+ }
151
+ }),
152
+ );
153
+
154
+ const requestFieldValues = {
155
+ [constants.REQUEST_FIELD.SUMMARY]: summary,
156
+ [constants.REQUEST_FIELD.DESCRIPTION]: description,
157
+ ...parsedExtraFields,
158
+ };
159
+
160
+ const response = await jiraServiceDesk.createCustomerRequest({
116
161
  $,
117
162
  cloudId,
118
163
  data: {
@@ -121,9 +166,13 @@ Field description: "${field.description}"`
121
166
  requestFieldValues,
122
167
  requestParticipants,
123
168
  raiseOnBehalfOf,
169
+ form,
170
+ isAdfRequest,
171
+ channel,
124
172
  },
125
173
  });
126
- $.export("$summary", "Successfully created request");
174
+
175
+ $.export("$summary", `Successfully created request ${response.issueKey}`);
127
176
  return response;
128
177
  },
129
178
  };
@@ -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.1",
11
+ version: "0.0.3",
12
12
  type: "action",
13
13
  annotations: {
14
14
  destructiveHint: false,
@@ -4,12 +4,13 @@ export default {
4
4
  key: "jira_service_desk-get-request",
5
5
  name: "Get Request",
6
6
  description:
7
- "Fetches the full details of a Jira Service Desk request including its field values and complete comment thread in a single response."
7
+ "Fetches the full details of a Jira Service Desk request including its field values and comment thread in a single response."
8
+ + " Comments are paginated automatically up to `maxResults`; the summary says so when the thread was truncated."
8
9
  + " Use this to summarize a ticket without needing follow-up calls."
9
10
  + " Use **List Sites** first to obtain the required `cloudId`."
10
11
  + " Use **List My Requests** to find the `issueKey` of a request (e.g. `IT-42`)."
11
12
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-get)",
12
- version: "0.0.1",
13
+ version: "0.2.0",
13
14
  type: "action",
14
15
  annotations: {
15
16
  destructiveHint: false,
@@ -30,11 +31,21 @@ export default {
30
31
  "issueIdOrKey",
31
32
  ],
32
33
  },
34
+ maxResults: {
35
+ propDefinition: [
36
+ app,
37
+ "maxResults",
38
+ ],
39
+ label: "Max Comments",
40
+ description: "Maximum number of comments to return across all pages (1-1000).",
41
+ },
33
42
  },
34
43
  async run({ $ }) {
35
44
  const [
36
45
  request,
37
- comments,
46
+ {
47
+ results: comments, hasMore,
48
+ },
38
49
  ] = await Promise.all([
39
50
  this.app.getRequest({
40
51
  $,
@@ -45,16 +56,20 @@ export default {
45
56
  $,
46
57
  cloudId: this.cloudId,
47
58
  issueIdOrKey: this.issueIdOrKey,
59
+ maxResults: this.maxResults,
48
60
  }),
49
61
  ]);
50
62
 
51
63
  const summary = request.requestFieldValues?.find?.(({ fieldId }) => fieldId === "summary")?.value
52
64
  || this.issueIdOrKey;
53
65
 
54
- $.export("$summary", `Request ${request.issueKey}: ${summary} (${comments?.length ?? 0} comment(s))`);
66
+ $.export("$summary", `Request ${request.issueKey}: ${summary} (${comments.length}${hasMore
67
+ ? "+"
68
+ : ""} comment(s))`);
55
69
  return {
56
70
  ...request,
57
71
  comments,
72
+ commentsTruncated: hasMore,
58
73
  };
59
74
  },
60
75
  };
@@ -4,12 +4,14 @@ export default {
4
4
  key: "jira_service_desk-get-request-status",
5
5
  name: "Get Request Status",
6
6
  description:
7
- "Returns the status history of a Jira Service Desk request the current status plus all previous states with timestamps."
7
+ "Returns the status history of a Jira Service Desk request: the current status plus previous states with timestamps."
8
+ + " The history is paginated automatically up to `maxResults`."
9
+ + " Returns `{ statuses, truncated }`, newest first, where `truncated` is `true` when more entries remained unfetched."
8
10
  + " Use this to understand how a request has progressed through the workflow."
9
11
  + " Use **List Sites** first to obtain the required `cloudId`."
10
12
  + " Use **List My Requests** to find the `issueKey` (e.g. `IT-42`)."
11
13
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-status-get)",
12
- version: "0.0.1",
14
+ version: "1.1.0",
13
15
  type: "action",
14
16
  annotations: {
15
17
  destructiveHint: false,
@@ -30,15 +32,31 @@ export default {
30
32
  "issueIdOrKey",
31
33
  ],
32
34
  },
35
+ maxResults: {
36
+ propDefinition: [
37
+ app,
38
+ "maxResults",
39
+ ],
40
+ label: "Max Entries",
41
+ description: "Maximum number of status history entries to return across all pages (1-1000).",
42
+ },
33
43
  },
34
44
  async run({ $ }) {
35
- const statuses = await this.app.getRequestStatus({
45
+ const {
46
+ results: statuses, hasMore,
47
+ } = await this.app.getRequestStatus({
36
48
  $,
37
49
  cloudId: this.cloudId,
38
50
  issueIdOrKey: this.issueIdOrKey,
51
+ maxResults: this.maxResults,
39
52
  });
40
53
  const current = statuses?.[0]?.status || "unknown";
41
- $.export("$summary", `Status history for ${this.issueIdOrKey}: current status is "${current}" (${statuses?.length ?? 0} entries)`);
42
- return statuses;
54
+ $.export("$summary", `Status history for ${this.issueIdOrKey}: current status is "${current}" (${statuses.length}${hasMore
55
+ ? "+"
56
+ : ""} entries)`);
57
+ return {
58
+ statuses,
59
+ truncated: hasMore,
60
+ };
43
61
  },
44
62
  };
@@ -3,8 +3,8 @@ import jira_service_desk from "../../jira_service_desk.app.mjs";
3
3
  export default {
4
4
  key: "jira_service_desk-list-cloud-id-options",
5
5
  name: "List Cloud ID Options",
6
- description: "Retrieves available options for the Cloud ID field.",
7
- version: "0.0.2",
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.0",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -15,8 +15,17 @@ export default {
15
15
  jira_service_desk,
16
16
  },
17
17
  async run({ $ }) {
18
- const options = await jira_service_desk.propDefinitions.cloudId.options
19
- .call(this.jira_service_desk);
18
+ const sites = await this.jira_service_desk.getSites({
19
+ $,
20
+ });
21
+ const options = sites
22
+ ?.filter?.(({ scopes }) => scopes?.includes("write:servicedesk-request"))
23
+ ?.map?.(({
24
+ id, name,
25
+ }) => ({
26
+ label: name,
27
+ value: id,
28
+ })) ?? [];
20
29
  $.export("$summary", `Successfully retrieved ${options.length} option${options.length === 1
21
30
  ? ""
22
31
  : "s"}`);
@@ -6,12 +6,14 @@ export default {
6
6
  description:
7
7
  "Lists Jira Service Desk requests owned or participated in by the current user."
8
8
  + " Defaults to open requests owned by the current user."
9
+ + " Results are paginated automatically up to `maxResults`."
10
+ + " Returns `{ requests, truncated }`, where `truncated` is `true` when more requests remained unfetched."
9
11
  + " Use **List Sites** first to obtain the required `cloudId`."
10
12
  + " Each result includes `issueKey`, `issueId`, and request field values (summary, status)."
11
13
  + " `requestStatus`: `OPEN_REQUESTS` (default), `CLOSED_REQUESTS`, or `ALL_REQUESTS`."
12
14
  + " `requestOwnership`: `OWNED_REQUESTS` (default) or `PARTICIPATED_REQUESTS`."
13
15
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-get)",
14
- version: "0.0.1",
16
+ version: "1.1.0",
15
17
  type: "action",
16
18
  annotations: {
17
19
  destructiveHint: false,
@@ -53,16 +55,31 @@ export default {
53
55
  ],
54
56
  optional: true,
55
57
  },
58
+ maxResults: {
59
+ propDefinition: [
60
+ app,
61
+ "maxResults",
62
+ ],
63
+ description: "Maximum number of requests to return across all pages (1-1000).",
64
+ },
56
65
  },
57
66
  async run({ $ }) {
58
- const requests = await this.app.listMyRequests({
67
+ const {
68
+ results: requests, hasMore,
69
+ } = await this.app.listMyRequests({
59
70
  $,
60
71
  cloudId: this.cloudId,
61
72
  serviceDeskId: this.serviceDeskId,
62
73
  requestStatus: this.requestStatus,
63
74
  requestOwnership: this.requestOwnership,
75
+ maxResults: this.maxResults,
64
76
  });
65
- $.export("$summary", `Found ${requests?.length ?? 0} request(s)`);
66
- return requests;
77
+ $.export("$summary", `Found ${requests.length} request(s)${hasMore
78
+ ? ", truncated at Max Results; raise it to fetch more"
79
+ : ""}`);
80
+ return {
81
+ requests,
82
+ truncated: hasMore,
83
+ };
67
84
  },
68
85
  };
@@ -4,12 +4,14 @@ export default {
4
4
  key: "jira_service_desk-list-request-transitions",
5
5
  name: "List Request Transitions",
6
6
  description:
7
- "Lists the available workflow transitions for a Jira Service Desk request returns each transition's `id` and `name`."
8
- + " **Call this before `Transition Request`** to obtain valid `transitionId` values."
7
+ "Lists the available workflow transitions for a Jira Service Desk request, returning each transition's `id` and `name`."
8
+ + " Transitions are paginated automatically up to `maxResults`."
9
+ + " Returns `{ transitions, truncated }`, where `truncated` is `true` when more transitions remained unfetched."
10
+ + " Call this before **Transition Request** to obtain valid `transitionId` values."
9
11
  + " Use **List Sites** first to obtain the required `cloudId`."
10
12
  + " Use **List My Requests** or **Get Request** to find the `issueKey` (e.g. `IT-42`)."
11
13
  + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-transition-get)",
12
- version: "0.0.1",
14
+ version: "1.1.0",
13
15
  type: "action",
14
16
  annotations: {
15
17
  destructiveHint: false,
@@ -30,14 +32,30 @@ export default {
30
32
  "issueIdOrKey",
31
33
  ],
32
34
  },
35
+ maxResults: {
36
+ propDefinition: [
37
+ app,
38
+ "maxResults",
39
+ ],
40
+ label: "Max Transitions",
41
+ description: "Maximum number of transitions to return across all pages (1-1000).",
42
+ },
33
43
  },
34
44
  async run({ $ }) {
35
- const transitions = await this.app.getRequestTransitions({
45
+ const {
46
+ results: transitions, hasMore,
47
+ } = await this.app.getRequestTransitions({
36
48
  $,
37
49
  cloudId: this.cloudId,
38
50
  issueIdOrKey: this.issueIdOrKey,
51
+ maxResults: this.maxResults,
39
52
  });
40
- $.export("$summary", `Found ${transitions?.length ?? 0} available transition(s) for ${this.issueIdOrKey}`);
41
- return transitions;
53
+ $.export("$summary", `Found ${transitions.length}${hasMore
54
+ ? "+"
55
+ : ""} available transition(s) for ${this.issueIdOrKey}`);
56
+ return {
57
+ transitions,
58
+ truncated: hasMore,
59
+ };
42
60
  },
43
61
  };
@@ -0,0 +1,87 @@
1
+ // x-pd-ai: optimized
2
+ import app from "../../jira_service_desk.app.mjs";
3
+
4
+ export default {
5
+ key: "jira_service_desk-list-request-type-fields",
6
+ name: "List Request Type Fields",
7
+ description:
8
+ "Lists the fields a given request type accepts, so you can build the `additionalFieldValues` argument for **Create Request** in a single pass."
9
+ + " Each entry gives the `fieldId` to use as the key, whether it is `required`, its `jiraSchema` (the value format), and any `validValues` for select-style fields."
10
+ + " Use **List Sites** for `cloudId`, **List Service Desks** for `serviceDeskId`, and **List Request Types** for `requestTypeId`."
11
+ + " Example: request type `4` (\"Onboard new employees\") on service desk `1` returns a required `summary` plus optional `duedate` (`jiraSchema.type` `date`, so pass `\"2026-09-01\"`), `description`, and `attachment`."
12
+ + " Also returns `canRaiseOnBehalfOf` and `canAddRequestParticipants`, which tell you whether the `raiseOnBehalfOf` and `requestParticipants` arguments of **Create Request** are usable with this account."
13
+ + " Hidden fields are only visible to service desk administrators."
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.1",
16
+ type: "action",
17
+ annotations: {
18
+ destructiveHint: false,
19
+ openWorldHint: true,
20
+ readOnlyHint: true,
21
+ },
22
+ props: {
23
+ app,
24
+ cloudId: {
25
+ propDefinition: [
26
+ app,
27
+ "cloudId",
28
+ ],
29
+ },
30
+ serviceDeskId: {
31
+ propDefinition: [
32
+ app,
33
+ "serviceDeskId",
34
+ ],
35
+ description: "The service desk the request type belongs to. Use **List Service Desks** to find valid IDs (e.g. `1`).",
36
+ },
37
+ requestTypeId: {
38
+ propDefinition: [
39
+ app,
40
+ "requestTypeId",
41
+ ],
42
+ description: "The request type whose fields to list. Use **List Request Types** to find valid IDs (e.g. `4`).",
43
+ },
44
+ expand: {
45
+ propDefinition: [
46
+ app,
47
+ "expand",
48
+ ],
49
+ description: "Extra data to include. Pass `[\"hiddenFields\"]` to also return fields hidden on the request type, which only a service desk administrator can see. `hiddenFields` is the only value this endpoint accepts.",
50
+ },
51
+ },
52
+ async run({ $ }) {
53
+ const {
54
+ requestTypeFields, canRaiseOnBehalfOf, canAddRequestParticipants,
55
+ } = await this.app.getRequestTypeCreateMeta({
56
+ $,
57
+ cloudId: this.cloudId,
58
+ serviceDeskId: this.serviceDeskId,
59
+ requestTypeId: this.requestTypeId,
60
+ params: {
61
+ expand: this.expand,
62
+ },
63
+ });
64
+
65
+ const fields = requestTypeFields?.map?.(({
66
+ fieldId, name, description, required, jiraSchema, validValues, defaultValues, visible,
67
+ }) => ({
68
+ fieldId,
69
+ name,
70
+ description,
71
+ required,
72
+ jiraSchema,
73
+ validValues,
74
+ defaultValues,
75
+ visible,
76
+ })) ?? [];
77
+
78
+ $.export("$summary", `Found ${fields.length} field${fields.length === 1
79
+ ? ""
80
+ : "s"}`);
81
+ return {
82
+ requestTypeFields: fields,
83
+ canRaiseOnBehalfOf,
84
+ canAddRequestParticipants,
85
+ };
86
+ },
87
+ };
@@ -0,0 +1,124 @@
1
+ // x-pd-ai: optimized
2
+ import app from "../../jira_service_desk.app.mjs";
3
+
4
+ export default {
5
+ key: "jira_service_desk-list-request-types",
6
+ name: "List Request Types",
7
+ description:
8
+ "Lists the customer request types a service desk offers, with the `id`, `name`, and `description` of each."
9
+ + " Call this before **Create Request** to choose the `requestTypeId` that matches what the user is asking for: the request type, not the summary wording, decides what kind of ticket gets created."
10
+ + " Names vary by desk, so match on meaning rather than assuming a type called \"Incident\" exists (an IT desk may instead offer \"Report a system problem\")."
11
+ + " Use **List Sites** for `cloudId` and **List Service Desks** for `serviceDeskId`."
12
+ + " Results are paginated automatically up to `maxResults`."
13
+ + " Returns `{ requestTypes, truncated }`, where `truncated` is `true` when more types remained unfetched."
14
+ + " Example: service desk `1` returns entries such as `{ \"id\": \"8\", \"name\": \"Report a system problem\", \"description\": \"Let us know if something isn't working properly\", \"canCreateRequest\": true }`."
15
+ + " Types with `canCreateRequest: false` cannot be used to raise a request."
16
+ + " Then call **List Request Type Fields** to see what the chosen type requires."
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.1",
19
+ type: "action",
20
+ annotations: {
21
+ destructiveHint: false,
22
+ openWorldHint: true,
23
+ readOnlyHint: true,
24
+ },
25
+ props: {
26
+ app,
27
+ cloudId: {
28
+ propDefinition: [
29
+ app,
30
+ "cloudId",
31
+ ],
32
+ },
33
+ serviceDeskId: {
34
+ propDefinition: [
35
+ app,
36
+ "serviceDeskId",
37
+ ],
38
+ description: "The service desk whose request types to list. Use **List Service Desks** to find valid IDs (e.g. `1`).",
39
+ },
40
+ searchQuery: {
41
+ type: "string",
42
+ label: "Search Query",
43
+ description: "Filter results to request types matching this text, e.g. `hardware`. Omit to return every type the desk offers.",
44
+ optional: true,
45
+ },
46
+ groupId: {
47
+ type: "integer",
48
+ label: "Group ID",
49
+ description: "Filter results to a single request type group. Group IDs appear as `groupIds` on the request types returned by this action.",
50
+ optional: true,
51
+ },
52
+ restrictionStatus: {
53
+ type: "string",
54
+ label: "Restriction Status",
55
+ description: "Filter by whether the request type is open to all customers or restricted.",
56
+ options: [
57
+ "open",
58
+ "restricted",
59
+ ],
60
+ optional: true,
61
+ },
62
+ includeHiddenRequestTypesInSearch: {
63
+ type: "boolean",
64
+ label: "Include Hidden Request Types In Search",
65
+ description: "Whether to include hidden request types. Only applies together with `searchQuery`.",
66
+ optional: true,
67
+ },
68
+ maxResults: {
69
+ propDefinition: [
70
+ app,
71
+ "maxResults",
72
+ ],
73
+ },
74
+ expand: {
75
+ propDefinition: [
76
+ app,
77
+ "expand",
78
+ ],
79
+ description: "Extra data to include on each request type. Pass `[\"field\"]` to return the type's fields inline, which saves a follow-up **List Request Type Fields** call. `field` is the only value this endpoint accepts.",
80
+ },
81
+ },
82
+ async run({ $ }) {
83
+ const {
84
+ results, hasMore,
85
+ } = await this.app.getRequestTypes({
86
+ $,
87
+ cloudId: this.cloudId,
88
+ serviceDeskId: this.serviceDeskId,
89
+ maxResults: this.maxResults,
90
+ params: {
91
+ searchQuery: this.searchQuery,
92
+ groupId: this.groupId,
93
+ restrictionStatus: this.restrictionStatus,
94
+ includeHiddenRequestTypesInSearch: this.includeHiddenRequestTypesInSearch,
95
+ expand: this.expand,
96
+ },
97
+ });
98
+
99
+ const requestTypes = results.map(({
100
+ id, name, description, helpText, issueTypeId, serviceDeskId, groupIds, canCreateRequest,
101
+ fields,
102
+ }) => ({
103
+ id,
104
+ name,
105
+ description,
106
+ helpText,
107
+ issueTypeId,
108
+ serviceDeskId,
109
+ groupIds,
110
+ canCreateRequest,
111
+ fields,
112
+ }));
113
+
114
+ $.export("$summary", `Found ${requestTypes.length} request type${requestTypes.length === 1
115
+ ? ""
116
+ : "s"}${hasMore
117
+ ? ", truncated at Max Results; raise it to fetch more"
118
+ : ""}`);
119
+ return {
120
+ requestTypes,
121
+ truncated: hasMore,
122
+ };
123
+ },
124
+ };
@@ -0,0 +1,66 @@
1
+ // x-pd-ai: optimized
2
+ import app from "../../jira_service_desk.app.mjs";
3
+
4
+ export default {
5
+ key: "jira_service_desk-list-service-desks",
6
+ name: "List Service Desks",
7
+ description:
8
+ "Lists every service desk on an Atlassian site, with the `id` and project details of each."
9
+ + " Call this to discover the `serviceDeskId` required by **Create Request**, **List Request Types**, and **List My Requests** when you only know a project name or key."
10
+ + " Use **List Sites** first to obtain the required `cloudId`."
11
+ + " Results are paginated automatically up to `maxResults`."
12
+ + " Returns `{ serviceDesks, truncated }`, where `truncated` is `true` when more desks remained unfetched."
13
+ + " Example: a site with one desk returns `{ \"serviceDesks\": [{ \"id\": \"1\", \"projectName\": \"Support\", \"projectKey\": \"SUP\" }], \"truncated\": false }`."
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.1",
16
+ type: "action",
17
+ annotations: {
18
+ destructiveHint: false,
19
+ openWorldHint: true,
20
+ readOnlyHint: true,
21
+ },
22
+ props: {
23
+ app,
24
+ cloudId: {
25
+ propDefinition: [
26
+ app,
27
+ "cloudId",
28
+ ],
29
+ },
30
+ maxResults: {
31
+ propDefinition: [
32
+ app,
33
+ "maxResults",
34
+ ],
35
+ },
36
+ },
37
+ async run({ $ }) {
38
+ const {
39
+ results, hasMore,
40
+ } = await this.app.getServiceDesks({
41
+ $,
42
+ cloudId: this.cloudId,
43
+ maxResults: this.maxResults,
44
+ });
45
+
46
+ const serviceDesks = results.map(({
47
+ id, projectId, projectName, projectKey, projectTypeKey,
48
+ }) => ({
49
+ id,
50
+ projectId,
51
+ projectName,
52
+ projectKey,
53
+ projectTypeKey,
54
+ }));
55
+
56
+ $.export("$summary", `Found ${serviceDesks.length} service desk${serviceDesks.length === 1
57
+ ? ""
58
+ : "s"}${hasMore
59
+ ? ", truncated at Max Results; raise it to fetch more"
60
+ : ""}`);
61
+ return {
62
+ serviceDesks,
63
+ truncated: hasMore,
64
+ };
65
+ },
66
+ };
@@ -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.1",
11
+ version: "0.0.3",
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.0.1",
13
+ version: "0.1.0",
14
14
  type: "action",
15
15
  annotations: {
16
16
  destructiveHint: false,
@@ -34,7 +34,7 @@ export default {
34
34
  transitionId: {
35
35
  type: "string",
36
36
  label: "Transition ID",
37
- description: "The ID of the transition to perform. Use **List Request Transitions** to retrieve available transition IDs and names.",
37
+ description: "The ID of the transition to perform. Use **List Request Transitions** to retrieve available transition IDs and names (in its `transitions` array).",
38
38
  },
39
39
  additionalComment: {
40
40
  type: "string",
@@ -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.0.1",
14
+ version: "0.1.0",
15
15
  type: "action",
16
16
  annotations: {
17
17
  destructiveHint: false,
@@ -0,0 +1,26 @@
1
+ // Jira Service Management caps the page size of each paginated `servicedeskapi`
2
+ // resource server-side, and the cap differs per resource (verified live:
3
+ // `/request` clamps to 100, `/request/{id}/comment` clamps to 50). Atlassian
4
+ // documents the cap as an implementation detail that may change, so `_paginate`
5
+ // requests this page size and then keys off the returned `isLastPage` and
6
+ // `size` rather than assuming the page it asked for is the page it got.
7
+ // https://developer.atlassian.com/cloud/jira/service-desk/rest/intro/#pagination
8
+ const PAGE_SIZE = 100;
9
+
10
+ // Ceiling on how many items a single action run will collect across pages.
11
+ const MAX_RESULTS_DEFAULT = 100;
12
+ const MAX_RESULTS_MIN = 1;
13
+ const MAX_RESULTS_MAX = 1000;
14
+
15
+ const REQUEST_FIELD = {
16
+ SUMMARY: "summary",
17
+ DESCRIPTION: "description",
18
+ };
19
+
20
+ export default {
21
+ PAGE_SIZE,
22
+ MAX_RESULTS_DEFAULT,
23
+ MAX_RESULTS_MIN,
24
+ MAX_RESULTS_MAX,
25
+ REQUEST_FIELD,
26
+ };
@@ -1,4 +1,6 @@
1
+ // x-pd-ai: optimized
1
2
  import { axios } from "@pipedream/platform";
3
+ import constants from "./common/constants.mjs";
2
4
 
3
5
  export default {
4
6
  type: "app",
@@ -7,75 +9,42 @@ export default {
7
9
  cloudId: {
8
10
  type: "string",
9
11
  label: "Cloud ID",
10
- description: "Select a site, or provide a custom ID.",
11
- async options() {
12
- const sites = await this.getSites();
13
- return sites?.filter?.(({ scopes }) => scopes?.includes("write:servicedesk-request")).map(({
14
- id, name,
15
- }) => ({
16
- label: name,
17
- value: id,
18
- }));
19
- },
12
+ description: "The Atlassian site (cloud) ID, e.g. `822faf0d-5427-420e-9016-999d3dc76918`. Run **List Sites** to get the `id` of every site you can access.",
20
13
  },
21
14
  serviceDeskId: {
22
15
  type: "string",
23
16
  label: "Service Desk ID",
24
- description: "Select a service desk, or provide a custom ID.",
25
- async options({ cloudId }) {
26
- const desks = await this.getServiceDesks({
27
- cloudId,
28
- });
29
- return desks?.map?.(({
30
- id, projectName,
31
- }) => ({
32
- label: projectName,
33
- value: id,
34
- }));
35
- },
17
+ description: "The numeric ID of the service desk, e.g. `1`. Run **List Service Desks** to find it from a project name or key.",
36
18
  },
37
19
  requestId: {
38
20
  type: "string",
39
21
  label: "Request ID",
40
- description: "Select a request, or provide a custom ID.",
41
- async options({ cloudId }) {
42
- const requests = await this.getCustomerRequests({
43
- cloudId,
44
- });
45
- return requests?.map?.(({
46
- issueId, issueKey, requestFieldValues,
47
- }) => {
48
- const summary = requestFieldValues?.find?.(({ fieldId }) => fieldId === "summary")?.value;
49
- return ({
50
- label: `(${issueKey}) ${summary}`,
51
- value: issueId,
52
- });
53
- });
54
- },
22
+ description: "The `issueId` of the customer request, e.g. `10288`. Run **List My Requests** to find it.",
55
23
  },
56
24
  requestTypeId: {
57
25
  type: "string",
58
26
  label: "Request Type ID",
59
- description: "Select a request type, or provide a custom ID.",
60
- async options({
61
- cloudId, serviceDeskId,
62
- }) {
63
- const types = await this.getRequestTypes({
64
- cloudId,
65
- serviceDeskId,
66
- });
67
- return types?.map?.(({
68
- id, name,
69
- }) => ({
70
- label: name,
71
- value: id,
72
- }));
73
- },
27
+ description: "The numeric ID of the request type, e.g. `4`. Run **List Request Types** to see what a service desk offers and pick the type matching the user's intent.",
74
28
  },
75
29
  issueIdOrKey: {
76
30
  type: "string",
77
31
  label: "Issue ID or Key",
78
- description: "The ID or key of the Jira Service Desk request (e.g. `IT-42` or `10001`). Use **List My Requests** to find the `issueKey` of a request.",
32
+ description: "The ID or key of the Jira Service Desk request (e.g. `IT-42` or `10001`). Use **List My Requests** to find the `issueKey` of a request (in its `requests` array).",
33
+ },
34
+ maxResults: {
35
+ type: "integer",
36
+ label: "Max Results",
37
+ description: `Maximum number of items to return across all pages (${constants.MAX_RESULTS_MIN}-${constants.MAX_RESULTS_MAX}).`,
38
+ optional: true,
39
+ default: constants.MAX_RESULTS_DEFAULT,
40
+ min: constants.MAX_RESULTS_MIN,
41
+ max: constants.MAX_RESULTS_MAX,
42
+ },
43
+ expand: {
44
+ type: "string[]",
45
+ label: "Expand",
46
+ description: "Additional data to include in the response, as a list of expansion names (e.g. `[\"field\"]`). Valid values differ per endpoint and are listed in the `_expands` property of that endpoint's response. Unrecognised names are ignored silently rather than rejected.",
47
+ optional: true,
79
48
  },
80
49
  },
81
50
  methods: {
@@ -94,40 +63,97 @@ export default {
94
63
  },
95
64
  });
96
65
  },
66
+ /**
67
+ * Walks a paginated `servicedeskapi` collection until the API reports
68
+ * `isLastPage`, or until `maxResults` items have been collected.
69
+ *
70
+ * @returns {Promise<{ results: object[], hasMore: boolean }>} the collected
71
+ * items and whether the API still had more to give when collection stopped.
72
+ */
73
+ async _paginate({
74
+ $, path, params, maxResults = constants.MAX_RESULTS_DEFAULT,
75
+ }) {
76
+ const results = [];
77
+ let start = 0;
78
+ let isLastPage = false;
79
+
80
+ while (results.length < maxResults) {
81
+ const response = await this._makeRequest({
82
+ $,
83
+ path,
84
+ params: {
85
+ ...params,
86
+ start,
87
+ limit: Math.min(maxResults - results.length, constants.PAGE_SIZE),
88
+ },
89
+ });
90
+
91
+ const values = response?.values;
92
+ if (!values?.length) {
93
+ isLastPage = true;
94
+ break;
95
+ }
96
+
97
+ results.push(...values);
98
+ isLastPage = Boolean(response.isLastPage);
99
+ if (isLastPage) {
100
+ break;
101
+ }
102
+
103
+ // The API may cap a page below the requested `limit`, so advance by what
104
+ // was actually returned instead of by the requested page size.
105
+ start += response.size || values.length;
106
+ }
107
+
108
+ return {
109
+ results: results.slice(0, maxResults),
110
+ hasMore: !isLastPage || results.length > maxResults,
111
+ };
112
+ },
97
113
  async getSites({ $ } = {}) {
98
114
  return this._makeRequest({
99
115
  $,
100
116
  path: "/oauth/token/accessible-resources",
101
117
  });
102
118
  },
103
- async getServiceDesks({ cloudId }) {
104
- const response = await this._makeRequest({
119
+ async getServiceDesks({
120
+ $, cloudId, maxResults,
121
+ }) {
122
+ const { results } = await this._paginate({
123
+ $,
105
124
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/servicedesk`,
125
+ maxResults,
106
126
  });
107
- return response.values;
127
+ return results;
108
128
  },
109
129
  async getRequestTypes({
110
- $, cloudId, serviceDeskId,
130
+ $, cloudId, serviceDeskId, params, maxResults,
111
131
  }) {
112
- const response = await this._makeRequest({
132
+ const { results } = await this._paginate({
113
133
  $,
114
134
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/servicedesk/${serviceDeskId}/requesttype`,
135
+ params,
136
+ maxResults,
115
137
  });
116
- return response.values;
138
+ return results;
117
139
  },
118
- async getRequestTypeFields({
119
- cloudId, serviceDeskId, requestTypeId,
140
+ async getRequestTypeCreateMeta({
141
+ $, cloudId, serviceDeskId, requestTypeId, params,
120
142
  }) {
121
- const response = await this._makeRequest({
143
+ return this._makeRequest({
144
+ $,
122
145
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/servicedesk/${serviceDeskId}/requesttype/${requestTypeId}/field`,
146
+ params,
123
147
  });
124
- return response.requestTypeFields;
125
148
  },
126
- async getCustomerRequests({ cloudId }) {
127
- const response = await this._makeRequest({
149
+ async getCustomerRequests({
150
+ $, cloudId,
151
+ }) {
152
+ const { results } = await this._paginate({
153
+ $,
128
154
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/request`,
129
155
  });
130
- return response.values;
156
+ return results;
131
157
  },
132
158
  async createCustomerRequest({
133
159
  cloudId, ...opts
@@ -154,19 +180,21 @@ export default {
154
180
  });
155
181
  },
156
182
  async listMyRequests({
157
- $, cloudId, serviceDeskId, requestStatus, requestOwnership,
183
+ $, cloudId, serviceDeskId, requestStatus, requestOwnership, maxResults,
158
184
  }) {
159
185
  const params = {
160
186
  requestStatus: requestStatus || "OPEN_REQUESTS",
161
187
  requestOwnership: requestOwnership || "OWNED_REQUESTS",
162
188
  };
163
- if (serviceDeskId) params.serviceDeskId = serviceDeskId;
164
- const response = await this._makeRequest({
189
+ if (serviceDeskId) {
190
+ params.serviceDeskId = serviceDeskId;
191
+ }
192
+ return this._paginate({
165
193
  $,
166
194
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/request`,
167
195
  params,
196
+ maxResults,
168
197
  });
169
- return response.values;
170
198
  },
171
199
  async getRequest({
172
200
  $, cloudId, issueIdOrKey,
@@ -177,31 +205,31 @@ export default {
177
205
  });
178
206
  },
179
207
  async getRequestComments({
180
- $, cloudId, issueIdOrKey,
208
+ $, cloudId, issueIdOrKey, maxResults,
181
209
  }) {
182
- const response = await this._makeRequest({
210
+ return this._paginate({
183
211
  $,
184
212
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/request/${issueIdOrKey}/comment`,
213
+ maxResults,
185
214
  });
186
- return response.values;
187
215
  },
188
216
  async getRequestStatus({
189
- $, cloudId, issueIdOrKey,
217
+ $, cloudId, issueIdOrKey, maxResults,
190
218
  }) {
191
- const response = await this._makeRequest({
219
+ return this._paginate({
192
220
  $,
193
221
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/request/${issueIdOrKey}/status`,
222
+ maxResults,
194
223
  });
195
- return response.values;
196
224
  },
197
225
  async getRequestTransitions({
198
- $, cloudId, issueIdOrKey,
226
+ $, cloudId, issueIdOrKey, maxResults,
199
227
  }) {
200
- const response = await this._makeRequest({
228
+ return this._paginate({
201
229
  $,
202
230
  path: `/ex/jira/${cloudId}/rest/servicedeskapi/request/${issueIdOrKey}/transition`,
231
+ maxResults,
203
232
  });
204
- return response.values;
205
233
  },
206
234
  async transitionRequest({
207
235
  cloudId, issueIdOrKey, ...opts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipedream/jira_service_desk",
3
- "version": "0.3.0",
3
+ "version": "2.0.0",
4
4
  "description": "Pipedream Jira Service Desk Components",
5
5
  "main": "jira_service_desk.app.mjs",
6
6
  "keywords": [
@@ -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.0.2",
9
+ version: "0.1.0",
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.0.2",
9
+ version: "0.1.0",
10
10
  type: "source",
11
11
  dedupe: "unique",
12
12
  methods: {
@@ -1,107 +0,0 @@
1
- import app from "../../jira_service_desk.app.mjs";
2
-
3
- export default {
4
- key: "jira_service_desk-create-incident",
5
- name: "Create Incident",
6
- description:
7
- "Creates a new incident request in a Jira Service Desk."
8
- + " Auto-discovers the best-matching request type (prefers types named 'incident' or 'problem') and, if `serviceDeskId` is omitted, auto-discovers it from the user's existing requests."
9
- + " Use **List Sites** first to obtain the required `cloudId`."
10
- + " `serviceDeskId` is optional — if not provided, the tool calls **List My Requests** to discover it automatically."
11
- + " Returns the new request including its `issueKey` and `issueId`."
12
- + " [See the documentation](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post)",
13
- version: "0.0.1",
14
- type: "action",
15
- annotations: {
16
- destructiveHint: false,
17
- openWorldHint: true,
18
- readOnlyHint: false,
19
- },
20
- props: {
21
- app,
22
- cloudId: {
23
- propDefinition: [
24
- app,
25
- "cloudId",
26
- ],
27
- },
28
- serviceDeskId: {
29
- type: "string",
30
- label: "Service Desk ID",
31
- description: "The numeric ID of the service desk (e.g. `2`). If omitted, the tool auto-discovers it from your existing requests. You can also find it in your Jira Service Management project settings.",
32
- optional: true,
33
- },
34
- summary: {
35
- type: "string",
36
- label: "Summary",
37
- description: "The title/summary of the incident.",
38
- },
39
- description: {
40
- type: "string",
41
- label: "Description",
42
- description: "A detailed description of the incident.",
43
- },
44
- },
45
- async run({ $ }) {
46
- // Auto-discover serviceDeskId if not provided — extract from existing requests
47
- let serviceDeskId = this.serviceDeskId;
48
- if (!serviceDeskId) {
49
- const existingRequests = await this.app.listMyRequests({
50
- $,
51
- cloudId: this.cloudId,
52
- });
53
- if (!existingRequests?.length) {
54
- throw new Error("No existing requests found to auto-discover serviceDeskId. Please provide serviceDeskId explicitly.");
55
- }
56
- const uniqueIds = [
57
- ...new Set(existingRequests.map((r) => r.serviceDeskId)),
58
- ];
59
- if (uniqueIds.length > 1) {
60
- throw new Error(
61
- `Multiple service desks found (${uniqueIds.join(", ")}). Please provide serviceDeskId explicitly.`,
62
- );
63
- }
64
- serviceDeskId = uniqueIds[0];
65
- }
66
-
67
- // Auto-discover the best-matching request type for an incident.
68
- // Prefers types named "incident" or "problem"; falls back to first available.
69
- const requestTypes = await this.app.getRequestTypes({
70
- $,
71
- cloudId: this.cloudId,
72
- serviceDeskId,
73
- });
74
-
75
- if (!requestTypes?.length) {
76
- throw new Error(`No request types found in service desk ${serviceDeskId}.`);
77
- }
78
-
79
- const INCIDENT_KEYWORDS = [
80
- "incident",
81
- "problem",
82
- "system problem",
83
- "issue",
84
- ];
85
-
86
- const incidentType = requestTypes.find(({ name }) =>
87
- INCIDENT_KEYWORDS.some((kw) => name?.toLowerCase().includes(kw))) || requestTypes[0];
88
-
89
- const requestFieldValues = {
90
- summary: this.summary,
91
- description: this.description,
92
- };
93
-
94
- const response = await this.app.createCustomerRequest({
95
- $,
96
- cloudId: this.cloudId,
97
- data: {
98
- serviceDeskId,
99
- requestTypeId: incidentType.id,
100
- requestFieldValues,
101
- },
102
- });
103
-
104
- $.export("$summary", `Created ${incidentType.name} ${response.issueKey}: ${this.summary}`);
105
- return response;
106
- },
107
- };