Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] [Cases] External services not getting all comments bug fix #65307

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions x-pack/plugins/siem/public/containers/case/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,11 @@ export const REOPENED_CASES = ({
defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
});

export const TAG_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.case.tagFetchFailDescription',
{
defaultMessage: 'Failed to fetch Tags',
}
);

export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = i18n.translate(
'xpack.siem.containers.case.pushToExterService',
{
defaultMessage: 'Successfully sent to ServiceNow',
}
);
export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) =>
i18n.translate('xpack.siem.containers.case.pushToExternalService', {
values: { serviceName },
defaultMessage: 'Successfully sent to { serviceName }',
});

export const ERROR_PUSH_TO_SERVICE = i18n.translate(
'xpack.siem.case.configure.errorPushingToService',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [],
hasDataToPush: false,
},
},
});
});

it('Correctly marks first/last index - hasDataToPush: true', () => {
it('Correctly marks first/last index and comment id - hasDataToPush: true', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
Expand All @@ -142,6 +143,83 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [userActions[userActions.length - 1].commentId],
hasDataToPush: true,
},
},
});
});

it('Correctly marks first/last index and multiple comment ids, both needs push', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
getUserAction(['comment'], 'create'),
{ ...getUserAction(['comment'], 'create'), commentId: 'muahaha' },
];
const result = getPushedInfo(userActions, '123');
expect(result).toEqual({
hasDataToPush: true,
caseServices: {
'123': {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [
userActions[userActions.length - 2].commentId,
userActions[userActions.length - 1].commentId,
],
hasDataToPush: true,
},
},
});
});

it('Correctly marks first/last index and multiple comment ids, one needs push', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
getUserAction(['comment'], 'create'),
getUserAction(['pushed'], 'push-to-service'),
{ ...getUserAction(['comment'], 'create'), commentId: 'muahaha' },
];
const result = getPushedInfo(userActions, '123');
expect(result).toEqual({
hasDataToPush: true,
caseServices: {
'123': {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 5,
commentsToUpdate: [userActions[userActions.length - 1].commentId],
hasDataToPush: true,
},
},
});
});

it('Correctly marks first/last index and multiple comment ids, one needs push and one needs update', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
getUserAction(['comment'], 'create'),
getUserAction(['pushed'], 'push-to-service'),
{ ...getUserAction(['comment'], 'create'), commentId: 'muahaha' },
getUserAction(['comment'], 'update'),
getUserAction(['comment'], 'update'),
];
const result = getPushedInfo(userActions, '123');
expect(result).toEqual({
hasDataToPush: true,
caseServices: {
'123': {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 5,
commentsToUpdate: [
userActions[userActions.length - 3].commentId,
userActions[userActions.length - 1].commentId,
],
hasDataToPush: true,
},
},
Expand All @@ -162,6 +240,7 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [],
hasDataToPush: false,
},
},
Expand All @@ -182,11 +261,34 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 5,
commentsToUpdate: [],
hasDataToPush: false,
},
},
});
});
it('Correctly handles comment update with multiple push actions', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
getUserAction(['comment'], 'create'),
getUserAction(['pushed'], 'push-to-service'),
getUserAction(['comment'], 'update'),
];
const result = getPushedInfo(userActions, '123');
expect(result).toEqual({
hasDataToPush: true,
caseServices: {
'123': {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 5,
commentsToUpdate: [userActions[userActions.length - 1].commentId],
hasDataToPush: true,
},
},
});
});

