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..93f9fa10cf0 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) { @@ -31,6 +45,7 @@ export async function POST(request: NextRequest) { description, raiseOnBehalfOf, requestFieldValues, + formAnswers, requestParticipants, channel, expand, @@ -55,7 +70,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 +84,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 || description || 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) { @@ -112,7 +142,10 @@ export async function POST(request: NextRequest) { }) return NextResponse.json( - { error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText }, + { + error: parseJsmErrorMessage(response.status, response.statusText, errorText), + details: errorText, + }, { status: response.status } ) } @@ -178,7 +211,10 @@ export async function POST(request: NextRequest) { }) return NextResponse.json( - { error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText }, + { + error: parseJsmErrorMessage(response.status, response.statusText, errorText), + 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..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: { @@ -238,6 +237,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 +245,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 +253,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 +262,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 +583,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, @@ -584,7 +596,22 @@ 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, } case 'get_request': @@ -826,6 +853,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