Skip to content

Commit db23078

Browse files
authored
fix(jsm): improve create request error handling, add form-based submission support (#4066)
* fix(jsm): improve create request error handling, add form-based submission support * refactor(jsm): extract parseJsmErrorMessage helper to deduplicate error handling * fix(jsm): remove required on summary for advanced mode, add JSON.parse error handling * fix(jsm): include description in requestFieldValues gate for form-only requests
1 parent 9fbe514 commit db23078

File tree

6 files changed

+100
-16
lines changed

6 files changed

+100
-16
lines changed

apps/docs/content/docs/en/tools/jira_service_management.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,11 @@ Create a new service request in Jira Service Management
113113
| `cloudId` | string | No | Jira Cloud ID for the instance |
114114
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
115115
| `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) |
116-
| `summary` | string | Yes | Summary/title for the service request |
116+
| `summary` | string | No | Summary/title for the service request \(required unless using Form Answers\) |
117117
| `description` | string | No | Description for the service request |
118118
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
119119
| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) |
120+
| `formAnswers` | json | No | Form answers for form-based request types \(e.g., \{"summary": \{"text": "Title"\}, "customfield_10010": \{"choices": \["10320"\]\}\}\) |
120121
| `requestParticipants` | string | No | Comma-separated account IDs to add as request participants |
121122
| `channel` | string | No | Channel the request originates from \(e.g., portal, email\) |
122123

apps/sim/app/api/tools/jsm/request/route.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ export const dynamic = 'force-dynamic'
1212

1313
const logger = createLogger('JsmRequestAPI')
1414

15+
function parseJsmErrorMessage(status: number, statusText: string, errorText: string): string {
16+
try {
17+
const errorData = JSON.parse(errorText)
18+
if (errorData.errorMessage) {
19+
return `JSM API error: ${errorData.errorMessage}`
20+
}
21+
} catch {
22+
if (errorText) {
23+
return `JSM API error: ${errorText}`
24+
}
25+
}
26+
return `JSM API error: ${status} ${statusText}`
27+
}
28+
1529
export async function POST(request: NextRequest) {
1630
const auth = await checkInternalAuth(request)
1731
if (!auth.success || !auth.userId) {
@@ -31,6 +45,7 @@ export async function POST(request: NextRequest) {
3145
description,
3246
raiseOnBehalfOf,
3347
requestFieldValues,
48+
formAnswers,
3449
requestParticipants,
3550
channel,
3651
expand,
@@ -55,7 +70,7 @@ export async function POST(request: NextRequest) {
5570

5671
const baseUrl = getJsmApiBaseUrl(cloudId)
5772

58-
const isCreateOperation = serviceDeskId && requestTypeId && summary
73+
const isCreateOperation = serviceDeskId && requestTypeId && (summary || formAnswers)
5974

6075
if (isCreateOperation) {
6176
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
@@ -69,15 +84,30 @@ export async function POST(request: NextRequest) {
6984
}
7085
const url = `${baseUrl}/request`
7186

72-
logger.info('Creating request at:', url)
87+
logger.info('Creating request at:', { url, serviceDeskId, requestTypeId })
7388

7489
const requestBody: Record<string, unknown> = {
7590
serviceDeskId,
7691
requestTypeId,
77-
requestFieldValues: requestFieldValues || {
78-
summary,
79-
...(description && { description }),
80-
},
92+
}
93+
94+
if (summary || description || requestFieldValues) {
95+
const fieldValues =
96+
requestFieldValues && typeof requestFieldValues === 'object'
97+
? {
98+
...(!requestFieldValues.summary && summary ? { summary } : {}),
99+
...(!requestFieldValues.description && description ? { description } : {}),
100+
...requestFieldValues,
101+
}
102+
: {
103+
...(summary && { summary }),
104+
...(description && { description }),
105+
}
106+
requestBody.requestFieldValues = fieldValues
107+
}
108+
109+
if (formAnswers && typeof formAnswers === 'object') {
110+
requestBody.form = { answers: formAnswers }
81111
}
82112

83113
if (raiseOnBehalfOf) {
@@ -112,7 +142,10 @@ export async function POST(request: NextRequest) {
112142
})
113143

114144
return NextResponse.json(
115-
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
145+
{
146+
error: parseJsmErrorMessage(response.status, response.statusText, errorText),
147+
details: errorText,
148+
},
116149
{ status: response.status }
117150
)
118151
}
@@ -178,7 +211,10 @@ export async function POST(request: NextRequest) {
178211
})
179212

180213
return NextResponse.json(
181-
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
214+
{
215+
error: parseJsmErrorMessage(response.status, response.statusText, errorText),
216+
details: errorText,
217+
},
182218
{ status: response.status }
183219
)
184220
}

apps/sim/blocks/blocks/jira_service_management.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
198198
id: 'summary',
199199
title: 'Summary',
200200
type: 'short-input',
201-
required: true,
202201
placeholder: 'Enter request summary',
203202
condition: { field: 'operation', value: 'create_request' },
204203
wandConfig: {
@@ -238,20 +237,23 @@ Return ONLY the description text - no explanations.`,
238237
title: 'Raise on Behalf Of',
239238
type: 'short-input',
240239
placeholder: 'Account ID to raise request on behalf of',
240+
mode: 'advanced',
241241
condition: { field: 'operation', value: 'create_request' },
242242
},
243243
{
244244
id: 'requestParticipants',
245245
title: 'Request Participants',
246246
type: 'short-input',
247247
placeholder: 'Comma-separated account IDs to add as participants',
248+
mode: 'advanced',
248249
condition: { field: 'operation', value: 'create_request' },
249250
},
250251
{
251252
id: 'channel',
252253
title: 'Channel',
253254
type: 'short-input',
254255
placeholder: 'Channel (e.g., portal, email)',
256+
mode: 'advanced',
255257
condition: { field: 'operation', value: 'create_request' },
256258
},
257259
{
@@ -260,6 +262,16 @@ Return ONLY the description text - no explanations.`,
260262
type: 'long-input',
261263
placeholder:
262264
'JSON object of field values (e.g., {"summary": "Title", "customfield_10010": "value"})',
265+
mode: 'advanced',
266+
condition: { field: 'operation', value: 'create_request' },
267+
},
268+
{
269+
id: 'formAnswers',
270+
title: 'Form Answers',
271+
type: 'long-input',
272+
placeholder:
273+
'JSON object for form-based request types (e.g., {"summary": {"text": "Title"}, "customfield_10010": {"choices": ["10320"]}})',
274+
mode: 'advanced',
263275
condition: { field: 'operation', value: 'create_request' },
264276
},
265277
{
@@ -571,8 +583,8 @@ Return ONLY the comment text - no explanations.`,
571583
if (!params.requestTypeId) {
572584
throw new Error('Request Type ID is required')
573585
}
574-
if (!params.summary) {
575-
throw new Error('Summary is required')
586+
if (!params.summary && !params.formAnswers) {
587+
throw new Error('Summary is required (unless using Form Answers)')
576588
}
577589
return {
578590
...baseParams,
@@ -584,7 +596,22 @@ Return ONLY the comment text - no explanations.`,
584596
requestParticipants: params.requestParticipants,
585597
channel: params.channel,
586598
requestFieldValues: params.requestFieldValues
587-
? JSON.parse(params.requestFieldValues)
599+
? (() => {
600+
try {
601+
return JSON.parse(params.requestFieldValues)
602+
} catch {
603+
throw new Error('requestFieldValues must be valid JSON')
604+
}
605+
})()
606+
: undefined,
607+
formAnswers: params.formAnswers
608+
? (() => {
609+
try {
610+
return JSON.parse(params.formAnswers)
611+
} catch {
612+
throw new Error('formAnswers must be valid JSON')
613+
}
614+
})()
588615
: undefined,
589616
}
590617
case 'get_request':
@@ -826,6 +853,10 @@ Return ONLY the comment text - no explanations.`,
826853
},
827854
channel: { type: 'string', description: 'Channel (e.g., portal, email)' },
828855
requestFieldValues: { type: 'string', description: 'JSON object of request field values' },
856+
formAnswers: {
857+
type: 'string',
858+
description: 'JSON object of form answers for form-based request types',
859+
},
829860
searchQuery: { type: 'string', description: 'Filter request types by name' },
830861
groupId: { type: 'string', description: 'Filter by request type group ID' },
831862
expand: { type: 'string', description: 'Comma-separated fields to expand' },

apps/sim/tools/error-extractors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export interface ErrorExtractorConfig {
3939
}
4040

4141
const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [
42+
{
43+
id: 'atlassian-errors',
44+
description: 'Atlassian REST API errorMessage field',
45+
examples: ['Jira', 'Jira Service Management', 'Confluence'],
46+
extract: (errorInfo) => errorInfo?.data?.errorMessage,
47+
},
4248
{
4349
id: 'graphql-errors',
4450
description: 'GraphQL errors array with message field',
@@ -221,6 +227,7 @@ export function extractErrorMessage(errorInfo?: ErrorInfo, extractorId?: string)
221227
}
222228

223229
export const ErrorExtractorId = {
230+
ATLASSIAN_ERRORS: 'atlassian-errors',
224231
GRAPHQL_ERRORS: 'graphql-errors',
225232
TWITTER_ERRORS: 'twitter-errors',
226233
DETAILS_ARRAY: 'details-array',

apps/sim/tools/jsm/create_request.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export const jsmCreateRequestTool: ToolConfig<JsmCreateRequestParams, JsmCreateR
4545
},
4646
summary: {
4747
type: 'string',
48-
required: true,
48+
required: false,
4949
visibility: 'user-or-llm',
50-
description: 'Summary/title for the service request',
50+
description: 'Summary/title for the service request (required unless using Form Answers)',
5151
},
5252
description: {
5353
type: 'string',
@@ -68,6 +68,13 @@ export const jsmCreateRequestTool: ToolConfig<JsmCreateRequestParams, JsmCreateR
6868
description:
6969
'Request field values as key-value pairs (overrides summary/description if provided)',
7070
},
71+
formAnswers: {
72+
type: 'json',
73+
required: false,
74+
visibility: 'user-or-llm',
75+
description:
76+
'Form answers for form-based request types (e.g., {"summary": {"text": "Title"}, "customfield_10010": {"choices": ["10320"]}})',
77+
},
7178
requestParticipants: {
7279
type: 'string',
7380
required: false,
@@ -98,6 +105,7 @@ export const jsmCreateRequestTool: ToolConfig<JsmCreateRequestParams, JsmCreateR
98105
description: params.description,
99106
raiseOnBehalfOf: params.raiseOnBehalfOf,
100107
requestFieldValues: params.requestFieldValues,
108+
formAnswers: params.formAnswers,
101109
requestParticipants: params.requestParticipants,
102110
channel: params.channel,
103111
}),

apps/sim/tools/jsm/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,10 @@ export interface JsmGetRequestTypesParams extends JsmBaseParams {
411411
export interface JsmCreateRequestParams extends JsmBaseParams {
412412
serviceDeskId: string
413413
requestTypeId: string
414-
summary: string
414+
summary?: string
415415
description?: string
416416
requestFieldValues?: Record<string, unknown>
417+
formAnswers?: Record<string, unknown>
417418
raiseOnBehalfOf?: string
418419
requestParticipants?: string[]
419420
channel?: string

0 commit comments

Comments
 (0)