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 all 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 @@ -14,9 +14,10 @@ import { CaseExternalService, CaseUserActions, ElasticUser } from './types';
import { convertToCamelCase, parseString } from './utils';
import { CaseFullExternalService } from '../../../../case/common/api/cases';

interface CaseService extends CaseExternalService {
export interface CaseService extends CaseExternalService {
firstPushIndex: number;
lastPushIndex: number;
commentsToUpdate: string[];
hasDataToPush: boolean;
}

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

const getExternalService = (value: string): CaseExternalService | null =>
convertToCamelCase<CaseFullExternalService, CaseExternalService>(parseString(`${value}`));
interface CommentsAndIndex {
commentId: string;
commentIndex: number;
}

export const getPushedInfo = (
caseUserActions: CaseUserActions[],
Expand All @@ -69,11 +74,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) => {
let 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 +106,7 @@ export const getPushedInfo = (
...acc[externalService.connectorId],
...externalService,
lastPushIndex: i,
commentsToUpdate: [],
},
}
: {
Expand All @@ -95,11 +115,31 @@ export const getPushedInfo = (
firstPushIndex: i,
lastPushIndex: i,
hasDataToPush: hasDataToPushForConnector(externalService.connectorId),
commentsToUpdate: [],
},
}),
};
}, {});

caseServices = Object.keys(caseServices).reduce<CaseServices>((acc, key) => {
return {
...acc,
[key]: {
...caseServices[key],
// if the comment happens after the lastUpdateToCaseIndex, it should be included in commentsToUpdate
commentsToUpdate: commentsAndIndex.reduce<string[]>(
(bacc, currentComment) =>
currentComment.commentIndex > caseServices[key].lastPushIndex
? bacc.indexOf(currentComment.commentId) > -1
? [...bacc.filter(e => e !== currentComment.commentId), currentComment.commentId]
: [...bacc, currentComment.commentId]
: bacc,
[]
),
},
};
}, {});

const hasDataToPush =
caseServices[caseConnectorId] != null ? caseServices[caseConnectorId].hasDataToPush : true;
return {
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
Loading