[Security Solution][Case] Push ITSM comments as work notes (#93916)
* Push ITSM comments as work notes * Fix cases mapping * Improve error messages * Fix tests
This commit is contained in:
parent
8de91b506e
commit
faae074607
6 changed files with 110 additions and 13 deletions
|
@ -78,7 +78,9 @@ describe('ServiceNow', () => {
|
||||||
services,
|
services,
|
||||||
} as unknown) as ServiceNowActionTypeExecutorOptions;
|
} as unknown) as ServiceNowActionTypeExecutorOptions;
|
||||||
await actionType.executor(executorOptions);
|
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'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,7 +76,12 @@ export function getServiceNowITSMActionType(params: GetActionTypeParams): Servic
|
||||||
}),
|
}),
|
||||||
params: ExecutorParamsSchemaITSM,
|
params: ExecutorParamsSchemaITSM,
|
||||||
},
|
},
|
||||||
executor: curry(executor)({ logger, configurationUtilities, table: serviceNowITSMTable }),
|
executor: curry(executor)({
|
||||||
|
logger,
|
||||||
|
configurationUtilities,
|
||||||
|
table: serviceNowITSMTable,
|
||||||
|
commentFieldKey: 'work_notes',
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,11 @@ import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } fr
|
||||||
|
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
import { Logger } from '../../../../../../src/core/server';
|
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 { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils';
|
||||||
import { ActionsConfigurationUtilities } from '../../actions_config';
|
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) => {
|
const getIncident = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await request({
|
||||||
|
@ -76,7 +89,9 @@ export const createExternalService = (
|
||||||
throw new Error(
|
throw new Error(
|
||||||
getErrorMessage(
|
getErrorMessage(
|
||||||
i18n.SERVICENOW,
|
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(
|
throw new Error(
|
||||||
getErrorMessage(
|
getErrorMessage(
|
||||||
i18n.SERVICENOW,
|
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) {
|
} catch (error) {
|
||||||
throw new 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(
|
throw new Error(
|
||||||
getErrorMessage(
|
getErrorMessage(
|
||||||
i18n.SERVICENOW,
|
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 : [];
|
return res.data.result.length > 0 ? res.data.result : [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new 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;
|
return res.data.result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new 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
|
||||||
|
)}`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -171,3 +171,12 @@ export interface ExternalServiceCommentResponse {
|
||||||
pushedDate: string;
|
pushedDate: string;
|
||||||
externalCommentId?: string;
|
externalCommentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TypeNullOrUndefined<T> = T | null | undefined;
|
||||||
|
export interface ResponseError {
|
||||||
|
error: TypeNullOrUndefined<{
|
||||||
|
message: TypeNullOrUndefined<string>;
|
||||||
|
detail: TypeNullOrUndefined<string>;
|
||||||
|
}>;
|
||||||
|
status: TypeNullOrUndefined<string>;
|
||||||
|
}
|
||||||
|
|
|
@ -83,7 +83,24 @@ export const mappings: TestMappings = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'comments',
|
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',
|
action_type: 'append',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -613,6 +630,24 @@ export const formatFieldsTestData: FormatFieldsTestData[] = [
|
||||||
fields: serviceNowFields,
|
fields: serviceNowFields,
|
||||||
type: ConnectorTypes.serviceNowITSM,
|
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 = {
|
export const mockGetFieldsResponse = {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
|
|
|
@ -93,21 +93,26 @@ const findTextAreaField = (fields: ConnectorField[]): string =>
|
||||||
const getPreferredFields = (theType: string) => {
|
const getPreferredFields = (theType: string) => {
|
||||||
let title: string = '';
|
let title: string = '';
|
||||||
let description: string = '';
|
let description: string = '';
|
||||||
|
let comments: string = '';
|
||||||
|
|
||||||
if (theType === ConnectorTypes.jira) {
|
if (theType === ConnectorTypes.jira) {
|
||||||
title = 'summary';
|
title = 'summary';
|
||||||
description = 'description';
|
description = 'description';
|
||||||
|
comments = 'comments';
|
||||||
} else if (theType === ConnectorTypes.resilient) {
|
} else if (theType === ConnectorTypes.resilient) {
|
||||||
title = 'name';
|
title = 'name';
|
||||||
description = 'description';
|
description = 'description';
|
||||||
|
comments = 'comments';
|
||||||
} else if (
|
} else if (
|
||||||
theType === ConnectorTypes.serviceNowITSM ||
|
theType === ConnectorTypes.serviceNowITSM ||
|
||||||
theType === ConnectorTypes.serviceNowSIR
|
theType === ConnectorTypes.serviceNowSIR
|
||||||
) {
|
) {
|
||||||
title = 'short_description';
|
title = 'short_description';
|
||||||
description = 'description';
|
description = 'description';
|
||||||
|
comments = 'work_notes';
|
||||||
}
|
}
|
||||||
|
|
||||||
return { title, description };
|
return { title, description, comments };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRemainingFields = (fields: ConnectorField[], titleTarget: string) =>
|
const getRemainingFields = (fields: ConnectorField[], titleTarget: string) =>
|
||||||
|
@ -143,9 +148,16 @@ export const createDefaultMapping = (
|
||||||
theType: string
|
theType: string
|
||||||
): ConnectorMappingsAttributes[] => {
|
): ConnectorMappingsAttributes[] => {
|
||||||
const { description: dynamicDescription, title: dynamicTitle } = getDynamicFields(fields);
|
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 titleTarget = dynamicTitle;
|
||||||
let descriptionTarget = dynamicDescription;
|
let descriptionTarget = dynamicDescription;
|
||||||
|
|
||||||
if (preferredTitle.length > 0 && preferredDescription.length > 0) {
|
if (preferredTitle.length > 0 && preferredDescription.length > 0) {
|
||||||
if (shouldTargetBePreferred(fields, dynamicTitle, preferredTitle)) {
|
if (shouldTargetBePreferred(fields, dynamicTitle, preferredTitle)) {
|
||||||
const { description: dynamicDescriptionOverwrite } = getDynamicFields(fields, preferredTitle);
|
const { description: dynamicDescriptionOverwrite } = getDynamicFields(fields, preferredTitle);
|
||||||
|
@ -169,7 +181,7 @@ export const createDefaultMapping = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'comments',
|
source: 'comments',
|
||||||
target: 'comments',
|
target: preferredComments,
|
||||||
action_type: 'append',
|
action_type: 'append',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue