[ResponseOps] [Cases] Update mapping for the status field in cases (#147920)

Connected to #132041

## Summary

Final PR with migrations to the Cases' saved objects to enable sorting
by additional fields in the all-cases view.

This time the **status** is being migrated.

- In this PR the case status field **mapping** is changed from `keyword`
to `short`.
- Added **data** migrations for 8.7.0 converting the status field values
from `keyword` to `short`.
- Added tests for the migrations.
- Updated existing transforms to take status into account. ES model to
external model and vice versa.
- Updated existing tests to take into account the new internal value
type of the status field.

PS: @cnasikas some of the changes you requested in the previous PR I did
here too. I noticed there were some tests that still used the
dictionaries to convert the fields. Here I hardcoded the values instead
of using the dictionary for testing.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Antonio 2022-12-27 15:21:19 +00:00 committed by GitHub
parent 8a44ba3158
commit 4adfce9080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 619 additions and 154 deletions

View file

@ -69,7 +69,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"canvas-element": "e2e312fc499c1a81e628b88baba492fb24f4e82d",
"canvas-workpad": "4b05f7829bc805bbaa07eb9fc0d2a2bbbd6bbf39",
"canvas-workpad-template": "d4bb65aa9c4a2b25029d3272fd9c715d8e4247d7",
"cases": "dc9b349d343dab9c0fecac4104f9f6d4d068bcb2",
"cases": "d9f9080c3200241628ae09d3614a03f1b5dacbfc",
"cases-comments": "d7c4c1d24e97620cd415e27e5eb7d5b5f2c5b461",
"cases-configure": "1afc414f5563a36e4612fa269193d3ed7277c7bd",
"cases-connector-mappings": "4b16d440af966e5d6e0fa33368bfa15d987a4b69",

View file

@ -15,7 +15,7 @@ import {
import { toElasticsearchQuery } from '@kbn/es-query';
import { CaseStatuses } from '../../common';
import { CaseSeverity } from '../../common/api';
import { SEVERITY_EXTERNAL_TO_ESMODEL } from '../common/constants';
import { ESCaseSeverity, ESCaseStatus } from '../services/cases/types';
describe('utils', () => {
describe('convertSortField', () => {
@ -386,8 +386,12 @@ describe('utils', () => {
`);
});
it('creates a filter for the status', () => {
expect(constructQueryOptions({ status: CaseStatuses.open }).filter).toMatchInlineSnapshot(`
it.each([
[CaseStatuses.open, ESCaseStatus.OPEN],
[CaseStatuses['in-progress'], ESCaseStatus.IN_PROGRESS],
[CaseStatuses.closed, ESCaseStatus.CLOSED],
])('creates a filter for status "%s"', (status, expectedStatus) => {
expect(constructQueryOptions({ status }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
@ -398,7 +402,7 @@ describe('utils', () => {
Object {
"isQuoted": false,
"type": "literal",
"value": "open",
"value": "${expectedStatus}",
},
],
"function": "is",
@ -407,9 +411,13 @@ describe('utils', () => {
`);
});
it('creates a filter for the severity', () => {
Object.values(CaseSeverity).forEach((severity) => {
expect(constructQueryOptions({ severity }).filter).toMatchInlineSnapshot(`
it.each([
[CaseSeverity.LOW, ESCaseSeverity.LOW],
[CaseSeverity.MEDIUM, ESCaseSeverity.MEDIUM],
[CaseSeverity.HIGH, ESCaseSeverity.HIGH],
[CaseSeverity.CRITICAL, ESCaseSeverity.CRITICAL],
])('creates a filter for severity "%s"', (severity, expectedSeverity) => {
expect(constructQueryOptions({ severity }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
@ -420,14 +428,13 @@ describe('utils', () => {
Object {
"isQuoted": false,
"type": "literal",
"value": "${SEVERITY_EXTERNAL_TO_ESMODEL[severity]}",
"value": "${expectedSeverity}",
},
],
"function": "is",
"type": "function",
}
`);
});
});
it('creates a filter for the time range', () => {

View file

@ -20,7 +20,7 @@ import {
} from '../../common/utils/attachments';
import { CASE_SAVED_OBJECT, NO_ASSIGNEES_FILTERING_KEYWORD } from '../../common/constants';
import { SEVERITY_EXTERNAL_TO_ESMODEL } from '../common/constants';
import { SEVERITY_EXTERNAL_TO_ESMODEL, STATUS_EXTERNAL_TO_ESMODEL } from '../common/constants';
import type {
CaseStatuses,
CommentRequest,
@ -147,7 +147,9 @@ export const addStatusFilter = ({
type?: string;
}): KueryNode => {
const filters: KueryNode[] = [];
filters.push(nodeBuilder.is(`${type}.attributes.status`, status));
filters.push(
nodeBuilder.is(`${type}.attributes.status`, `${STATUS_EXTERNAL_TO_ESMODEL[status]}`)
);
if (appendFilter) {
filters.push(appendFilter);

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { CaseSeverity } from '../../common/api';
import { CaseSeverity, CaseStatuses } from '../../common/api';
import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../common/constants';
import { ESCaseSeverity } from '../services/cases/types';
import { ESCaseSeverity, ESCaseStatus } from '../services/cases/types';
/**
* The name of the saved object reference indicating the action connector ID. This is stored in the Saved Object reference
@ -53,3 +53,15 @@ export const SEVERITY_ESMODEL_TO_EXTERNAL: Record<ESCaseSeverity, CaseSeverity>
[ESCaseSeverity.HIGH]: CaseSeverity.HIGH,
[ESCaseSeverity.CRITICAL]: CaseSeverity.CRITICAL,
};
export const STATUS_EXTERNAL_TO_ESMODEL: Record<CaseStatuses, ESCaseStatus> = {
[CaseStatuses.open]: ESCaseStatus.OPEN,
[CaseStatuses['in-progress']]: ESCaseStatus.IN_PROGRESS,
[CaseStatuses.closed]: ESCaseStatus.CLOSED,
};
export const STATUS_ESMODEL_TO_EXTERNAL: Record<ESCaseStatus, CaseStatuses> = {
[ESCaseStatus.OPEN]: CaseStatuses.open,
[ESCaseStatus.IN_PROGRESS]: CaseStatuses['in-progress'],
[ESCaseStatus.CLOSED]: CaseStatuses.closed,
};

View file

@ -145,7 +145,7 @@ export const createCaseSavedObjectType = (
},
},
status: {
type: 'keyword',
type: 'short',
},
tags: {
type: 'keyword',

View file

@ -7,11 +7,10 @@
import type { SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import type { CaseAttributes, CaseFullExternalService } from '../../../common/api';
import { CaseSeverity, ConnectorTypes, NONE_CONNECTOR_ID } from '../../../common/api';
import { CaseSeverity, CaseStatuses, ConnectorTypes, NONE_CONNECTOR_ID } from '../../../common/api';
import { CASE_SAVED_OBJECT } from '../../../common/constants';
import { SEVERITY_EXTERNAL_TO_ESMODEL } from '../../common/constants';
import { getNoneCaseConnector } from '../../common/utils';
import { ESCaseSeverity } from '../../services/cases/types';
import { ESCaseSeverity, ESCaseStatus } from '../../services/cases/types';
import type { ESCaseConnectorWithId } from '../../services/test_utils';
import { createExternalService } from '../../services/test_utils';
import {
@ -20,6 +19,7 @@ import {
addSeverity,
caseConnectorIdMigration,
convertSeverity,
convertStatus,
removeCaseType,
} from './cases';
@ -582,8 +582,14 @@ describe('case migrations', () => {
});
describe('update severity', () => {
for (const oldSeverityValue of Object.values(CaseSeverity)) {
it(`migrates ${oldSeverityValue} severity string label to matching number`, () => {
it.each([
[CaseSeverity.LOW, ESCaseSeverity.LOW],
[CaseSeverity.MEDIUM, ESCaseSeverity.MEDIUM],
[CaseSeverity.HIGH, ESCaseSeverity.HIGH],
[CaseSeverity.CRITICAL, ESCaseSeverity.CRITICAL],
])(
'migrates "%s" severity keyword value to matching short',
(oldSeverityValue, expectedSeverityValue) => {
const doc = {
id: '123',
type: 'abc',
@ -597,14 +603,14 @@ describe('case migrations', () => {
...doc,
attributes: {
...doc.attributes,
severity: SEVERITY_EXTERNAL_TO_ESMODEL[oldSeverityValue],
severity: expectedSeverityValue,
},
references: [],
});
});
}
}
);
it('default value for severity is 0 if it does not exist', () => {
it('default value for severity is 0(LOW) if it does not exist', () => {
const doc = {
id: '123',
type: 'abc',
@ -622,4 +628,51 @@ describe('case migrations', () => {
});
});
});
describe('update status', () => {
it.each([
[CaseStatuses.open, ESCaseStatus.OPEN],
[CaseStatuses['in-progress'], ESCaseStatus.IN_PROGRESS],
[CaseStatuses.closed, ESCaseStatus.CLOSED],
])(
'migrates "%s" status keyword value to matching short',
(oldStatusValue, expectedStatusValue) => {
const doc = {
id: '123',
type: 'abc',
attributes: {
status: oldStatusValue,
},
references: [],
} as unknown as SavedObjectUnsanitizedDoc<CaseAttributes>;
expect(convertStatus(doc)).toEqual({
...doc,
attributes: {
...doc.attributes,
status: expectedStatusValue,
},
references: [],
});
}
);
it('default value for status is 0(OPEN) if it does not exist', () => {
const doc = {
id: '123',
type: 'abc',
attributes: {},
references: [],
} as unknown as SavedObjectUnsanitizedDoc<CaseAttributes>;
expect(convertStatus(doc)).toEqual({
...doc,
attributes: {
...doc.attributes,
status: ESCaseStatus.OPEN,
},
references: [],
});
});
});
});

View file

@ -7,7 +7,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { cloneDeep, unset } from 'lodash';
import { cloneDeep, unset, flow } from 'lodash';
import type { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '@kbn/core/server';
import type { SanitizedCaseOwner } from '.';
import { addOwnerToSO } from '.';
@ -19,6 +19,7 @@ import {
CONNECTOR_ID_REFERENCE_NAME,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
SEVERITY_EXTERNAL_TO_ESMODEL,
STATUS_EXTERNAL_TO_ESMODEL,
} from '../../common/constants';
import {
transformConnectorIdToReference,
@ -26,7 +27,7 @@ import {
} from './user_actions/connector_id';
import { CASE_TYPE_INDIVIDUAL } from './constants';
import { pipeMigrations } from './utils';
import { ESCaseSeverity } from '../../services/cases/types';
import { ESCaseSeverity, ESCaseStatus } from '../../services/cases/types';
interface UnsanitizedCaseConnector {
connector_id: string;
@ -145,6 +146,17 @@ export const convertSeverity = (
};
};
export const convertStatus = (
doc: SavedObjectUnsanitizedDoc<CaseAttributes>
): SavedObjectSanitizedDoc<Omit<CaseAttributes, 'status'> & { status: ESCaseStatus }> => {
const status = STATUS_EXTERNAL_TO_ESMODEL[doc.attributes?.status] ?? ESCaseStatus.OPEN;
return {
...doc,
attributes: { ...doc.attributes, status },
references: doc.references ?? [],
};
};
export const caseMigrations = {
'7.10.0': (
doc: SavedObjectUnsanitizedDoc<UnsanitizedCaseConnector>
@ -208,5 +220,5 @@ export const caseMigrations = {
'8.1.0': removeCaseType,
'8.3.0': pipeMigrations(addDuration, addSeverity),
'8.5.0': addAssignees,
'8.7.0': convertSeverity,
'8.7.0': flow(convertSeverity, convertStatus),
};

View file

@ -14,7 +14,7 @@
*/
import type { CaseAttributes, CaseConnector, CaseFullExternalService } from '../../../common/api';
import { CaseSeverity } from '../../../common/api';
import { CaseSeverity, CaseStatuses } from '../../../common/api';
import { CASE_SAVED_OBJECT } from '../../../common/constants';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import type {
@ -42,7 +42,7 @@ import {
createSOFindResponse,
} from '../test_utils';
import type { ESCaseAttributes } from './types';
import { ESCaseSeverity } from './types';
import { ESCaseSeverity, ESCaseStatus } from './types';
import { AttachmentService } from '../attachments';
import { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry';
import type { CaseSavedObject } from '../../common/types';
@ -51,10 +51,12 @@ const createUpdateSOResponse = ({
connector,
externalService,
severity,
status,
}: {
connector?: ESCaseConnectorWithId;
externalService?: CaseFullExternalService;
severity?: ESCaseSeverity;
status?: ESCaseStatus;
} = {}): SavedObjectsUpdateResponse<ESCaseAttributes> => {
const references: SavedObjectReference[] = createSavedObjectReferences({
connector,
@ -79,6 +81,10 @@ const createUpdateSOResponse = ({
attributes = { ...attributes, severity };
}
if (status || status === 0) {
attributes = { ...attributes, status };
}
return {
type: CASE_SAVED_OBJECT,
id: '1',
@ -98,24 +104,38 @@ const createFindSO = (
score: 0,
});
const createCaseUpdateParams = (
connector?: CaseConnector,
externalService?: CaseFullExternalService,
severity?: CaseSeverity
): Partial<CaseAttributes> => ({
const createCaseUpdateParams = ({
connector,
externalService,
severity,
status,
}: {
connector?: CaseConnector;
externalService?: CaseFullExternalService;
severity?: CaseSeverity;
status?: CaseStatuses;
}): Partial<CaseAttributes> => ({
...(connector && { connector }),
...(externalService && { external_service: externalService }),
...(severity && { severity }),
...(status && { status }),
});
const createCasePostParams = (
connector: CaseConnector,
externalService?: CaseFullExternalService,
severity?: CaseSeverity
): CaseAttributes => ({
const createCasePostParams = ({
connector,
externalService,
severity,
status,
}: {
connector: CaseConnector;
externalService?: CaseFullExternalService;
severity?: CaseSeverity;
status?: CaseStatuses;
}): CaseAttributes => ({
...basicCaseFields,
connector,
...(severity ? { severity } : { severity: basicCaseFields.severity }),
...(status ? { status } : { status: basicCaseFields.status }),
...(externalService ? { external_service: externalService } : { external_service: null }),
});
@ -160,11 +180,12 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(
createJiraConnector(),
createExternalService(),
CaseSeverity.CRITICAL
),
updatedAttributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
severity: CaseSeverity.CRITICAL,
status: CaseStatuses['in-progress'],
}),
originalCase: {} as CaseSavedObject,
});
@ -191,7 +212,7 @@ describe('CasesService', () => {
"syncAlerts": true,
},
"severity": 30,
"status": "open",
"status": 10,
"tags": Array [
"defacement",
],
@ -213,7 +234,10 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
updatedAttributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
originalCase: {} as CaseSavedObject,
});
@ -244,7 +268,10 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
updatedAttributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
originalCase: {} as CaseSavedObject,
});
@ -279,7 +306,7 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(createJiraConnector()),
updatedAttributes: createCaseUpdateParams({ connector: createJiraConnector() }),
originalCase: {} as CaseSavedObject,
});
@ -307,7 +334,10 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
externalService: createExternalService(),
}),
originalCase: {} as CaseSavedObject,
});
@ -335,7 +365,10 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
updatedAttributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
originalCase: {
references: [{ id: 'a', name: 'awesome', type: 'hello' }],
} as CaseSavedObject,
@ -404,7 +437,10 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
externalService: createExternalService(),
}),
originalCase: {} as CaseSavedObject,
});
@ -433,7 +469,7 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -452,7 +488,7 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(getNoneCaseConnector()),
updatedAttributes: createCaseUpdateParams({ connector: getNoneCaseConnector() }),
originalCase: {} as CaseSavedObject,
});
@ -481,7 +517,7 @@ describe('CasesService', () => {
await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(undefined, undefined, patchParamsSeverity),
updatedAttributes: createCaseUpdateParams({ severity: patchParamsSeverity }),
originalCase: {} as CaseSavedObject,
});
@ -491,6 +527,30 @@ describe('CasesService', () => {
expect(patchAttributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[CaseStatuses.open, ESCaseStatus.OPEN],
[CaseStatuses['in-progress'], ESCaseStatus.IN_PROGRESS],
[CaseStatuses.closed, ESCaseStatus.CLOSED],
])(
'properly converts "%s" status to corresponding ES value on updating SO',
async (patchParamsStatus, expectedStatus) => {
unsecuredSavedObjectsClient.update.mockResolvedValue(
{} as SavedObjectsUpdateResponse<ESCaseAttributes>
);
await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams({ status: patchParamsStatus }),
originalCase: {} as CaseSavedObject,
});
const patchAttributes = unsecuredSavedObjectsClient.update.mock
.calls[0][2] as ESCaseAttributes;
expect(patchAttributes.status).toEqual(expectedStatus);
}
);
});
describe('bulkPatch', () => {
@ -508,38 +568,34 @@ describe('CasesService', () => {
cases: [
{
caseId: '1',
updatedAttributes: createCasePostParams(
getNoneCaseConnector(),
undefined,
CaseSeverity.LOW
),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
severity: CaseSeverity.LOW,
}),
originalCase: {} as CaseSavedObject,
},
{
caseId: '2',
updatedAttributes: createCasePostParams(
getNoneCaseConnector(),
undefined,
CaseSeverity.MEDIUM
),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
severity: CaseSeverity.MEDIUM,
}),
originalCase: {} as CaseSavedObject,
},
{
caseId: '3',
updatedAttributes: createCasePostParams(
getNoneCaseConnector(),
undefined,
CaseSeverity.HIGH
),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
severity: CaseSeverity.HIGH,
}),
originalCase: {} as CaseSavedObject,
},
{
caseId: '4',
updatedAttributes: createCasePostParams(
getNoneCaseConnector(),
undefined,
CaseSeverity.CRITICAL
),
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
severity: CaseSeverity.CRITICAL,
}),
originalCase: {} as CaseSavedObject,
},
],
@ -553,6 +609,52 @@ describe('CasesService', () => {
expect(patchResults[2].attributes.severity).toEqual(ESCaseSeverity.HIGH);
expect(patchResults[3].attributes.severity).toEqual(ESCaseSeverity.CRITICAL);
});
it('properly converts status to corresponding ES value on bulk updating SO', 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,
}),
originalCase: {} as CaseSavedObject,
},
{
caseId: '2',
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
status: CaseStatuses['in-progress'],
}),
originalCase: {} as CaseSavedObject,
},
{
caseId: '3',
updatedAttributes: createCasePostParams({
connector: getNoneCaseConnector(),
status: CaseStatuses.closed,
}),
originalCase: {} as CaseSavedObject,
},
],
});
const patchResults = unsecuredSavedObjectsClient.bulkUpdate.mock
.calls[0][0] as unknown as Array<SavedObject<ESCaseAttributes>>;
expect(patchResults[0].attributes.status).toEqual(ESCaseStatus.OPEN);
expect(patchResults[1].attributes.status).toEqual(ESCaseStatus.IN_PROGRESS);
expect(patchResults[2].attributes.status).toEqual(ESCaseStatus.CLOSED);
});
});
describe('post', () => {
@ -560,7 +662,7 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(createJiraConnector()),
attributes: createCasePostParams({ connector: createJiraConnector() }),
id: '1',
});
@ -573,7 +675,10 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(createJiraConnector(), createExternalService()),
attributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
id: '1',
});
@ -629,7 +734,7 @@ describe('CasesService', () => {
"syncAlerts": true,
},
"severity": 0,
"status": "open",
"status": 0,
"tags": Array [
"defacement",
],
@ -666,7 +771,10 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(createJiraConnector(), createExternalService()),
attributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
id: '1',
});
@ -692,10 +800,10 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(
createJiraConnector({ setFieldsToNull: true }),
createExternalService()
),
attributes: createCasePostParams({
connector: createJiraConnector({ setFieldsToNull: true }),
externalService: createExternalService(),
}),
id: '1',
});
@ -708,7 +816,7 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(getNoneCaseConnector()),
attributes: createCasePostParams({ connector: getNoneCaseConnector() }),
id: '1',
});
@ -721,7 +829,7 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(getNoneCaseConnector()),
attributes: createCasePostParams({ connector: getNoneCaseConnector() }),
id: '1',
});
@ -741,7 +849,10 @@ describe('CasesService', () => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams(getNoneCaseConnector(), undefined, postParamsSeverity),
attributes: createCasePostParams({
connector: getNoneCaseConnector(),
severity: postParamsSeverity,
}),
id: '1',
});
@ -750,6 +861,29 @@ describe('CasesService', () => {
expect(postAttributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[CaseStatuses.open, ESCaseStatus.OPEN],
[CaseStatuses['in-progress'], ESCaseStatus.IN_PROGRESS],
[CaseStatuses.closed, ESCaseStatus.CLOSED],
])(
'properly converts "%s" status to corresponding ES value on creating SO',
async (postParamsStatus, expectedStatus) => {
unsecuredSavedObjectsClient.create.mockResolvedValue({} as SavedObject<ESCaseAttributes>);
await service.postNewCase({
attributes: createCasePostParams({
connector: getNoneCaseConnector(),
status: postParamsStatus,
}),
id: '1',
});
const postAttributes = unsecuredSavedObjectsClient.create.mock
.calls[0][1] as ESCaseAttributes;
expect(postAttributes.status).toEqual(expectedStatus);
}
);
});
});
@ -773,10 +907,10 @@ describe('CasesService', () => {
cases: [
{
caseId: '1',
updatedAttributes: createCasePostParams(
createJiraConnector(),
createExternalService()
),
updatedAttributes: createCasePostParams({
connector: createJiraConnector(),
externalService: createExternalService(),
}),
originalCase: {} as CaseSavedObject,
},
],
@ -815,7 +949,7 @@ describe('CasesService', () => {
).toMatchInlineSnapshot(`"100"`);
});
it('properly converts the severity field to the corresponding external value the bulkPatch response', async () => {
it('properly converts the severity field to the corresponding external value in the bulkPatch response', async () => {
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [
createCaseSavedObjectResponse({ overrides: { severity: ESCaseSeverity.LOW } }),
@ -829,7 +963,7 @@ describe('CasesService', () => {
cases: [
{
caseId: '1',
updatedAttributes: createCasePostParams(getNoneCaseConnector()),
updatedAttributes: createCasePostParams({ connector: getNoneCaseConnector() }),
originalCase: {} as CaseSavedObject,
},
],
@ -839,6 +973,29 @@ describe('CasesService', () => {
expect(res.saved_objects[2].attributes.severity).toEqual(CaseSeverity.HIGH);
expect(res.saved_objects[3].attributes.severity).toEqual(CaseSeverity.CRITICAL);
});
it('properly converts the status field to the corresponding external value in the bulkPatch response', async () => {
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [
createCaseSavedObjectResponse({ overrides: { status: ESCaseStatus.OPEN } }),
createCaseSavedObjectResponse({ overrides: { status: ESCaseStatus.IN_PROGRESS } }),
createCaseSavedObjectResponse({ overrides: { status: ESCaseStatus.CLOSED } }),
],
});
const res = await service.patchCases({
cases: [
{
caseId: '1',
updatedAttributes: createCasePostParams({ connector: getNoneCaseConnector() }),
originalCase: {} as CaseSavedObject,
},
],
});
expect(res.saved_objects[0].attributes.status).toEqual(CaseStatuses.open);
expect(res.saved_objects[1].attributes.status).toEqual(CaseStatuses['in-progress']);
expect(res.saved_objects[2].attributes.status).toEqual(CaseStatuses.closed);
});
});
describe('patch', () => {
@ -849,7 +1006,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -873,7 +1030,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -890,7 +1047,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -905,7 +1062,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -936,7 +1093,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -966,7 +1123,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -989,7 +1146,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -1025,7 +1182,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -1051,7 +1208,7 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
@ -1087,13 +1244,34 @@ describe('CasesService', () => {
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams(),
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
expect(res.attributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'properly converts "%s" status to corresponding external value in the patch response',
async (internalStatusValue, expectedStatus) => {
unsecuredSavedObjectsClient.update.mockResolvedValue(
createUpdateSOResponse({ status: internalStatusValue })
);
const res = await service.patchCase({
caseId: '1',
updatedAttributes: createCaseUpdateParams({}),
originalCase: {} as CaseSavedObject,
});
expect(res.attributes.status).toEqual(expectedStatus);
}
);
});
describe('post', () => {
@ -1106,7 +1284,7 @@ describe('CasesService', () => {
);
const res = await service.postNewCase({
attributes: createCasePostParams(getNoneCaseConnector()),
attributes: createCasePostParams({ connector: getNoneCaseConnector() }),
id: '1',
});
@ -1127,13 +1305,33 @@ describe('CasesService', () => {
);
const res = await service.postNewCase({
attributes: createCasePostParams(getNoneCaseConnector()),
attributes: createCasePostParams({ connector: getNoneCaseConnector() }),
id: '1',
});
expect(res.attributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'properly converts "%s" status to corresponding external value in the post response',
async (internalStatusValue, expectedStatus) => {
unsecuredSavedObjectsClient.create.mockResolvedValue(
createCaseSavedObjectResponse({ overrides: { status: internalStatusValue } })
);
const res = await service.postNewCase({
attributes: createCasePostParams({ connector: getNoneCaseConnector() }),
id: '1',
});
expect(res.attributes.status).toEqual(expectedStatus);
}
);
});
describe('find', () => {
@ -1193,6 +1391,24 @@ describe('CasesService', () => {
expect(res.saved_objects[0].attributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'includes the properly converted "%s" status field in the result',
async (status, expectedStatus) => {
const findMockReturn = createSOFindResponse([
createFindSO({ overrides: { status } }),
createFindSO(),
]);
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
const res = await service.findCases();
expect(res.saved_objects[0].attributes.status).toEqual(expectedStatus);
}
);
});
describe('bulkGet', () => {
@ -1247,6 +1463,27 @@ describe('CasesService', () => {
expect(res.saved_objects[2].attributes.severity).toEqual(CaseSeverity.HIGH);
expect(res.saved_objects[3].attributes.severity).toEqual(CaseSeverity.CRITICAL);
});
it('includes all status values properly converted in the response', async () => {
unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({
saved_objects: [
createCaseSavedObjectResponse({
overrides: { status: ESCaseStatus.OPEN },
}),
createCaseSavedObjectResponse({
overrides: { status: ESCaseStatus.IN_PROGRESS },
}),
createCaseSavedObjectResponse({
overrides: { status: ESCaseStatus.CLOSED },
}),
],
});
const res = await service.getCases({ caseIds: ['a'] });
expect(res.saved_objects[0].attributes.status).toEqual(CaseStatuses.open);
expect(res.saved_objects[1].attributes.status).toEqual(CaseStatuses['in-progress']);
expect(res.saved_objects[2].attributes.status).toEqual(CaseStatuses.closed);
});
});
describe('get', () => {
@ -1367,6 +1604,23 @@ describe('CasesService', () => {
expect(res.attributes.severity).toEqual(expectedSeverity);
}
);
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'includes the properly converted "%s" status field in the result',
async (internalStatusValue, expectedStatus) => {
unsecuredSavedObjectsClient.get.mockResolvedValue({
attributes: { status: internalStatusValue },
} as SavedObject<ESCaseAttributes>);
const res = await service.getCase({ id: 'a' });
expect(res.attributes.status).toEqual(expectedStatus);
}
);
});
});

View file

@ -51,6 +51,7 @@ import {
transformFindResponseToExternalModel,
} from './transform';
import type { ESCaseAttributes } from './types';
import { ESCaseStatus } from './types';
import type { AttachmentService } from '../attachments';
import type { AggregationBuilder, AggregationResponse } from '../../client/metrics/types';
import { createCaseError } from '../../common/error';
@ -285,19 +286,19 @@ export class CasesService {
const statusBuckets = CasesService.getStatusBuckets(cases.aggregations?.statuses.buckets);
return {
open: statusBuckets?.get('open') ?? 0,
'in-progress': statusBuckets?.get('in-progress') ?? 0,
closed: statusBuckets?.get('closed') ?? 0,
open: statusBuckets?.get(ESCaseStatus.OPEN) ?? 0,
'in-progress': statusBuckets?.get(ESCaseStatus.IN_PROGRESS) ?? 0,
closed: statusBuckets?.get(ESCaseStatus.CLOSED) ?? 0,
};
}
private static getStatusBuckets(
buckets: Array<{ key: string; doc_count: number }> | undefined
): Map<string, number> | undefined {
): Map<number, number> | undefined {
return buckets?.reduce((acc, bucket) => {
acc.set(bucket.key, bucket.doc_count);
acc.set(Number(bucket.key), bucket.doc_count);
return acc;
}, new Map<string, number>());
}, new Map<number, number>());
}
public async deleteCase({ id: caseId, refresh }: DeleteCaseArgs) {

View file

@ -17,15 +17,13 @@ import {
transformUpdateResponseToExternalModel,
} from './transform';
import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server';
import { CaseSeverity, ConnectorTypes } from '../../../common/api';
import { CaseSeverity, CaseStatuses, ConnectorTypes } from '../../../common/api';
import {
CONNECTOR_ID_REFERENCE_NAME,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
SEVERITY_ESMODEL_TO_EXTERNAL,
SEVERITY_EXTERNAL_TO_ESMODEL,
} from '../../common/constants';
import { getNoneCaseConnector } from '../../common/utils';
import { ESCaseSeverity } from './types';
import { ESCaseSeverity, ESCaseStatus } from './types';
describe('case transforms', () => {
describe('transformUpdateResponseToExternalModel', () => {
@ -200,6 +198,49 @@ describe('case transforms', () => {
}
`);
});
it.each([
[ESCaseSeverity.LOW, CaseSeverity.LOW],
[ESCaseSeverity.MEDIUM, CaseSeverity.MEDIUM],
[ESCaseSeverity.HIGH, CaseSeverity.HIGH],
[ESCaseSeverity.CRITICAL, CaseSeverity.CRITICAL],
])(
'properly converts "%s" severity to corresponding external value "%s"',
(internalSeverityValue, expectedSeverityValue) => {
const transformedResponse = transformUpdateResponseToExternalModel({
type: 'a',
id: '1',
attributes: {
severity: internalSeverityValue,
},
references: undefined,
});
expect(transformedResponse.attributes).toHaveProperty('severity');
expect(transformedResponse.attributes.severity).toBe(expectedSeverityValue);
}
);
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'properly converts "%s" status to corresponding ES Value "%s"',
(internalStatusValue, expectedStatusValue) => {
const transformedAttributes = transformUpdateResponseToExternalModel({
type: 'a',
id: '1',
attributes: {
status: internalStatusValue,
},
references: undefined,
});
expect(transformedAttributes.attributes).toHaveProperty('status');
expect(transformedAttributes.attributes.status).toBe(expectedStatusValue);
}
);
});
describe('transformAttributesToESModel', () => {
@ -346,26 +387,49 @@ describe('case transforms', () => {
});
it.each([
[CaseSeverity.LOW],
[CaseSeverity.MEDIUM],
[CaseSeverity.HIGH],
[CaseSeverity.CRITICAL],
])('properly converts "%s" severity to corresponding ES Value', (externalSeverityValue) => {
const transformedAttributes = transformAttributesToESModel({
severity: externalSeverityValue,
});
[CaseSeverity.LOW, ESCaseSeverity.LOW],
[CaseSeverity.MEDIUM, ESCaseSeverity.MEDIUM],
[CaseSeverity.HIGH, ESCaseSeverity.HIGH],
[CaseSeverity.CRITICAL, ESCaseSeverity.CRITICAL],
])(
'properly converts "%s" severity to corresponding ES Value "%s"',
(externalSeverityValue, expectedSeverityValue) => {
const transformedAttributes = transformAttributesToESModel({
severity: externalSeverityValue,
});
expect(transformedAttributes.attributes).toHaveProperty('severity');
expect(transformedAttributes.attributes.severity).toBe(
SEVERITY_EXTERNAL_TO_ESMODEL[externalSeverityValue]
);
});
expect(transformedAttributes.attributes).toHaveProperty('severity');
expect(transformedAttributes.attributes.severity).toBe(expectedSeverityValue);
}
);
it('does not return the severity when it is undefined', () => {
expect(transformAttributesToESModel({ severity: undefined }).attributes).not.toHaveProperty(
'severity'
);
});
it.each([
[CaseStatuses.open, ESCaseStatus.OPEN],
[CaseStatuses['in-progress'], ESCaseStatus.IN_PROGRESS],
[CaseStatuses.closed, ESCaseStatus.CLOSED],
])(
'properly converts "%s" status to corresponding ES Value "%s"',
(externalStatusValue, expectedStatusValue) => {
const transformedAttributes = transformAttributesToESModel({
status: externalStatusValue,
});
expect(transformedAttributes.attributes).toHaveProperty('status');
expect(transformedAttributes.attributes.status).toBe(expectedStatusValue);
}
);
it('does not return the status when it is undefined', () => {
expect(transformAttributesToESModel({ status: undefined }).attributes).not.toHaveProperty(
'status'
);
});
});
describe('transformSavedObjectToExternalModel', () => {
@ -437,13 +501,13 @@ describe('case transforms', () => {
});
it.each([
[ESCaseSeverity.LOW],
[ESCaseSeverity.MEDIUM],
[ESCaseSeverity.HIGH],
[ESCaseSeverity.CRITICAL],
[ESCaseSeverity.LOW, CaseSeverity.LOW],
[ESCaseSeverity.MEDIUM, CaseSeverity.MEDIUM],
[ESCaseSeverity.HIGH, CaseSeverity.HIGH],
[ESCaseSeverity.CRITICAL, CaseSeverity.CRITICAL],
])(
'properly converts "%s" severity to corresponding external value',
(internalSeverityValue) => {
'properly converts "%s" severity to corresponding external value "%s"',
(internalSeverityValue, expectedSeverityValue) => {
const caseSO = createCaseSavedObjectResponse({
overrides: { severity: internalSeverityValue },
});
@ -454,9 +518,7 @@ describe('case transforms', () => {
const transformedSO = transformSavedObjectToExternalModel(caseSO);
expect(transformedSO.attributes).toHaveProperty('severity');
expect(transformedSO.attributes.severity).toBe(
SEVERITY_ESMODEL_TO_EXTERNAL[internalSeverityValue]
);
expect(transformedSO.attributes.severity).toBe(expectedSeverityValue);
}
);
@ -465,5 +527,32 @@ describe('case transforms', () => {
'severity'
);
});
it.each([
[ESCaseStatus.OPEN, CaseStatuses.open],
[ESCaseStatus.IN_PROGRESS, CaseStatuses['in-progress']],
[ESCaseStatus.CLOSED, CaseStatuses.closed],
])(
'properly converts "%s" status to corresponding external value "%s"',
(internalStatusValue, expectedStatusValue) => {
const caseSO = createCaseSavedObjectResponse({
overrides: { status: internalStatusValue },
});
expect(caseSO.attributes).toHaveProperty('status');
expect(caseSO.attributes.status).toBe(internalStatusValue);
const transformedSO = transformSavedObjectToExternalModel(caseSO);
expect(transformedSO.attributes).toHaveProperty('status');
expect(transformedSO.attributes.status).toBe(expectedStatusValue);
}
);
it('does not return the status when it is undefined', () => {
expect(transformAttributesToESModel({ status: undefined }).attributes).not.toHaveProperty(
'status'
);
});
});
});

View file

@ -22,9 +22,11 @@ import {
PUSH_CONNECTOR_ID_REFERENCE_NAME,
SEVERITY_ESMODEL_TO_EXTERNAL,
SEVERITY_EXTERNAL_TO_ESMODEL,
STATUS_ESMODEL_TO_EXTERNAL,
STATUS_EXTERNAL_TO_ESMODEL,
} from '../../common/constants';
import type { CaseAttributes, CaseFullExternalService } from '../../../common/api';
import { CaseSeverity, NONE_CONNECTOR_ID } from '../../../common/api';
import { CaseSeverity, CaseStatuses, NONE_CONNECTOR_ID } from '../../../common/api';
import {
findConnectorIdReference,
transformFieldsToESModel,
@ -49,7 +51,7 @@ export function transformUpdateResponsesToExternalModels(
export function transformUpdateResponseToExternalModel(
updatedCase: SavedObjectsUpdateResponse<ESCaseAttributes>
): SavedObjectsUpdateResponse<CaseAttributes> {
const { connector, external_service, severity, ...restUpdateAttributes } =
const { connector, external_service, severity, status, ...restUpdateAttributes } =
updatedCase.attributes ?? {};
const transformedConnector = transformESConnectorToExternalModel({
@ -72,6 +74,7 @@ export function transformUpdateResponseToExternalModel(
attributes: {
...restUpdateAttributes,
...((severity || severity === 0) && { severity: SEVERITY_ESMODEL_TO_EXTERNAL[severity] }),
...((status || status === 0) && { status: STATUS_ESMODEL_TO_EXTERNAL[status] }),
...(transformedConnector && { connector: transformedConnector }),
// if externalService is null that means we intentionally updated it to null within ES so return that as a valid value
...(externalService !== undefined && { external_service: externalService }),
@ -91,7 +94,7 @@ export function transformAttributesToESModel(caseAttributes: Partial<CaseAttribu
attributes: Partial<ESCaseAttributes>;
referenceHandler: ConnectorReferenceHandler;
} {
const { connector, external_service, severity, ...restAttributes } = caseAttributes;
const { connector, external_service, severity, status, ...restAttributes } = caseAttributes;
const { connector_id: pushConnectorId, ...restExternalService } = external_service ?? {};
const transformedConnector = {
@ -118,6 +121,7 @@ export function transformAttributesToESModel(caseAttributes: Partial<CaseAttribu
...transformedConnector,
...transformedExternalService,
...(severity && { severity: SEVERITY_EXTERNAL_TO_ESMODEL[severity] }),
...(status && { status: STATUS_EXTERNAL_TO_ESMODEL[status] }),
},
referenceHandler: buildReferenceHandler(connector?.id, pushConnectorId),
};
@ -185,12 +189,15 @@ export function transformSavedObjectToExternalModel(
const severity =
SEVERITY_ESMODEL_TO_EXTERNAL[caseSavedObject.attributes?.severity] ?? CaseSeverity.LOW;
const status =
STATUS_ESMODEL_TO_EXTERNAL[caseSavedObject.attributes?.status] ?? CaseStatuses.open;
return {
...caseSavedObject,
attributes: {
...caseSavedObject.attributes,
severity,
status,
connector,
external_service: externalService,
},

View file

@ -27,6 +27,12 @@ export enum ESCaseSeverity {
CRITICAL = 30,
}
export enum ESCaseStatus {
OPEN = 0,
IN_PROGRESS = 10,
CLOSED = 20,
}
/**
* This type should only be used within the cases service and its helper functions (e.g. the transforms).
*
@ -36,9 +42,10 @@ export enum ESCaseSeverity {
*/
export type ESCaseAttributes = Omit<
CaseAttributes,
'connector' | 'external_service' | 'severity'
'connector' | 'external_service' | 'severity' | 'status'
> & {
severity: ESCaseSeverity;
status: ESCaseStatus;
connector: ESCaseConnector;
external_service: ExternalServicesWithoutConnectorId | null;
};

View file

@ -18,7 +18,7 @@ import type {
import { CaseSeverity, CaseStatuses, ConnectorTypes, NONE_CONNECTOR_ID } from '../../common/api';
import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../common/constants';
import type { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './cases/types';
import { ESCaseSeverity } from './cases/types';
import { ESCaseSeverity, ESCaseStatus } from './cases/types';
import { getNoneCaseConnector } from '../common/utils';
/**
@ -110,7 +110,7 @@ export const basicESCaseFields: ESCaseAttributes = {
duration: null,
description: 'This is a brand new case of a bad meanie defacing data',
title: 'Super Bad Security Issue',
status: CaseStatuses.open,
status: ESCaseStatus.OPEN,
tags: ['defacement'],
updated_at: '2019-11-25T21:54:48.952Z',
updated_by: {

View file

@ -7,6 +7,7 @@
import type { SavedObjectsFindResponse } from '@kbn/core/server';
import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { ESCaseStatus } from '../../services/cases/types';
import type { CaseAggregationResult } from '../types';
import { getCasesTelemetryData } from './cases';
@ -93,7 +94,7 @@ describe('getCasesTelemetryData', () => {
status: {
buckets: [
{
key: 'open',
key: ESCaseStatus.OPEN,
doc_count: 2,
},
],

View file

@ -10,6 +10,7 @@ import {
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
} from '../../../common/constants';
import { ESCaseStatus } from '../../services/cases/types';
import type { ESCaseAttributes } from '../../services/cases/types';
import { OWNERS } from '../constants';
import type {
@ -169,9 +170,9 @@ export const getCasesTelemetryData = async ({
total: casesRes.total,
...getCountsFromBuckets(aggregationsBuckets.counts),
status: {
open: findValueInBuckets(aggregationsBuckets.status, 'open'),
inProgress: findValueInBuckets(aggregationsBuckets.status, 'in-progress'),
closed: findValueInBuckets(aggregationsBuckets.status, 'closed'),
open: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.OPEN),
inProgress: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.IN_PROGRESS),
closed: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.CLOSED),
},
syncAlertsOn: findValueInBuckets(aggregationsBuckets.syncAlerts, 1),
syncAlertsOff: findValueInBuckets(aggregationsBuckets.syncAlerts, 0),

View file

@ -268,6 +268,7 @@ export default ({ getService }: FtrProviderContext): void => {
owner: postedCase.owner,
connector: postedCase.connector,
severity: postedCase.severity,
status: postedCase.status,
comments: [],
totalAlerts: 0,
totalComment: 0,

View file

@ -29,7 +29,7 @@ import {
CaseStatuses,
CaseSeverity,
} from '@kbn/cases-plugin/common/api';
import { ESCaseSeverity } from '@kbn/cases-plugin/server/services/cases/types';
import { ESCaseSeverity, ESCaseStatus } from '@kbn/cases-plugin/server/services/cases/types';
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
import {
deleteAllCaseItems,
@ -206,7 +206,7 @@ const expectExportToHaveCaseSavedObject = (
expect(createdCaseSO.attributes.connector.name).to.eql(caseRequest.connector.name);
expect(createdCaseSO.attributes.connector.fields).to.eql([]);
expect(createdCaseSO.attributes.settings).to.eql(caseRequest.settings);
expect(createdCaseSO.attributes.status).to.eql(CaseStatuses.open);
expect(createdCaseSO.attributes.status).to.eql(ESCaseStatus.OPEN);
expect(createdCaseSO.attributes.severity).to.eql(ESCaseSeverity.LOW);
expect(createdCaseSO.attributes.duration).to.eql(null);
expect(createdCaseSO.attributes.tags).to.eql(caseRequest.tags);

View file

@ -8,7 +8,7 @@
import expect from '@kbn/expect';
import { CASES_URL, SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common/constants';
import { AttributesTypeUser } from '@kbn/cases-plugin/common/api';
import { ESCaseSeverity } from '@kbn/cases-plugin/server/services/cases/types';
import { ESCaseSeverity, ESCaseStatus } from '@kbn/cases-plugin/server/services/cases/types';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import {
deleteAllCaseItems,
@ -481,19 +481,19 @@ export default function createGetTests({ getService }: FtrProviderContext) {
describe('8.7.0', () => {
before(async () => {
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/cases/8.5.0/cases_severity.json'
'x-pack/test/functional/fixtures/kbn_archiver/cases/8.5.0/cases_severity_and_status.json'
);
});
after(async () => {
await kibanaServer.importExport.unload(
'x-pack/test/functional/fixtures/kbn_archiver/cases/8.5.0/cases_severity.json'
'x-pack/test/functional/fixtures/kbn_archiver/cases/8.5.0/cases_severity_and_status.json'
);
await deleteAllCaseItems(es);
});
describe('severity', () => {
it('severity string labels are converted to matching number', async () => {
it('severity keyword values are converted to matching short', async () => {
const expectedSeverityValues: Record<string, ESCaseSeverity> = {
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf6': ESCaseSeverity.LOW,
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf7': ESCaseSeverity.MEDIUM,
@ -510,6 +510,25 @@ export default function createGetTests({ getService }: FtrProviderContext) {
}
});
});
describe('status', () => {
it('status keyword values are converted to matching short', async () => {
const expectedStatusValues: Record<string, ESCaseStatus> = {
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf6': ESCaseStatus.OPEN,
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf7': ESCaseStatus.OPEN,
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf8': ESCaseStatus.IN_PROGRESS,
'cases:063d5820-1284-11ed-81af-63a2bdfb2bf9': ESCaseStatus.CLOSED,
};
const casesFromES = await getCaseSavedObjectsFromES({ es });
for (const hit of casesFromES.body.hits.hits) {
const caseID = hit._id;
expect(expectedStatusValues[caseID]).not.to.be(undefined);
expect(hit._source?.cases.status).to.eql(expectedStatusValues[caseID]);
}
});
});
});
});
}

View file

@ -26,7 +26,6 @@
"syncAlerts": false
},
"severity": "low",
"status": "open",
"tags": [
"super",
"awesome"
@ -122,7 +121,7 @@
"syncAlerts": false
},
"severity": "high",
"status": "open",
"status": "in-progress",
"tags": [
"super",
"awesome"
@ -170,7 +169,7 @@
"syncAlerts": false
},
"severity": "critical",
"status": "open",
"status": "closed",
"tags": [
"super",
"awesome"