it('Multiple connector tracking - hasDataToPush: true', () => {
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
Expand Down Expand Up @@ -215,6 +317,7 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [userActions[userActions.length - 2].commentId],
hasDataToPush: true,
},
'456': {
Expand All @@ -224,6 +327,7 @@ describe('useGetCaseUserActions', () => {
externalId: 'other_external_id',
firstPushIndex: 5,
lastPushIndex: 5,
commentsToUpdate: [],
hasDataToPush: false,
},
},
Expand Down Expand Up @@ -257,6 +361,7 @@ describe('useGetCaseUserActions', () => {
...basicPush,
firstPushIndex: 3,
lastPushIndex: 3,
commentsToUpdate: [userActions[userActions.length - 2].commentId],
hasDataToPush: true,
},
'456': {
Expand All @@ -266,6 +371,7 @@ describe('useGetCaseUserActions', () => {
externalId: 'other_external_id',
firstPushIndex: 5,
lastPushIndex: 5,
commentsToUpdate: [],
hasDataToPush: false,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CaseFullExternalService } from '../../../../case/common/api/cases';
interface CaseService extends CaseExternalService {
firstPushIndex: number;
lastPushIndex: number;
commentsToUpdate: string[];
hasDataToPush: boolean;
}

Expand Down Expand Up @@ -48,6 +49,24 @@ export interface UseGetCaseUserActions extends CaseUserActionsState {

const getExternalService = (value: string): CaseExternalService | null =>
convertToCamelCase<CaseFullExternalService, CaseExternalService>(parseString(`${value}`));
interface CommentsAndIndex {
commentId: string;
commentIndex: number;
}
// if the comment happens after the lastUpdateToCaseIndex, it should be included in commentsToUpdate
stephmilovic marked this conversation as resolved.
Show resolved Hide resolved
const buildCommentsToUpdate = (
commentsAndIndex: CommentsAndIndex[],
lastUpdateToCaseIndex: number
) =>
commentsAndIndex.reduce<string[]>(
(bacc, currentComment) =>
currentComment.commentIndex > lastUpdateToCaseIndex
? bacc.indexOf(currentComment.commentId) > -1
? [...bacc.filter(e => e !== currentComment.commentId), currentComment.commentId]
: [...bacc, currentComment.commentId]
: bacc,
[]
);

export const getPushedInfo = (
caseUserActions: CaseUserActions[],
Expand All @@ -69,11 +88,25 @@ export const getPushedInfo = (
.action !== 'push-to-service'
);
};
const commentsAndIndex = caseUserActions.reduce<CommentsAndIndex[]>(
(bacc, mua, index) =>
mua.actionField[0] === 'comment' && mua.commentId != null
? [
...bacc,
{
commentId: mua.commentId,
commentIndex: index,
},
]
: bacc,
[]
);

const caseServices = caseUserActions.reduce<CaseServices>((acc, cua, i) => {
if (cua.action !== 'push-to-service') {
return acc;
}

const externalService = getExternalService(`${cua.newValue}`);
if (externalService === null) {
return acc;
Expand All @@ -87,6 +120,7 @@ export const getPushedInfo = (
...acc[externalService.connectorId],
...externalService,
lastPushIndex: i,
commentsToUpdate: buildCommentsToUpdate(commentsAndIndex, i),
},
}
: {
Expand All @@ -95,6 +129,7 @@ export const getPushedInfo = (
firstPushIndex: i,
lastPushIndex: i,
hasDataToPush: hasDataToPushForConnector(externalService.connectorId),
commentsToUpdate: buildCommentsToUpdate(commentsAndIndex, i),
},
}),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
serviceConnectorUser,
} from './mock';
import * as api from './api';
import { CaseServices } from './use_get_case_user_actions';

jest.mock('./api');

Expand All @@ -32,6 +33,7 @@ describe('usePostPushToService', () => {
...basicPush,
firstPushIndex: 1,
lastPushIndex: 1,
commentsToUpdate: [basicComment.id],
hasDataToPush: false,
},
},
Expand Down Expand Up @@ -64,13 +66,15 @@ describe('usePostPushToService', () => {
...basicPush,
firstPushIndex: 1,
lastPushIndex: 1,
commentsToUpdate: [basicComment.id],
hasDataToPush: true,
},
'456': {
...basicPush,
connectorId: '456',
externalId: 'other_external_id',
firstPushIndex: 4,
commentsToUpdate: [basicComment.id],
lastPushIndex: 6,
hasDataToPush: false,
},
Expand Down Expand Up @@ -127,6 +131,31 @@ describe('usePostPushToService', () => {
await waitForNextUpdate();
expect(spyOnPushToService).toBeCalledWith(
samplePush.connectorId,
formatServiceRequestData(basicCase, '123', sampleCaseServices as CaseServices),
abortCtrl.signal
);
});
});

it('calls pushToService with correct arguments when no push history', async () => {
const samplePush2 = {
caseId: pushedCase.id,
caseServices: {},
connectorName: 'connector name',
connectorId: 'none',
updateCase,
};
const spyOnPushToService = jest.spyOn(api, 'pushToService');

await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UsePostPushToService>(() =>
usePostPushToService()
);
await waitForNextUpdate();
result.current.postPushToService(samplePush2);
await waitForNextUpdate();
expect(spyOnPushToService).toBeCalledWith(
samplePush2.connectorId,
formatServiceRequestData(basicCase, 'none', {}),
abortCtrl.signal
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ export const usePostPushToService = (): UsePostPushToService => {
dispatch({ type: 'FETCH_SUCCESS_PUSH_SERVICE', payload: responseService });
dispatch({ type: 'FETCH_SUCCESS_PUSH_CASE', payload: responseCase });
updateCase(responseCase);
displaySuccessToast(i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE, dispatchToaster);
displaySuccessToast(
i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE(connectorName),
dispatchToaster
);
}
} catch (error) {
if (!cancel) {
Expand Down Expand Up @@ -156,25 +159,12 @@ export const formatServiceRequestData = (
createdBy,
comments,
description,
externalService,
title,
updatedAt,
updatedBy,
} = myCase;
let actualExternalService = externalService;
if (
externalService != null &&
externalService.connectorId !== connectorId &&
caseServices[connectorId]
) {
actualExternalService = caseServices[connectorId];
} else if (
externalService != null &&
externalService.connectorId !== connectorId &&
!caseServices[connectorId]
) {
actualExternalService = null;
}
const actualExternalService = caseServices[connectorId] ?? null;

return {
caseId,
createdAt,
Expand All @@ -183,17 +173,9 @@ export const formatServiceRequestData = (
username: createdBy?.username ?? '',
},
comments: comments
.filter(c => {
const lastPush = c.pushedAt != null ? new Date(c.pushedAt) : null;
const lastUpdate = c.updatedAt != null ? new Date(c.updatedAt) : null;
if (
lastPush === null ||
(lastPush != null && lastUpdate != null && lastPush.getTime() < lastUpdate?.getTime())
) {
return true;
}
return false;
})
.filter(
c => actualExternalService == null || actualExternalService.commentsToUpdate.includes(c.id)
)
.map(c => ({
commentId: c.id,
comment: c.comment,
Expand Down
Loading