From 648b65f5622583d6bcbaa74d01211d8908126dda Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 8 Apr 2026 21:37:17 -0700 Subject: [PATCH 1/4] fix(jsm): improve create request error handling, add form-based submission support --- .../docs/en/tools/jira_service_management.mdx | 3 +- apps/sim/app/api/tools/jsm/request/route.ts | 56 ++++++++++++++++--- .../blocks/blocks/jira_service_management.ts | 22 +++++++- apps/sim/tools/error-extractors.ts | 7 +++ apps/sim/tools/jsm/create_request.ts | 12 +++- apps/sim/tools/jsm/types.ts | 3 +- 6 files changed, 89 insertions(+), 14 deletions(-) diff --git a/apps/docs/content/docs/en/tools/jira_service_management.mdx b/apps/docs/content/docs/en/tools/jira_service_management.mdx index cd294152d3e..533acee20ad 100644 --- a/apps/docs/content/docs/en/tools/jira_service_management.mdx +++ b/apps/docs/content/docs/en/tools/jira_service_management.mdx @@ -113,10 +113,11 @@ Create a new service request in Jira Service Management | `cloudId` | string | No | Jira Cloud ID for the instance | | `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) | | `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) | -| `summary` | string | Yes | Summary/title for the service request | +| `summary` | string | No | Summary/title for the service request \(required unless using Form Answers\) | | `description` | string | No | Description for the service request | | `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of | | `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) | +| `formAnswers` | json | No | Form answers for form-based request types \(e.g., \{"summary": \{"text": "Title"\}, "customfield_10010": \{"choices": \["10320"\]\}\}\) | | `requestParticipants` | string | No | Comma-separated account IDs to add as request participants | | `channel` | string | No | Channel the request originates from \(e.g., portal, email\) | diff --git a/apps/sim/app/api/tools/jsm/request/route.ts b/apps/sim/app/api/tools/jsm/request/route.ts index ae5b150b5b8..c1263485a8b 100644 --- a/apps/sim/app/api/tools/jsm/request/route.ts +++ b/apps/sim/app/api/tools/jsm/request/route.ts @@ -31,6 +31,7 @@ export async function POST(request: NextRequest) { description, raiseOnBehalfOf, requestFieldValues, + formAnswers, requestParticipants, channel, expand, @@ -55,7 +56,7 @@ export async function POST(request: NextRequest) { const baseUrl = getJsmApiBaseUrl(cloudId) - const isCreateOperation = serviceDeskId && requestTypeId && summary + const isCreateOperation = serviceDeskId && requestTypeId && (summary || formAnswers) if (isCreateOperation) { const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') @@ -69,15 +70,30 @@ export async function POST(request: NextRequest) { } const url = `${baseUrl}/request` - logger.info('Creating request at:', url) + logger.info('Creating request at:', { url, serviceDeskId, requestTypeId }) const requestBody: Record = { serviceDeskId, requestTypeId, - requestFieldValues: requestFieldValues || { - summary, - ...(description && { description }), - }, + } + + if (summary || requestFieldValues) { + const fieldValues = + requestFieldValues && typeof requestFieldValues === 'object' + ? { + ...(!requestFieldValues.summary && summary ? { summary } : {}), + ...(!requestFieldValues.description && description ? { description } : {}), + ...requestFieldValues, + } + : { + ...(summary && { summary }), + ...(description && { description }), + } + requestBody.requestFieldValues = fieldValues + } + + if (formAnswers && typeof formAnswers === 'object') { + requestBody.form = { answers: formAnswers } } if (raiseOnBehalfOf) { @@ -111,8 +127,20 @@ export async function POST(request: NextRequest) { error: errorText, }) + let errorMessage = `JSM API error: ${response.status} ${response.statusText}` + try { + const errorData = JSON.parse(errorText) + if (errorData.errorMessage) { + errorMessage = `JSM API error: ${errorData.errorMessage}` + } + } catch { + if (errorText) { + errorMessage = `JSM API error: ${errorText}` + } + } + return NextResponse.json( - { error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText }, + { error: errorMessage, details: errorText }, { status: response.status } ) } @@ -177,8 +205,20 @@ export async function POST(request: NextRequest) { error: errorText, }) + let errorMessage = `JSM API error: ${response.status} ${response.statusText}` + try { + const errorData = JSON.parse(errorText) + if (errorData.errorMessage) { + errorMessage = `JSM API error: ${errorData.errorMessage}` + } + } catch { + if (errorText) { + errorMessage = `JSM API error: ${errorText}` + } + } + return NextResponse.json( - { error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText }, + { error: errorMessage, details: errorText }, { status: response.status } ) } diff --git a/apps/sim/blocks/blocks/jira_service_management.ts b/apps/sim/blocks/blocks/jira_service_management.ts index d78251cb3c0..22cb575436a 100644 --- a/apps/sim/blocks/blocks/jira_service_management.ts +++ b/apps/sim/blocks/blocks/jira_service_management.ts @@ -238,6 +238,7 @@ Return ONLY the description text - no explanations.`, title: 'Raise on Behalf Of', type: 'short-input', placeholder: 'Account ID to raise request on behalf of', + mode: 'advanced', condition: { field: 'operation', value: 'create_request' }, }, { @@ -245,6 +246,7 @@ Return ONLY the description text - no explanations.`, title: 'Request Participants', type: 'short-input', placeholder: 'Comma-separated account IDs to add as participants', + mode: 'advanced', condition: { field: 'operation', value: 'create_request' }, }, { @@ -252,6 +254,7 @@ Return ONLY the description text - no explanations.`, title: 'Channel', type: 'short-input', placeholder: 'Channel (e.g., portal, email)', + mode: 'advanced', condition: { field: 'operation', value: 'create_request' }, }, { @@ -260,6 +263,16 @@ Return ONLY the description text - no explanations.`, type: 'long-input', placeholder: 'JSON object of field values (e.g., {"summary": "Title", "customfield_10010": "value"})', + mode: 'advanced', + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'formAnswers', + title: 'Form Answers', + type: 'long-input', + placeholder: + 'JSON object for form-based request types (e.g., {"summary": {"text": "Title"}, "customfield_10010": {"choices": ["10320"]}})', + mode: 'advanced', condition: { field: 'operation', value: 'create_request' }, }, { @@ -571,8 +584,8 @@ Return ONLY the comment text - no explanations.`, if (!params.requestTypeId) { throw new Error('Request Type ID is required') } - if (!params.summary) { - throw new Error('Summary is required') + if (!params.summary && !params.formAnswers) { + throw new Error('Summary is required (unless using Form Answers)') } return { ...baseParams, @@ -586,6 +599,7 @@ Return ONLY the comment text - no explanations.`, requestFieldValues: params.requestFieldValues ? JSON.parse(params.requestFieldValues) : undefined, + formAnswers: params.formAnswers ? JSON.parse(params.formAnswers) : undefined, } case 'get_request': if (!params.issueIdOrKey) { @@ -826,6 +840,10 @@ Return ONLY the comment text - no explanations.`, }, channel: { type: 'string', description: 'Channel (e.g., portal, email)' }, requestFieldValues: { type: 'string', description: 'JSON object of request field values' }, + formAnswers: { + type: 'string', + description: 'JSON object of form answers for form-based request types', + }, searchQuery: { type: 'string', description: 'Filter request types by name' }, groupId: { type: 'string', description: 'Filter by request type group ID' }, expand: { type: 'string', description: 'Comma-separated fields to expand' }, diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index 9de3e94e82b..45b6db39220 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -39,6 +39,12 @@ export interface ErrorExtractorConfig { } const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ + { + id: 'atlassian-errors', + description: 'Atlassian REST API errorMessage field', + examples: ['Jira', 'Jira Service Management', 'Confluence'], + extract: (errorInfo) => errorInfo?.data?.errorMessage, + }, { id: 'graphql-errors', description: 'GraphQL errors array with message field', @@ -221,6 +227,7 @@ export function extractErrorMessage(errorInfo?: ErrorInfo, extractorId?: string) } export const ErrorExtractorId = { + ATLASSIAN_ERRORS: 'atlassian-errors', GRAPHQL_ERRORS: 'graphql-errors', TWITTER_ERRORS: 'twitter-errors', DETAILS_ARRAY: 'details-array', diff --git a/apps/sim/tools/jsm/create_request.ts b/apps/sim/tools/jsm/create_request.ts index c31db514f1b..d8e51a7e7e5 100644 --- a/apps/sim/tools/jsm/create_request.ts +++ b/apps/sim/tools/jsm/create_request.ts @@ -45,9 +45,9 @@ export const jsmCreateRequestTool: ToolConfig + formAnswers?: Record raiseOnBehalfOf?: string requestParticipants?: string[] channel?: string From e513fd18896012bcfa6c5f3587172509188bea30 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 8 Apr 2026 21:43:37 -0700 Subject: [PATCH 2/4] refactor(jsm): extract parseJsmErrorMessage helper to deduplicate error handling --- apps/sim/app/api/tools/jsm/request/route.ts | 48 ++++++++++----------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/apps/sim/app/api/tools/jsm/request/route.ts b/apps/sim/app/api/tools/jsm/request/route.ts index c1263485a8b..3241c05a092 100644 --- a/apps/sim/app/api/tools/jsm/request/route.ts +++ b/apps/sim/app/api/tools/jsm/request/route.ts @@ -12,6 +12,20 @@ export const dynamic = 'force-dynamic' const logger = createLogger('JsmRequestAPI') +function parseJsmErrorMessage(status: number, statusText: string, errorText: string): string { + try { + const errorData = JSON.parse(errorText) + if (errorData.errorMessage) { + return `JSM API error: ${errorData.errorMessage}` + } + } catch { + if (errorText) { + return `JSM API error: ${errorText}` + } + } + return `JSM API error: ${status} ${statusText}` +} + export async function POST(request: NextRequest) { const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { @@ -127,20 +141,11 @@ export async function POST(request: NextRequest) { error: errorText, }) - let errorMessage = `JSM API error: ${response.status} ${response.statusText}` - try { - const errorData = JSON.parse(errorText) - if (errorData.errorMessage) { - errorMessage = `JSM API error: ${errorData.errorMessage}` - } - } catch { - if (errorText) { - errorMessage = `JSM API error: ${errorText}` - } - } - return NextResponse.json( - { error: errorMessage, details: errorText }, + { + error: parseJsmErrorMessage(response.status, response.statusText, errorText), + details: errorText, + }, { status: response.status } ) } @@ -205,20 +210,11 @@ export async function POST(request: NextRequest) { error: errorText, }) - let errorMessage = `JSM API error: ${response.status} ${response.statusText}` - try { - const errorData = JSON.parse(errorText) - if (errorData.errorMessage) { - errorMessage = `JSM API error: ${errorData.errorMessage}` - } - } catch { - if (errorText) { - errorMessage = `JSM API error: ${errorText}` - } - } - return NextResponse.json( - { error: errorMessage, details: errorText }, + { + error: parseJsmErrorMessage(response.status, response.statusText, errorText), + details: errorText, + }, { status: response.status } ) } From 724f9fd636b2ba458506cef3ab5c929f410c590d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 8 Apr 2026 21:44:26 -0700 Subject: [PATCH 3/4] fix(jsm): remove required on summary for advanced mode, add JSON.parse error handling --- .../blocks/blocks/jira_service_management.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/sim/blocks/blocks/jira_service_management.ts b/apps/sim/blocks/blocks/jira_service_management.ts index 22cb575436a..68e8a357e9b 100644 --- a/apps/sim/blocks/blocks/jira_service_management.ts +++ b/apps/sim/blocks/blocks/jira_service_management.ts @@ -198,7 +198,6 @@ export const JiraServiceManagementBlock: BlockConfig = { id: 'summary', title: 'Summary', type: 'short-input', - required: true, placeholder: 'Enter request summary', condition: { field: 'operation', value: 'create_request' }, wandConfig: { @@ -597,9 +596,23 @@ Return ONLY the comment text - no explanations.`, requestParticipants: params.requestParticipants, channel: params.channel, requestFieldValues: params.requestFieldValues - ? JSON.parse(params.requestFieldValues) + ? (() => { + try { + return JSON.parse(params.requestFieldValues) + } catch { + throw new Error('requestFieldValues must be valid JSON') + } + })() + : undefined, + formAnswers: params.formAnswers + ? (() => { + try { + return JSON.parse(params.formAnswers) + } catch { + throw new Error('formAnswers must be valid JSON') + } + })() : undefined, - formAnswers: params.formAnswers ? JSON.parse(params.formAnswers) : undefined, } case 'get_request': if (!params.issueIdOrKey) { From 3019fd062f0456b72f4b27f67308b0a169b0d9e6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 8 Apr 2026 22:03:48 -0700 Subject: [PATCH 4/4] fix(jsm): include description in requestFieldValues gate for form-only requests --- apps/sim/app/api/tools/jsm/request/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/api/tools/jsm/request/route.ts b/apps/sim/app/api/tools/jsm/request/route.ts index 3241c05a092..93f9fa10cf0 100644 --- a/apps/sim/app/api/tools/jsm/request/route.ts +++ b/apps/sim/app/api/tools/jsm/request/route.ts @@ -91,7 +91,7 @@ export async function POST(request: NextRequest) { requestTypeId, } - if (summary || requestFieldValues) { + if (summary || description || requestFieldValues) { const fieldValues = requestFieldValues && typeof requestFieldValues === 'object' ? {