diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts index e7e2b2bc4118..848575798f10 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts @@ -78,7 +78,9 @@ describe('ServiceNow', () => { services, } as unknown) as ServiceNowActionTypeExecutorOptions; await actionType.executor(executorOptions); - expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe('comments'); + expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe( + 'work_notes' + ); }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index f6be7c90820a..f2b500df6ccb 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -76,7 +76,12 @@ export function getServiceNowITSMActionType(params: GetActionTypeParams): Servic }), params: ExecutorParamsSchemaITSM, }, - executor: curry(executor)({ logger, configurationUtilities, table: serviceNowITSMTable }), + executor: curry(executor)({ + logger, + configurationUtilities, + table: serviceNowITSMTable, + commentFieldKey: 'work_notes', + }), }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 6707d9393bd5..07ed9edc94d3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -11,7 +11,11 @@ import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } fr import * as i18n from './translations'; import { Logger } from '../../../../../../src/core/server'; -import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types'; +import { + ServiceNowPublicConfigurationType, + ServiceNowSecretConfigurationType, + ResponseError, +} from './types'; import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils'; import { ActionsConfigurationUtilities } from '../../actions_config'; @@ -62,6 +66,15 @@ export const createExternalService = ( } }; + const createErrorMessage = (errorResponse: ResponseError): string => { + if (errorResponse == null) { + return ''; + } + + const { error } = errorResponse; + return error != null ? `${error?.message}: ${error?.detail}` : ''; + }; + const getIncident = async (id: string) => { try { const res = await request({ @@ -76,7 +89,9 @@ export const createExternalService = ( throw new Error( getErrorMessage( i18n.SERVICENOW, - `Unable to get incident with id ${id}. Error: ${error.message}` + `Unable to get incident with id ${id}. Error: ${ + error.message + } Reason: ${createErrorMessage(error.response?.data)}` ) ); } @@ -97,7 +112,9 @@ export const createExternalService = ( throw new Error( getErrorMessage( i18n.SERVICENOW, - `Unable to find incidents by query. Error: ${error.message}` + `Unable to find incidents by query. Error: ${error.message} Reason: ${createErrorMessage( + error.response?.data + )}` ) ); } @@ -122,7 +139,12 @@ export const createExternalService = ( }; } catch (error) { throw new Error( - getErrorMessage(i18n.SERVICENOW, `Unable to create incident. Error: ${error.message}`) + getErrorMessage( + i18n.SERVICENOW, + `Unable to create incident. Error: ${error.message} Reason: ${createErrorMessage( + error.response?.data + )}` + ) ); } }; @@ -147,7 +169,9 @@ export const createExternalService = ( throw new Error( getErrorMessage( i18n.SERVICENOW, - `Unable to update incident with id ${incidentId}. Error: ${error.message}` + `Unable to update incident with id ${incidentId}. Error: ${ + error.message + } Reason: ${createErrorMessage(error.response?.data)}` ) ); } @@ -165,7 +189,12 @@ export const createExternalService = ( return res.data.result.length > 0 ? res.data.result : []; } catch (error) { throw new Error( - getErrorMessage(i18n.SERVICENOW, `Unable to get fields. Error: ${error.message}`) + getErrorMessage( + i18n.SERVICENOW, + `Unable to get fields. Error: ${error.message} Reason: ${createErrorMessage( + error.response?.data + )}` + ) ); } }; @@ -182,7 +211,12 @@ export const createExternalService = ( return res.data.result; } catch (error) { throw new Error( - getErrorMessage(i18n.SERVICENOW, `Unable to get choices. Error: ${error.message}`) + getErrorMessage( + i18n.SERVICENOW, + `Unable to get choices. Error: ${error.message} Reason: ${createErrorMessage( + error.response?.data + )}` + ) ); } }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index 1c0b2c9c62ee..50631cf289a7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -171,3 +171,12 @@ export interface ExternalServiceCommentResponse { pushedDate: string; externalCommentId?: string; } + +type TypeNullOrUndefined = T | null | undefined; +export interface ResponseError { + error: TypeNullOrUndefined<{ + message: TypeNullOrUndefined; + detail: TypeNullOrUndefined; + }>; + status: TypeNullOrUndefined; +} diff --git a/x-pack/plugins/case/server/client/configure/mock.ts b/x-pack/plugins/case/server/client/configure/mock.ts index 4d0c384e23e2..ee214de9b51d 100644 --- a/x-pack/plugins/case/server/client/configure/mock.ts +++ b/x-pack/plugins/case/server/client/configure/mock.ts @@ -83,7 +83,24 @@ export const mappings: TestMappings = { }, { source: 'comments', - target: 'comments', + target: 'work_notes', + action_type: 'append', + }, + ], + [ConnectorTypes.serviceNowSIR]: [ + { + source: 'title', + target: 'short_description', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'work_notes', action_type: 'append', }, ], @@ -613,6 +630,24 @@ export const formatFieldsTestData: FormatFieldsTestData[] = [ fields: serviceNowFields, type: ConnectorTypes.serviceNowITSM, }, + { + expected: [ + { id: 'approval', name: 'Approval', required: false, type: 'text' }, + { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, + { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, + { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, + { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'textarea' }, + { id: 'number', name: 'Number', required: false, type: 'text' }, + { id: 'short_description', name: 'Short description', required: false, type: 'text' }, + { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, + { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, + { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, + { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, + ], + fields: serviceNowFields, + type: ConnectorTypes.serviceNowSIR, + }, ]; export const mockGetFieldsResponse = { status: 'ok', diff --git a/x-pack/plugins/case/server/client/configure/utils.ts b/x-pack/plugins/case/server/client/configure/utils.ts index 7e91c2ae5a4d..80e6c7a3b886 100644 --- a/x-pack/plugins/case/server/client/configure/utils.ts +++ b/x-pack/plugins/case/server/client/configure/utils.ts @@ -93,21 +93,26 @@ const findTextAreaField = (fields: ConnectorField[]): string => const getPreferredFields = (theType: string) => { let title: string = ''; let description: string = ''; + let comments: string = ''; + if (theType === ConnectorTypes.jira) { title = 'summary'; description = 'description'; + comments = 'comments'; } else if (theType === ConnectorTypes.resilient) { title = 'name'; description = 'description'; + comments = 'comments'; } else if ( theType === ConnectorTypes.serviceNowITSM || theType === ConnectorTypes.serviceNowSIR ) { title = 'short_description'; description = 'description'; + comments = 'work_notes'; } - return { title, description }; + return { title, description, comments }; }; const getRemainingFields = (fields: ConnectorField[], titleTarget: string) => @@ -143,9 +148,16 @@ export const createDefaultMapping = ( theType: string ): ConnectorMappingsAttributes[] => { const { description: dynamicDescription, title: dynamicTitle } = getDynamicFields(fields); - const { description: preferredDescription, title: preferredTitle } = getPreferredFields(theType); + + const { + description: preferredDescription, + title: preferredTitle, + comments: preferredComments, + } = getPreferredFields(theType); + let titleTarget = dynamicTitle; let descriptionTarget = dynamicDescription; + if (preferredTitle.length > 0 && preferredDescription.length > 0) { if (shouldTargetBePreferred(fields, dynamicTitle, preferredTitle)) { const { description: dynamicDescriptionOverwrite } = getDynamicFields(fields, preferredTitle); @@ -169,7 +181,7 @@ export const createDefaultMapping = ( }, { source: 'comments', - target: 'comments', + target: preferredComments, action_type: 'append', }, ];