mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[ResponseOps][Cases] Populate total alerts and comments in the cases saved objects (#223992)
## Summary This is a farewell PR to Cases. Probably my last PR to the cases codebase. It was quite a journey, and I learned a lot. I hope the best for the feature of Cases. ## Decisions Just before Cases was forbidden to do migrations, we did a last migration to all cases to persist `total_alerts: -1` and `total_comments: -1`. We did that so that in the future, when we would want to populate the fields, we would know which cases have their fields populated and which do not. In this PR, due to time constraints and criticality of the feature, I took the following decisions: - Cases return from their APIs the total comments and alerts of each case. They do that by doing an aggregation, getting the counts, and merging them with the response. I did not change that behavior. In following PRs, it can be optimized and fetch the stats only for cases that do not yet have their stats populated (cases with -1 in the counts) - When a case is created, the counts are zero. - When a comment or alert is added, I do an aggregation to get the stats (total alerts and comments) of the current case, and then update the counters with the number of the newly created attachments. The case is updated without version checks. In race conditions, where an attachment is being added before updating the case, the numbers could be off. This is a deliberate choice. It can be fixed later with retries and version concurrency control. - The case service will continue to not return the `total_alerts` and `total_comments`. - The case service will accept the `total_alerts` and `total_comments` attributes to be able to set them. Fixes: https://github.com/elastic/kibana/issues/217636 cc @michaelolo24 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
0c419c97ac
commit
f30335ac3d
24 changed files with 834 additions and 90 deletions
|
@ -15,12 +15,30 @@ describe('delete', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
clientArgs.services.attachmentService.getter.get.mockResolvedValue(mockCaseComments[0]);
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refreshes when deleting', async () => {
|
||||||
|
await deleteComment({ caseID: 'mock-id-1', attachmentID: 'mock-comment-1' }, clientArgs);
|
||||||
|
|
||||||
|
expect(clientArgs.services.attachmentService.bulkDelete).toHaveBeenCalledWith({
|
||||||
|
attachmentIds: ['mock-comment-1'],
|
||||||
|
refresh: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Alerts', () => {
|
describe('Alerts', () => {
|
||||||
const commentSO = mockCaseComments[0];
|
const commentSO = mockCaseComments[0];
|
||||||
const alertsSO = mockCaseComments[3];
|
const alertsSO = mockCaseComments[3];
|
||||||
clientArgs.services.attachmentService.getter.get.mockResolvedValue(alertsSO);
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clientArgs.services.attachmentService.getter.get.mockResolvedValue(alertsSO);
|
||||||
|
});
|
||||||
|
|
||||||
it('delete alerts correctly', async () => {
|
it('delete alerts correctly', async () => {
|
||||||
await deleteComment({ caseID: 'mock-id-4', attachmentID: 'mock-comment-4' }, clientArgs);
|
await deleteComment({ caseID: 'mock-id-4', attachmentID: 'mock-comment-4' }, clientArgs);
|
||||||
|
@ -38,10 +56,41 @@ describe('delete', () => {
|
||||||
expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).not.toHaveBeenCalledWith();
|
expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).not.toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Attachment stats', () => {
|
||||||
|
it('updates attachment stats correctly', async () => {
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 2,
|
||||||
|
alerts: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteComment({ caseID: 'mock-id-1', attachmentID: 'mock-comment-1' }, clientArgs);
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(2);
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(2);
|
||||||
|
expect(args.updatedAttributes.updated_at).toBeDefined();
|
||||||
|
expect(args.updatedAttributes.updated_by).toEqual({
|
||||||
|
email: 'damaged_raccoon@elastic.co',
|
||||||
|
full_name: 'Damaged Raccoon',
|
||||||
|
profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
|
||||||
|
username: 'damaged_raccoon',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteAll', () => {
|
describe('deleteAll', () => {
|
||||||
const clientArgs = createCasesClientMockArgs();
|
const clientArgs = createCasesClientMockArgs();
|
||||||
|
|
||||||
const getAllCaseCommentsResponse = {
|
const getAllCaseCommentsResponse = {
|
||||||
saved_objects: mockCaseComments.map((so) => ({ ...so, score: 0 })),
|
saved_objects: mockCaseComments.map((so) => ({ ...so, score: 0 })),
|
||||||
total: mockCaseComments.length,
|
total: mockCaseComments.length,
|
||||||
|
@ -51,13 +100,36 @@ describe('delete', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
clientArgs.services.attachmentService.getter.get.mockResolvedValue(mockCaseComments[0]);
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
|
||||||
describe('Alerts', () => {
|
|
||||||
clientArgs.services.caseService.getAllCaseComments.mockResolvedValue(
|
clientArgs.services.caseService.getAllCaseComments.mockResolvedValue(
|
||||||
getAllCaseCommentsResponse
|
getAllCaseCommentsResponse
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refreshes when deleting', async () => {
|
||||||
|
await deleteAll({ caseID: 'mock-id-1' }, clientArgs);
|
||||||
|
|
||||||
|
expect(clientArgs.services.attachmentService.bulkDelete).toHaveBeenCalledWith({
|
||||||
|
attachmentIds: [
|
||||||
|
'mock-comment-1',
|
||||||
|
'mock-comment-2',
|
||||||
|
'mock-comment-3',
|
||||||
|
'mock-comment-4',
|
||||||
|
'mock-comment-5',
|
||||||
|
'mock-comment-6',
|
||||||
|
'mock-comment-7',
|
||||||
|
],
|
||||||
|
refresh: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Alerts', () => {
|
||||||
it('delete alerts correctly', async () => {
|
it('delete alerts correctly', async () => {
|
||||||
await deleteAll({ caseID: 'mock-id-4' }, clientArgs);
|
await deleteAll({ caseID: 'mock-id-4' }, clientArgs);
|
||||||
|
|
||||||
|
@ -82,5 +154,35 @@ describe('delete', () => {
|
||||||
expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).not.toHaveBeenCalledWith();
|
expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).not.toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Attachment stats', () => {
|
||||||
|
it('updates attachment stats correctly', async () => {
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 0,
|
||||||
|
alerts: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteAll({ caseID: 'mock-id-1' }, clientArgs);
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(0);
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(0);
|
||||||
|
expect(args.updatedAttributes.updated_at).toBeDefined();
|
||||||
|
expect(args.updatedAttributes.updated_by).toEqual({
|
||||||
|
email: 'damaged_raccoon@elastic.co',
|
||||||
|
full_name: 'Damaged Raccoon',
|
||||||
|
profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
|
||||||
|
username: 'damaged_raccoon',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,7 +52,14 @@ export async function deleteAll(
|
||||||
|
|
||||||
await attachmentService.bulkDelete({
|
await attachmentService.bulkDelete({
|
||||||
attachmentIds: comments.saved_objects.map((so) => so.id),
|
attachmentIds: comments.saved_objects.map((so) => so.id),
|
||||||
refresh: false,
|
refresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateCaseAttachmentStats({
|
||||||
|
caseService: clientArgs.services.caseService,
|
||||||
|
attachmentService: clientArgs.services.attachmentService,
|
||||||
|
caseId: caseID,
|
||||||
|
user,
|
||||||
});
|
});
|
||||||
|
|
||||||
await userActionService.creator.bulkCreateAttachmentDeletion({
|
await userActionService.creator.bulkCreateAttachmentDeletion({
|
||||||
|
@ -115,7 +122,14 @@ export async function deleteComment(
|
||||||
|
|
||||||
await attachmentService.bulkDelete({
|
await attachmentService.bulkDelete({
|
||||||
attachmentIds: [attachmentID],
|
attachmentIds: [attachmentID],
|
||||||
refresh: false,
|
refresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateCaseAttachmentStats({
|
||||||
|
caseService: clientArgs.services.caseService,
|
||||||
|
attachmentService: clientArgs.services.attachmentService,
|
||||||
|
caseId: caseID,
|
||||||
|
user,
|
||||||
});
|
});
|
||||||
|
|
||||||
// we only want to store the fields related to the original request of the attachment, not fields like
|
// we only want to store the fields related to the original request of the attachment, not fields like
|
||||||
|
@ -163,3 +177,42 @@ const handleAlerts = async ({ alertsService, attachments, caseId }: HandleAlerts
|
||||||
const alerts = getAlertInfoFromComments(alertAttachments);
|
const alerts = getAlertInfoFromComments(alertAttachments);
|
||||||
await alertsService.removeCaseIdFromAlerts({ alerts, caseId });
|
await alertsService.removeCaseIdFromAlerts({ alerts, caseId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface UpdateCaseAttachmentStats {
|
||||||
|
caseService: CasesClientArgs['services']['caseService'];
|
||||||
|
attachmentService: CasesClientArgs['services']['attachmentService'];
|
||||||
|
caseId: string;
|
||||||
|
user: CasesClientArgs['user'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCaseAttachmentStats = async ({
|
||||||
|
caseService,
|
||||||
|
attachmentService,
|
||||||
|
caseId,
|
||||||
|
user,
|
||||||
|
}: UpdateCaseAttachmentStats) => {
|
||||||
|
const originalCase = await caseService.getCase({
|
||||||
|
id: caseId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const date = new Date().toISOString();
|
||||||
|
|
||||||
|
const attachmentStats = await attachmentService.getter.getCaseAttatchmentStats({
|
||||||
|
caseIds: [caseId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalComments = attachmentStats.get(caseId)?.userComments ?? 0;
|
||||||
|
const totalAlerts = attachmentStats.get(caseId)?.alerts ?? 0;
|
||||||
|
|
||||||
|
await caseService.patchCase({
|
||||||
|
originalCase,
|
||||||
|
caseId,
|
||||||
|
updatedAttributes: {
|
||||||
|
updated_at: date,
|
||||||
|
updated_by: user,
|
||||||
|
total_comments: totalComments,
|
||||||
|
total_alerts: totalAlerts,
|
||||||
|
},
|
||||||
|
refresh: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -21,7 +21,9 @@ describe('bulkGet', () => {
|
||||||
unauthorized: [],
|
unauthorized: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
|
@ -49,7 +49,7 @@ export const bulkGet = async (
|
||||||
operation: Operations.bulkGetCases,
|
operation: Operations.bulkGetCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const commentTotals = await attachmentService.getter.getCaseCommentStats({
|
const commentTotals = await attachmentService.getter.getCaseAttatchmentStats({
|
||||||
caseIds: authorizedCases.map((theCase) => theCase.id),
|
caseIds: authorizedCases.map((theCase) => theCase.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,9 @@ describe('update', () => {
|
||||||
saved_objects: [{ ...mockCases[0], attributes: { assignees: cases.cases[0].assignees } }],
|
saved_objects: [{ ...mockCases[0], attributes: { assignees: cases.cases[0].assignees } }],
|
||||||
});
|
});
|
||||||
|
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('notifies an assignee', async () => {
|
it('notifies an assignee', async () => {
|
||||||
|
@ -437,7 +439,9 @@ describe('update', () => {
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`does not throw error when category is non empty string less than ${MAX_CATEGORY_LENGTH} characters`, async () => {
|
it(`does not throw error when category is non empty string less than ${MAX_CATEGORY_LENGTH} characters`, async () => {
|
||||||
|
@ -571,7 +575,9 @@ describe('update', () => {
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`does not throw error when title is non empty string less than ${MAX_TITLE_LENGTH} characters`, async () => {
|
it(`does not throw error when title is non empty string less than ${MAX_TITLE_LENGTH} characters`, async () => {
|
||||||
|
@ -706,7 +712,9 @@ describe('update', () => {
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`does not throw error when description is non empty string less than ${MAX_DESCRIPTION_LENGTH} characters`, async () => {
|
it(`does not throw error when description is non empty string less than ${MAX_DESCRIPTION_LENGTH} characters`, async () => {
|
||||||
|
@ -848,7 +856,7 @@ describe('update', () => {
|
||||||
const caseCommentsStats = new Map();
|
const caseCommentsStats = new Map();
|
||||||
caseCommentsStats.set(mockCases[0].id, { userComments: 1, alerts: 2 });
|
caseCommentsStats.set(mockCases[0].id, { userComments: 1, alerts: 2 });
|
||||||
caseCommentsStats.set(mockCases[1].id, { userComments: 3, alerts: 4 });
|
caseCommentsStats.set(mockCases[1].id, { userComments: 3, alerts: 4 });
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
caseCommentsStats
|
caseCommentsStats
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -972,7 +980,9 @@ describe('update', () => {
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(clientArgs.services.attachmentService.getter.getCaseCommentStats).toHaveBeenCalledWith(
|
expect(
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
caseIds: [mockCases[0].id, mockCases[1].id],
|
caseIds: [mockCases[0].id, mockCases[1].id],
|
||||||
})
|
})
|
||||||
|
@ -992,7 +1002,9 @@ describe('update', () => {
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw error when tags array is empty', async () => {
|
it('does not throw error when tags array is empty', async () => {
|
||||||
|
@ -1197,7 +1209,9 @@ describe('update', () => {
|
||||||
customFields: defaultCustomFieldsConfiguration,
|
customFields: defaultCustomFieldsConfiguration,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update customFields', async () => {
|
it('can update customFields', async () => {
|
||||||
|
@ -1587,7 +1601,7 @@ describe('update', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
clientArgsMock.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(
|
clientArgsMock.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
new Map()
|
new Map()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1807,7 +1821,7 @@ describe('update', () => {
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
new Map()
|
new Map()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1915,7 +1929,9 @@ describe('update', () => {
|
||||||
saved_objects: mockCases,
|
saved_objects: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculates metrics correctly', async () => {
|
it('calculates metrics correctly', async () => {
|
||||||
|
|
|
@ -516,7 +516,7 @@ export const bulkUpdate = async (
|
||||||
alertsService,
|
alertsService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const commentsMap = await attachmentService.getter.getCaseCommentStats({
|
const commentsMap = await attachmentService.getter.getCaseAttatchmentStats({
|
||||||
caseIds,
|
caseIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ export const getCasesByAlertID = async (
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const commentStats = await attachmentService.getter.getCaseCommentStats({
|
const commentStats = await attachmentService.getter.getCaseAttatchmentStats({
|
||||||
caseIds,
|
caseIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ export const get = async (
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!includeComments) {
|
if (!includeComments) {
|
||||||
const commentStats = await attachmentService.getter.getCaseCommentStats({
|
const commentStats = await attachmentService.getter.getCaseAttatchmentStats({
|
||||||
caseIds: [theCase.id],
|
caseIds: [theCase.id],
|
||||||
});
|
});
|
||||||
return decodeOrThrow(CaseRt)(
|
return decodeOrThrow(CaseRt)(
|
||||||
|
|
|
@ -33,6 +33,7 @@ describe('CaseCommentModel', () => {
|
||||||
clientArgs.services.attachmentService.bulkCreate.mockResolvedValue({
|
clientArgs.services.attachmentService.bulkCreate.mockResolvedValue({
|
||||||
saved_objects: mockCaseComments,
|
saved_objects: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(new Map());
|
||||||
|
|
||||||
const alertIdsAttachedToCase = new Set(['test-id-4']);
|
const alertIdsAttachedToCase = new Set(['test-id-4']);
|
||||||
clientArgs.services.attachmentService.getter.getAllAlertIds.mockResolvedValue(
|
clientArgs.services.attachmentService.getter.getAllAlertIds.mockResolvedValue(
|
||||||
|
@ -85,7 +86,7 @@ describe('CaseCommentModel', () => {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -136,7 +137,7 @@ describe('CaseCommentModel', () => {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -189,7 +190,7 @@ describe('CaseCommentModel', () => {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -244,7 +245,7 @@ describe('CaseCommentModel', () => {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -291,6 +292,62 @@ describe('CaseCommentModel', () => {
|
||||||
expect(args.version).toBeUndefined();
|
expect(args.version).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates the total number of comments correctly', async () => {
|
||||||
|
// user comment
|
||||||
|
clientArgs.services.attachmentService.create.mockResolvedValue(mockCaseComments[0]);
|
||||||
|
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 2,
|
||||||
|
alerts: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await model.createComment({
|
||||||
|
id: 'comment-1',
|
||||||
|
commentReq: comment,
|
||||||
|
createdDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(2);
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the total number of alerts correctly', async () => {
|
||||||
|
// alert comment
|
||||||
|
clientArgs.services.attachmentService.create.mockResolvedValue(mockCaseComments[3]);
|
||||||
|
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 1,
|
||||||
|
alerts: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await model.createComment({
|
||||||
|
id: 'comment-1',
|
||||||
|
commentReq: alertComment,
|
||||||
|
createdDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(3);
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
clientArgs.services.attachmentService.countPersistableStateAndExternalReferenceAttachments.mockResolvedValue(
|
clientArgs.services.attachmentService.countPersistableStateAndExternalReferenceAttachments.mockResolvedValue(
|
||||||
MAX_PERSISTABLE_STATE_AND_EXTERNAL_REFERENCES
|
MAX_PERSISTABLE_STATE_AND_EXTERNAL_REFERENCES
|
||||||
|
@ -611,6 +668,58 @@ describe('CaseCommentModel', () => {
|
||||||
expect(args.version).toBeUndefined();
|
expect(args.version).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates the total number of comments and alerts correctly', async () => {
|
||||||
|
clientArgs.services.attachmentService.bulkCreate.mockResolvedValue({
|
||||||
|
saved_objects: mockCaseComments,
|
||||||
|
});
|
||||||
|
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 4,
|
||||||
|
alerts: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await model.bulkCreate({
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
id: 'mock-comment-1',
|
||||||
|
...comment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-comment-2',
|
||||||
|
...comment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-comment-3',
|
||||||
|
...comment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-comment-4',
|
||||||
|
...alertComment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-comment-5',
|
||||||
|
...alertComment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-comment-6',
|
||||||
|
...alertComment,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(5);
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
clientArgs.services.attachmentService.countPersistableStateAndExternalReferenceAttachments.mockResolvedValue(
|
clientArgs.services.attachmentService.countPersistableStateAndExternalReferenceAttachments.mockResolvedValue(
|
||||||
MAX_PERSISTABLE_STATE_AND_EXTERNAL_REFERENCES
|
MAX_PERSISTABLE_STATE_AND_EXTERNAL_REFERENCES
|
||||||
|
@ -670,5 +779,71 @@ describe('CaseCommentModel', () => {
|
||||||
|
|
||||||
expect(args.version).toBeUndefined();
|
expect(args.version).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not increase the counters when updating a user comment', async () => {
|
||||||
|
// the case has 1 user comment and 2 alert comments
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 1,
|
||||||
|
alerts: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await model.updateComment({
|
||||||
|
updateRequest: {
|
||||||
|
id: 'comment-id',
|
||||||
|
version: 'comment-version',
|
||||||
|
type: AttachmentType.user,
|
||||||
|
comment: 'my updated comment',
|
||||||
|
owner: SECURITY_SOLUTION_OWNER,
|
||||||
|
},
|
||||||
|
updatedAt: createdDate,
|
||||||
|
owner: SECURITY_SOLUTION_OWNER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(2);
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increase the counters when updating an alert', async () => {
|
||||||
|
// the case has 1 user comment and 2 alert comments
|
||||||
|
clientArgs.services.attachmentService.getter.getCaseAttatchmentStats.mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'mock-id-1',
|
||||||
|
{
|
||||||
|
userComments: 1,
|
||||||
|
alerts: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await model.updateComment({
|
||||||
|
updateRequest: {
|
||||||
|
id: 'comment-id',
|
||||||
|
version: 'comment-version',
|
||||||
|
type: AttachmentType.alert,
|
||||||
|
alertId: ['alert-id-1'],
|
||||||
|
index: ['alert-index-1'],
|
||||||
|
rule: { id: 'rule-id-1', name: 'rule-name-1' },
|
||||||
|
owner: SECURITY_SOLUTION_OWNER,
|
||||||
|
},
|
||||||
|
updatedAt: createdDate,
|
||||||
|
owner: SECURITY_SOLUTION_OWNER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = clientArgs.services.caseService.patchCase.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(args.updatedAttributes.total_alerts).toEqual(2);
|
||||||
|
expect(args.updatedAttributes.total_comments).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -129,7 +129,7 @@ export class CaseCommentModel {
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
}),
|
}),
|
||||||
this.partialUpdateCaseUserAndDateSkipRefresh(updatedAt),
|
this.partialUpdateCaseWithAttachmentDataSkipRefresh({ date: updatedAt }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await commentableCase.createUpdateCommentUserAction(comment, updateRequest, owner);
|
await commentableCase.createUpdateCommentUserAction(comment, updateRequest, owner);
|
||||||
|
@ -144,21 +144,35 @@ export class CaseCommentModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async partialUpdateCaseUserAndDateSkipRefresh(date: string) {
|
private async partialUpdateCaseWithAttachmentDataSkipRefresh({
|
||||||
return this.partialUpdateCaseUserAndDate(date, false);
|
date,
|
||||||
|
}: {
|
||||||
|
date: string;
|
||||||
|
}): Promise<CaseCommentModel> {
|
||||||
|
return this.partialUpdateCaseWithAttachmentData({
|
||||||
|
date,
|
||||||
|
refresh: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async partialUpdateCaseUserAndDate(
|
private async partialUpdateCaseWithAttachmentData({
|
||||||
date: string,
|
date,
|
||||||
refresh: RefreshSetting
|
refresh,
|
||||||
): Promise<CaseCommentModel> {
|
}: {
|
||||||
|
date: string;
|
||||||
|
refresh: RefreshSetting;
|
||||||
|
}): Promise<CaseCommentModel> {
|
||||||
try {
|
try {
|
||||||
|
const { totalComments, totalAlerts } = await this.getAttachmentStats();
|
||||||
|
|
||||||
const updatedCase = await this.params.services.caseService.patchCase({
|
const updatedCase = await this.params.services.caseService.patchCase({
|
||||||
originalCase: this.caseInfo,
|
originalCase: this.caseInfo,
|
||||||
caseId: this.caseInfo.id,
|
caseId: this.caseInfo.id,
|
||||||
updatedAttributes: {
|
updatedAttributes: {
|
||||||
updated_at: date,
|
updated_at: date,
|
||||||
updated_by: { ...this.params.user },
|
updated_by: { ...this.params.user },
|
||||||
|
total_comments: totalComments,
|
||||||
|
total_alerts: totalAlerts,
|
||||||
},
|
},
|
||||||
refresh,
|
refresh,
|
||||||
});
|
});
|
||||||
|
@ -180,6 +194,21 @@ export class CaseCommentModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getAttachmentStats() {
|
||||||
|
const attachmentStats =
|
||||||
|
await this.params.services.attachmentService.getter.getCaseAttatchmentStats({
|
||||||
|
caseIds: [this.caseInfo.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalComments = attachmentStats.get(this.caseInfo.id)?.userComments ?? 0;
|
||||||
|
const totalAlerts = attachmentStats.get(this.caseInfo.id)?.alerts ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalComments,
|
||||||
|
totalAlerts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private newObjectWithInfo(caseInfo: CaseSavedObjectTransformed): CaseCommentModel {
|
private newObjectWithInfo(caseInfo: CaseSavedObjectTransformed): CaseCommentModel {
|
||||||
return new CaseCommentModel(caseInfo, this.params);
|
return new CaseCommentModel(caseInfo, this.params);
|
||||||
}
|
}
|
||||||
|
@ -230,19 +259,20 @@ export class CaseCommentModel {
|
||||||
|
|
||||||
const references = [...this.buildRefsToCase(), ...this.getCommentReferences(attachment)];
|
const references = [...this.buildRefsToCase(), ...this.getCommentReferences(attachment)];
|
||||||
|
|
||||||
const [comment, commentableCase] = await Promise.all([
|
const comment = await this.params.services.attachmentService.create({
|
||||||
this.params.services.attachmentService.create({
|
attributes: transformNewComment({
|
||||||
attributes: transformNewComment({
|
createdDate,
|
||||||
createdDate,
|
...attachment,
|
||||||
...attachment,
|
...this.params.user,
|
||||||
...this.params.user,
|
|
||||||
}),
|
|
||||||
references,
|
|
||||||
id,
|
|
||||||
refresh: false,
|
|
||||||
}),
|
}),
|
||||||
this.partialUpdateCaseUserAndDateSkipRefresh(createdDate),
|
references,
|
||||||
]);
|
id,
|
||||||
|
refresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentableCase = await this.partialUpdateCaseWithAttachmentDataSkipRefresh({
|
||||||
|
date: createdDate,
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
commentableCase.handleAlertComments([attachment]),
|
commentableCase.handleAlertComments([attachment]),
|
||||||
|
@ -486,23 +516,24 @@ export class CaseCommentModel {
|
||||||
|
|
||||||
const caseReference = this.buildRefsToCase();
|
const caseReference = this.buildRefsToCase();
|
||||||
|
|
||||||
const [newlyCreatedAttachments, commentableCase] = await Promise.all([
|
const newlyCreatedAttachments = await this.params.services.attachmentService.bulkCreate({
|
||||||
this.params.services.attachmentService.bulkCreate({
|
attachments: attachmentWithoutDuplicateAlerts.map(({ id, ...attachment }) => {
|
||||||
attachments: attachmentWithoutDuplicateAlerts.map(({ id, ...attachment }) => {
|
return {
|
||||||
return {
|
attributes: transformNewComment({
|
||||||
attributes: transformNewComment({
|
createdDate: new Date().toISOString(),
|
||||||
createdDate: new Date().toISOString(),
|
...attachment,
|
||||||
...attachment,
|
...this.params.user,
|
||||||
...this.params.user,
|
}),
|
||||||
}),
|
references: [...caseReference, ...this.getCommentReferences(attachment)],
|
||||||
references: [...caseReference, ...this.getCommentReferences(attachment)],
|
id,
|
||||||
id,
|
};
|
||||||
};
|
|
||||||
}),
|
|
||||||
refresh: false,
|
|
||||||
}),
|
}),
|
||||||
this.partialUpdateCaseUserAndDateSkipRefresh(new Date().toISOString()),
|
refresh: true,
|
||||||
]);
|
});
|
||||||
|
|
||||||
|
const commentableCase = await this.partialUpdateCaseWithAttachmentDataSkipRefresh({
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
const savedObjectsWithoutErrors = newlyCreatedAttachments.saved_objects.filter(
|
const savedObjectsWithoutErrors = newlyCreatedAttachments.saved_objects.filter(
|
||||||
(attachment) => attachment.error == null
|
(attachment) => attachment.error == null
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { number } from 'io-ts';
|
||||||
import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
|
import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
|
||||||
import {
|
import {
|
||||||
CaseTransformedAttributesRt,
|
CaseTransformedAttributesRt,
|
||||||
|
@ -52,8 +53,9 @@ describe('case types', () => {
|
||||||
assignees: [],
|
assignees: [],
|
||||||
observables: [],
|
observables: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce(
|
const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce(
|
||||||
(acc, type) => ({ ...acc, ...type.type.props }),
|
(acc, type) => ({ ...acc, ...type.type.props, total_comments: number, total_alerts: number }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -78,6 +80,18 @@ describe('case types', () => {
|
||||||
// @ts-expect-error: the check above ensures that right exists
|
// @ts-expect-error: the check above ensures that right exists
|
||||||
expect(decodedRes.right).toEqual({ description: 'test' });
|
expect(decodedRes.right).toEqual({ description: 'test' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not remove the attachment stats', () => {
|
||||||
|
const decodedRes = type.decode({
|
||||||
|
description: 'test',
|
||||||
|
total_alerts: 0,
|
||||||
|
total_comments: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decodedRes._tag).toEqual('Right');
|
||||||
|
// @ts-expect-error: the check above ensures that right exists
|
||||||
|
expect(decodedRes.right).toEqual({ description: 'test', total_alerts: 0, total_comments: 0 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('OwnerRt', () => {
|
describe('OwnerRt', () => {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||||
import type { Type } from 'io-ts';
|
import type { Type } from 'io-ts';
|
||||||
import { exact, partial, strict, string } from 'io-ts';
|
import { exact, partial, strict, string, number } from 'io-ts';
|
||||||
import type { CaseAttributes, Observable } from '../../../common/types/domain';
|
import type { CaseAttributes, Observable } from '../../../common/types/domain';
|
||||||
import { CaseAttributesRt } from '../../../common/types/domain';
|
import { CaseAttributesRt } from '../../../common/types/domain';
|
||||||
import type { ConnectorPersisted } from './connectors';
|
import type { ConnectorPersisted } from './connectors';
|
||||||
|
@ -64,16 +64,28 @@ type CasePersistedCustomFields = Array<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type CaseTransformedAttributes = CaseAttributes;
|
export type CaseTransformedAttributes = CaseAttributes;
|
||||||
|
export type CaseTransformedAttributesWithAttachmentStats = CaseAttributes & {
|
||||||
|
total_comments: number;
|
||||||
|
total_alerts: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const CaseTransformedAttributesRt = CaseAttributesRt;
|
export const CaseTransformedAttributesRt = CaseAttributesRt;
|
||||||
|
|
||||||
export const getPartialCaseTransformedAttributesRt = (): Type<Partial<CaseAttributes>> => {
|
export const getPartialCaseTransformedAttributesRt = (): Type<
|
||||||
|
Partial<CaseTransformedAttributesWithAttachmentStats>
|
||||||
|
> => {
|
||||||
const caseTransformedAttributesProps = CaseAttributesRt.types.reduce(
|
const caseTransformedAttributesProps = CaseAttributesRt.types.reduce(
|
||||||
(acc, type) => Object.assign(acc, type.type.props),
|
(acc, type) => Object.assign(acc, type.type.props),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
return exact(partial({ ...caseTransformedAttributesProps }));
|
return exact(
|
||||||
|
/**
|
||||||
|
* We add the `total_comments` and `total_alerts` properties to allow the
|
||||||
|
* attachments stats to be updated.
|
||||||
|
*/
|
||||||
|
partial({ ...caseTransformedAttributesProps, total_comments: number, total_alerts: number })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CaseSavedObject = SavedObject<CasePersistedAttributes>;
|
export type CaseSavedObject = SavedObject<CasePersistedAttributes>;
|
||||||
|
|
|
@ -249,7 +249,7 @@ export class AttachmentGetter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCaseCommentStats({
|
public async getCaseAttatchmentStats({
|
||||||
caseIds,
|
caseIds,
|
||||||
}: {
|
}: {
|
||||||
caseIds: string[];
|
caseIds: string[];
|
||||||
|
|
|
@ -287,12 +287,16 @@ describe('CasesService', () => {
|
||||||
|
|
||||||
await service.patchCase({
|
await service.patchCase({
|
||||||
caseId: '1',
|
caseId: '1',
|
||||||
updatedAttributes: createCasePostParams({
|
updatedAttributes: {
|
||||||
connector: createJiraConnector(),
|
...createCasePostParams({
|
||||||
externalService: createExternalService(),
|
connector: createJiraConnector(),
|
||||||
severity: CaseSeverity.CRITICAL,
|
externalService: createExternalService(),
|
||||||
status: CaseStatuses['in-progress'],
|
severity: CaseSeverity.CRITICAL,
|
||||||
}),
|
status: CaseStatuses['in-progress'],
|
||||||
|
}),
|
||||||
|
total_alerts: 10,
|
||||||
|
total_comments: 5,
|
||||||
|
},
|
||||||
originalCase: {} as CaseSavedObjectTransformed,
|
originalCase: {} as CaseSavedObjectTransformed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -327,6 +331,8 @@ describe('CasesService', () => {
|
||||||
"defacement",
|
"defacement",
|
||||||
],
|
],
|
||||||
"title": "Super Bad Security Issue",
|
"title": "Super Bad Security Issue",
|
||||||
|
"total_alerts": 10,
|
||||||
|
"total_comments": 5,
|
||||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||||
"updated_by": Object {
|
"updated_by": Object {
|
||||||
"email": "testemail@elastic.co",
|
"email": "testemail@elastic.co",
|
||||||
|
@ -661,6 +667,33 @@ describe('CasesService', () => {
|
||||||
expect(patchAttributes.status).toEqual(expectedStatus);
|
expect(patchAttributes.status).toEqual(expectedStatus);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('updates the total attachment stats', async () => {
|
||||||
|
unsecuredSavedObjectsClient.update.mockResolvedValue(
|
||||||
|
{} as SavedObjectsUpdateResponse<CasePersistedAttributes>
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.patchCase({
|
||||||
|
caseId: '1',
|
||||||
|
updatedAttributes: {
|
||||||
|
...createCasePostParams({
|
||||||
|
connector: createJiraConnector(),
|
||||||
|
externalService: createExternalService(),
|
||||||
|
severity: CaseSeverity.CRITICAL,
|
||||||
|
status: CaseStatuses['in-progress'],
|
||||||
|
}),
|
||||||
|
total_alerts: 10,
|
||||||
|
total_comments: 5,
|
||||||
|
},
|
||||||
|
originalCase: {} as CaseSavedObjectTransformed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchAttributes = unsecuredSavedObjectsClient.update.mock
|
||||||
|
.calls[0][2] as CasePersistedAttributes;
|
||||||
|
|
||||||
|
expect(patchAttributes.total_alerts).toEqual(10);
|
||||||
|
expect(patchAttributes.total_comments).toEqual(5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('bulkPatch', () => {
|
describe('bulkPatch', () => {
|
||||||
|
@ -765,6 +798,39 @@ describe('CasesService', () => {
|
||||||
expect(patchResults[1].attributes.status).toEqual(CasePersistedStatus.IN_PROGRESS);
|
expect(patchResults[1].attributes.status).toEqual(CasePersistedStatus.IN_PROGRESS);
|
||||||
expect(patchResults[2].attributes.status).toEqual(CasePersistedStatus.CLOSED);
|
expect(patchResults[2].attributes.status).toEqual(CasePersistedStatus.CLOSED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates the total attachment stats', async () => {
|
||||||
|
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
|
||||||
|
saved_objects: [
|
||||||
|
createCaseSavedObjectResponse({ caseId: '1' }),
|
||||||
|
createCaseSavedObjectResponse({ caseId: '2' }),
|
||||||
|
createCaseSavedObjectResponse({ caseId: '3' }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.patchCases({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
caseId: '1',
|
||||||
|
updatedAttributes: {
|
||||||
|
...createCasePostParams({
|
||||||
|
connector: getNoneCaseConnector(),
|
||||||
|
status: CaseStatuses.open,
|
||||||
|
}),
|
||||||
|
total_alerts: 10,
|
||||||
|
total_comments: 5,
|
||||||
|
},
|
||||||
|
originalCase: {} as CaseSavedObjectTransformed,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchResults = unsecuredSavedObjectsClient.bulkUpdate.mock
|
||||||
|
.calls[0][0] as unknown as Array<SavedObject<CasePersistedAttributes>>;
|
||||||
|
|
||||||
|
expect(patchResults[0].attributes.total_alerts).toEqual(10);
|
||||||
|
expect(patchResults[0].attributes.total_comments).toEqual(5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createCase', () => {
|
describe('createCase', () => {
|
||||||
|
@ -852,8 +918,8 @@ describe('CasesService', () => {
|
||||||
"defacement",
|
"defacement",
|
||||||
],
|
],
|
||||||
"title": "Super Bad Security Issue",
|
"title": "Super Bad Security Issue",
|
||||||
"total_alerts": -1,
|
"total_alerts": 0,
|
||||||
"total_comments": -1,
|
"total_comments": 0,
|
||||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||||
"updated_by": Object {
|
"updated_by": Object {
|
||||||
"email": "testemail@elastic.co",
|
"email": "testemail@elastic.co",
|
||||||
|
@ -895,8 +961,8 @@ describe('CasesService', () => {
|
||||||
const postAttributes = unsecuredSavedObjectsClient.create.mock
|
const postAttributes = unsecuredSavedObjectsClient.create.mock
|
||||||
.calls[0][1] as CasePersistedAttributes;
|
.calls[0][1] as CasePersistedAttributes;
|
||||||
|
|
||||||
expect(postAttributes.total_alerts).toEqual(-1);
|
expect(postAttributes.total_alerts).toEqual(0);
|
||||||
expect(postAttributes.total_comments).toEqual(-1);
|
expect(postAttributes.total_comments).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves the connector.id and connector_id to the references', async () => {
|
it('moves the connector.id and connector_id to the references', async () => {
|
||||||
|
@ -1073,8 +1139,8 @@ describe('CasesService', () => {
|
||||||
"defacement",
|
"defacement",
|
||||||
],
|
],
|
||||||
"title": "Super Bad Security Issue",
|
"title": "Super Bad Security Issue",
|
||||||
"total_alerts": -1,
|
"total_alerts": 0,
|
||||||
"total_comments": -1,
|
"total_comments": 0,
|
||||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||||
"updated_by": Object {
|
"updated_by": Object {
|
||||||
"email": "testemail@elastic.co",
|
"email": "testemail@elastic.co",
|
||||||
|
@ -1112,8 +1178,8 @@ describe('CasesService', () => {
|
||||||
const postAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0]
|
const postAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0]
|
||||||
.attributes as CasePersistedAttributes;
|
.attributes as CasePersistedAttributes;
|
||||||
|
|
||||||
expect(postAttributes.total_alerts).toEqual(-1);
|
expect(postAttributes.total_alerts).toEqual(0);
|
||||||
expect(postAttributes.total_comments).toEqual(-1);
|
expect(postAttributes.total_comments).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2200,7 +2266,10 @@ describe('CasesService', () => {
|
||||||
* - external_service
|
* - external_service
|
||||||
* - category
|
* - category
|
||||||
*
|
*
|
||||||
* Decode is not expected to throw an error as they are defined.
|
* The following fields can be undefined:
|
||||||
|
* - total_alerts
|
||||||
|
* - total_comments
|
||||||
|
* - incremental_id
|
||||||
*/
|
*/
|
||||||
const attributesToValidateIfMissing = omit(
|
const attributesToValidateIfMissing = omit(
|
||||||
caseTransformedAttributesProps,
|
caseTransformedAttributesProps,
|
||||||
|
@ -2212,6 +2281,8 @@ describe('CasesService', () => {
|
||||||
'customFields',
|
'customFields',
|
||||||
'observables',
|
'observables',
|
||||||
'incremental_id',
|
'incremental_id',
|
||||||
|
'total_alerts',
|
||||||
|
'total_comments',
|
||||||
'in_progress_at',
|
'in_progress_at',
|
||||||
'time_to_acknowledge',
|
'time_to_acknowledge',
|
||||||
'time_to_resolve',
|
'time_to_resolve',
|
||||||
|
|
|
@ -181,7 +181,7 @@ export class CasesService {
|
||||||
return accMap;
|
return accMap;
|
||||||
}, new Map<string, SavedObjectsFindResult<CaseTransformedAttributes>>());
|
}, new Map<string, SavedObjectsFindResult<CaseTransformedAttributes>>());
|
||||||
|
|
||||||
const commentTotals = await this.attachmentService.getter.getCaseCommentStats({
|
const commentTotals = await this.attachmentService.getter.getCaseAttatchmentStats({
|
||||||
caseIds: Array.from(casesMap.keys()),
|
caseIds: Array.from(casesMap.keys()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -594,8 +594,8 @@ export class CasesService {
|
||||||
const decodedAttributes = decodeOrThrow(CaseTransformedAttributesRt)(attributes);
|
const decodedAttributes = decodeOrThrow(CaseTransformedAttributesRt)(attributes);
|
||||||
const transformedAttributes = transformAttributesToESModel(decodedAttributes);
|
const transformedAttributes = transformAttributesToESModel(decodedAttributes);
|
||||||
|
|
||||||
transformedAttributes.attributes.total_alerts = -1;
|
transformedAttributes.attributes.total_alerts = 0;
|
||||||
transformedAttributes.attributes.total_comments = -1;
|
transformedAttributes.attributes.total_comments = 0;
|
||||||
|
|
||||||
const createdCase = await this.unsecuredSavedObjectsClient.create<CasePersistedAttributes>(
|
const createdCase = await this.unsecuredSavedObjectsClient.create<CasePersistedAttributes>(
|
||||||
CASE_SAVED_OBJECT,
|
CASE_SAVED_OBJECT,
|
||||||
|
@ -626,8 +626,8 @@ export class CasesService {
|
||||||
const { attributes: transformedAttributes, referenceHandler } =
|
const { attributes: transformedAttributes, referenceHandler } =
|
||||||
transformAttributesToESModel(decodedAttributes);
|
transformAttributesToESModel(decodedAttributes);
|
||||||
|
|
||||||
transformedAttributes.total_alerts = -1;
|
transformedAttributes.total_alerts = 0;
|
||||||
transformedAttributes.total_comments = -1;
|
transformedAttributes.total_comments = 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: CASE_SAVED_OBJECT,
|
type: CASE_SAVED_OBJECT,
|
||||||
|
|
|
@ -243,6 +243,28 @@ describe('case transforms', () => {
|
||||||
expect(transformedAttributes.attributes.status).toBe(expectedStatusValue);
|
expect(transformedAttributes.attributes.status).toBe(expectedStatusValue);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('does not return the total alerts', () => {
|
||||||
|
expect(
|
||||||
|
transformUpdateResponseToExternalModel({
|
||||||
|
type: 'a',
|
||||||
|
id: '1',
|
||||||
|
attributes: { total_alerts: 2 },
|
||||||
|
references: undefined,
|
||||||
|
}).attributes
|
||||||
|
).not.toHaveProperty('total_alerts');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return the total comments', () => {
|
||||||
|
expect(
|
||||||
|
transformUpdateResponseToExternalModel({
|
||||||
|
type: 'a',
|
||||||
|
id: '1',
|
||||||
|
attributes: { total_comments: 2 },
|
||||||
|
references: undefined,
|
||||||
|
}).attributes
|
||||||
|
).not.toHaveProperty('total_comments');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transformAttributesToESModel', () => {
|
describe('transformAttributesToESModel', () => {
|
||||||
|
@ -438,6 +460,22 @@ describe('case transforms', () => {
|
||||||
'incremental_id'
|
'incremental_id'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not remove the total alerts', () => {
|
||||||
|
expect(transformAttributesToESModel({ total_alerts: 10 }).attributes).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"total_alerts": 10,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not remove the total comments', () => {
|
||||||
|
expect(transformAttributesToESModel({ total_comments: 5 }).attributes).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"total_comments": 5,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transformSavedObjectToExternalModel', () => {
|
describe('transformSavedObjectToExternalModel', () => {
|
||||||
|
@ -656,5 +694,31 @@ describe('case transforms', () => {
|
||||||
transformSavedObjectToExternalModel(CaseSOResponseWithObservables).attributes.incremental_id
|
transformSavedObjectToExternalModel(CaseSOResponseWithObservables).attributes.incremental_id
|
||||||
).not.toBeDefined();
|
).not.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not return the total comments', () => {
|
||||||
|
const resWithTotalComments = createCaseSavedObjectResponse({
|
||||||
|
overrides: {
|
||||||
|
total_comments: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
// @ts-expect-error: total_comments is not defined in the attributes
|
||||||
|
transformSavedObjectToExternalModel(resWithTotalComments).attributes.total_comments
|
||||||
|
).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return the total alerts', () => {
|
||||||
|
const resWithTotalAlerts = createCaseSavedObjectResponse({
|
||||||
|
overrides: {
|
||||||
|
total_alerts: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
// @ts-expect-error: total_alerts is not defined in the attributes
|
||||||
|
transformSavedObjectToExternalModel(resWithTotalAlerts).attributes.total_alerts
|
||||||
|
).not.toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,11 @@ import {
|
||||||
transformESConnectorToExternalModel,
|
transformESConnectorToExternalModel,
|
||||||
} from '../transform';
|
} from '../transform';
|
||||||
import { ConnectorReferenceHandler } from '../connector_reference_handler';
|
import { ConnectorReferenceHandler } from '../connector_reference_handler';
|
||||||
import type { CasePersistedAttributes, CaseTransformedAttributes } from '../../common/types/case';
|
import type {
|
||||||
|
CasePersistedAttributes,
|
||||||
|
CaseTransformedAttributes,
|
||||||
|
CaseTransformedAttributesWithAttachmentStats,
|
||||||
|
} from '../../common/types/case';
|
||||||
import type { ExternalServicePersisted } from '../../common/types/external_service';
|
import type { ExternalServicePersisted } from '../../common/types/external_service';
|
||||||
|
|
||||||
export function transformUpdateResponseToExternalModel(
|
export function transformUpdateResponseToExternalModel(
|
||||||
|
@ -89,10 +93,14 @@ export function transformAttributesToESModel(caseAttributes: CaseTransformedAttr
|
||||||
attributes: CasePersistedAttributes;
|
attributes: CasePersistedAttributes;
|
||||||
referenceHandler: ConnectorReferenceHandler;
|
referenceHandler: ConnectorReferenceHandler;
|
||||||
};
|
};
|
||||||
export function transformAttributesToESModel(caseAttributes: Partial<CaseTransformedAttributes>): {
|
|
||||||
|
export function transformAttributesToESModel(
|
||||||
|
caseAttributes: Partial<CaseTransformedAttributesWithAttachmentStats>
|
||||||
|
): {
|
||||||
attributes: Partial<CasePersistedAttributes>;
|
attributes: Partial<CasePersistedAttributes>;
|
||||||
referenceHandler: ConnectorReferenceHandler;
|
referenceHandler: ConnectorReferenceHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function transformAttributesToESModel(caseAttributes: Partial<CaseTransformedAttributes>): {
|
export function transformAttributesToESModel(caseAttributes: Partial<CaseTransformedAttributes>): {
|
||||||
attributes: Partial<CasePersistedAttributes>;
|
attributes: Partial<CasePersistedAttributes>;
|
||||||
referenceHandler: ConnectorReferenceHandler;
|
referenceHandler: ConnectorReferenceHandler;
|
||||||
|
|
|
@ -26,6 +26,11 @@ export interface PushedArgs {
|
||||||
pushed_by: User;
|
pushed_by: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AttachmentStatsAttributes {
|
||||||
|
total_comments: number;
|
||||||
|
total_alerts: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetCaseArgs {
|
export interface GetCaseArgs {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +62,7 @@ export interface BulkCreateCasesArgs extends IndexRefresh {
|
||||||
|
|
||||||
export interface PatchCase extends IndexRefresh {
|
export interface PatchCase extends IndexRefresh {
|
||||||
caseId: string;
|
caseId: string;
|
||||||
updatedAttributes: Partial<CaseTransformedAttributes & PushedArgs>;
|
updatedAttributes: Partial<CaseTransformedAttributes & PushedArgs & AttachmentStatsAttributes>;
|
||||||
originalCase: CaseSavedObjectTransformed;
|
originalCase: CaseSavedObjectTransformed;
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ const createAttachmentGetterServiceMock = (): AttachmentGetterServiceMock => {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
bulkGet: jest.fn(),
|
bulkGet: jest.fn(),
|
||||||
getAllAlertsAttachToCase: jest.fn(),
|
getAllAlertsAttachToCase: jest.fn(),
|
||||||
getCaseCommentStats: jest.fn(),
|
getCaseAttatchmentStats: jest.fn(),
|
||||||
getAttachmentIdsForCases: jest.fn(),
|
getAttachmentIdsForCases: jest.fn(),
|
||||||
getFileAttachments: jest.fn(),
|
getFileAttachments: jest.fn(),
|
||||||
getAllAlertIds: jest.fn(),
|
getAllAlertIds: jest.fn(),
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
getSpaceUrlPrefix,
|
getSpaceUrlPrefix,
|
||||||
removeServerGeneratedPropertiesFromCase,
|
removeServerGeneratedPropertiesFromCase,
|
||||||
removeServerGeneratedPropertiesFromUserAction,
|
removeServerGeneratedPropertiesFromUserAction,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import type { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
import type { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||||
import {
|
import {
|
||||||
|
@ -232,6 +233,28 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('attachment stats', () => {
|
||||||
|
it('should set the attachment stats to zero', async () => {
|
||||||
|
await bulkCreateCases({
|
||||||
|
data: {
|
||||||
|
cases: [getPostCaseRequest(), getPostCaseRequest({ severity: CaseSeverity.MEDIUM })],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(2);
|
||||||
|
|
||||||
|
const firstCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
const secondCase = res.body.hits.hits[1]._source?.cases!;
|
||||||
|
|
||||||
|
expect(firstCase.total_alerts).to.eql(0);
|
||||||
|
expect(firstCase.total_comments).to.eql(0);
|
||||||
|
expect(secondCase.total_alerts).to.eql(0);
|
||||||
|
expect(secondCase.total_comments).to.eql(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
it('returns a 403 when attempting to create a case with an owner that was from a disabled feature in the space', async () => {
|
it('returns a 403 when attempting to create a case with an owner that was from a disabled feature in the space', async () => {
|
||||||
const theCase = (await bulkCreateCases({
|
const theCase = (await bulkCreateCases({
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
removeServerGeneratedPropertiesFromUserAction,
|
removeServerGeneratedPropertiesFromUserAction,
|
||||||
createConfiguration,
|
createConfiguration,
|
||||||
getConfigurationRequest,
|
getConfigurationRequest,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
secOnly,
|
secOnly,
|
||||||
|
@ -721,6 +722,21 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('attachment stats', () => {
|
||||||
|
it('should set the attachment stats to zero', async () => {
|
||||||
|
await createCase(supertest, getPostCaseRequest());
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(1);
|
||||||
|
|
||||||
|
const theCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
|
||||||
|
expect(theCase.total_alerts).to.eql(0);
|
||||||
|
expect(theCase.total_comments).to.eql(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
it('returns a 403 when attempting to create a case with an owner that was from a disabled feature in the space', async () => {
|
it('returns a 403 when attempting to create a case with an owner that was from a disabled feature in the space', async () => {
|
||||||
const theCase = (await createCase(
|
const theCase = (await createCase(
|
||||||
|
|
|
@ -20,7 +20,12 @@ import {
|
||||||
} from '../../../../../common/utils/security_solution';
|
} from '../../../../../common/utils/security_solution';
|
||||||
import type { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
import type { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||||
|
|
||||||
import { getPostCaseRequest, postCaseReq, postCommentUserReq } from '../../../../common/lib/mock';
|
import {
|
||||||
|
getPostCaseRequest,
|
||||||
|
postCaseReq,
|
||||||
|
postCommentAlertReq,
|
||||||
|
postCommentUserReq,
|
||||||
|
} from '../../../../common/lib/mock';
|
||||||
import {
|
import {
|
||||||
deleteAllCaseItems,
|
deleteAllCaseItems,
|
||||||
deleteCasesByESQuery,
|
deleteCasesByESQuery,
|
||||||
|
@ -30,6 +35,9 @@ import {
|
||||||
createComment,
|
createComment,
|
||||||
deleteComment,
|
deleteComment,
|
||||||
superUserSpace1Auth,
|
superUserSpace1Auth,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
|
bulkCreateAttachments,
|
||||||
|
resolveCase,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
globalRead,
|
globalRead,
|
||||||
|
@ -278,6 +286,50 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('attachment stats', () => {
|
||||||
|
it('should set the attachment stats correctly', async () => {
|
||||||
|
const postedCase = await createCase(supertest, postCaseReq);
|
||||||
|
|
||||||
|
await bulkCreateAttachments({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: [postCommentUserReq, postCommentUserReq, postCommentAlertReq],
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolvedCase = await resolveCase({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseComments = resolvedCase.case.comments!;
|
||||||
|
|
||||||
|
const userComment = caseComments?.find((comment) => comment.type === 'user');
|
||||||
|
const alertComment = caseComments?.find((comment) => comment.type === 'alert');
|
||||||
|
|
||||||
|
await deleteComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
commentId: userComment!.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
commentId: alertComment!.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(1);
|
||||||
|
|
||||||
|
const theCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
|
||||||
|
expect(theCase.total_alerts).to.eql(0);
|
||||||
|
expect(theCase.total_comments).to.eql(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await deleteAllCaseItems(es);
|
await deleteAllCaseItems(es);
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {
|
||||||
superUserSpace1Auth,
|
superUserSpace1Auth,
|
||||||
bulkCreateAttachments,
|
bulkCreateAttachments,
|
||||||
getAllComments,
|
getAllComments,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
globalRead,
|
globalRead,
|
||||||
|
@ -362,6 +363,33 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('attachment stats', () => {
|
||||||
|
it('should set the attachment stats correctly', async () => {
|
||||||
|
const postedCase = await createCase(supertest, postCaseReq);
|
||||||
|
|
||||||
|
await bulkCreateAttachments({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: [postCommentUserReq, postCommentUserReq, postCommentAlertReq],
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteAllComments({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(1);
|
||||||
|
|
||||||
|
const theCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
|
||||||
|
expect(theCase.total_alerts).to.eql(0);
|
||||||
|
expect(theCase.total_comments).to.eql(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await deleteAllCaseItems(es);
|
await deleteAllCaseItems(es);
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
fileAttachmentMetadata,
|
fileAttachmentMetadata,
|
||||||
fileMetadata,
|
fileMetadata,
|
||||||
postCommentAlertMultipleIdsReq,
|
postCommentAlertMultipleIdsReq,
|
||||||
|
postCommentActionsReq,
|
||||||
} from '../../../../common/lib/mock';
|
} from '../../../../common/lib/mock';
|
||||||
import {
|
import {
|
||||||
deleteAllCaseItems,
|
deleteAllCaseItems,
|
||||||
|
@ -46,6 +47,7 @@ import {
|
||||||
removeServerGeneratedPropertiesFromUserAction,
|
removeServerGeneratedPropertiesFromUserAction,
|
||||||
getAllComments,
|
getAllComments,
|
||||||
bulkCreateAttachments,
|
bulkCreateAttachments,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
createAlertsIndex,
|
createAlertsIndex,
|
||||||
|
@ -1152,6 +1154,48 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
expectedHttpCode: 200,
|
expectedHttpCode: 200,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the attachment stats correctly', async () => {
|
||||||
|
const postedCase = await createCase(supertest, postCaseReq);
|
||||||
|
|
||||||
|
await createComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: postCommentUserReq,
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: postCommentUserReq,
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: postCommentAlertReq,
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
// an attachment that is not a comment or an alert should not affect the stats
|
||||||
|
await createComment({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: postCommentActionsReq,
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(1);
|
||||||
|
|
||||||
|
const theCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
|
||||||
|
expect(theCase.total_alerts).to.eql(1);
|
||||||
|
expect(theCase.total_comments).to.eql(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
postExternalReferenceSOReq,
|
postExternalReferenceSOReq,
|
||||||
fileMetadata,
|
fileMetadata,
|
||||||
postCommentAlertMultipleIdsReq,
|
postCommentAlertMultipleIdsReq,
|
||||||
|
postCommentActionsReq,
|
||||||
} from '../../../../common/lib/mock';
|
} from '../../../../common/lib/mock';
|
||||||
import {
|
import {
|
||||||
deleteAllCaseItems,
|
deleteAllCaseItems,
|
||||||
|
@ -41,6 +42,7 @@ import {
|
||||||
deleteAllFiles,
|
deleteAllFiles,
|
||||||
getAllComments,
|
getAllComments,
|
||||||
createComment,
|
createComment,
|
||||||
|
getCaseSavedObjectsFromES,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
createAlertsIndex,
|
createAlertsIndex,
|
||||||
|
@ -1537,6 +1539,32 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
expectedHttpCode: 200,
|
expectedHttpCode: 200,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the attachment stats correctly', async () => {
|
||||||
|
const postedCase = await createCase(supertest, postCaseReq);
|
||||||
|
|
||||||
|
await bulkCreateAttachments({
|
||||||
|
supertest,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
params: [
|
||||||
|
postCommentUserReq,
|
||||||
|
postCommentUserReq,
|
||||||
|
postCommentAlertReq,
|
||||||
|
// an attachment that is not a comment or an alert should not affect the stats
|
||||||
|
postCommentActionsReq,
|
||||||
|
],
|
||||||
|
expectedHttpCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getCaseSavedObjectsFromES({ es });
|
||||||
|
|
||||||
|
expect(res.body.hits.hits.length).to.eql(1);
|
||||||
|
|
||||||
|
const theCase = res.body.hits.hits[0]._source?.cases!;
|
||||||
|
|
||||||
|
expect(theCase.total_alerts).to.eql(1);
|
||||||
|
expect(theCase.total_comments).to.eql(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rbac', () => {
|
describe('rbac', () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue