From 42e992bc3fb702e4a5c70de08839e0dab9a6c87a Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 2 Feb 2021 14:06:43 +0200 Subject: [PATCH] Fix types --- .../case/common/api/connectors/mappings.ts | 29 ++- x-pack/plugins/case/server/client/mocks.ts | 9 +- .../routes/api/__mocks__/request_responses.ts | 4 +- .../server/routes/api/cases/configure/mock.ts | 80 +++--- .../cases/configure/post_push_to_service.ts | 9 +- .../routes/api/cases/configure/utils.test.ts | 239 +++++++++--------- .../routes/api/cases/configure/utils.ts | 16 +- x-pack/plugins/case/server/services/mocks.ts | 1 + .../components/connector_selector/form.tsx | 2 +- .../cases/components/edit_connector/index.tsx | 2 +- .../components/user_action_tree/index.tsx | 2 +- .../public/cases/containers/api.test.tsx | 40 ++- .../public/cases/containers/api.ts | 1 - .../public/cases/containers/mock.ts | 13 - .../use_post_push_to_service.test.tsx | 168 +----------- 15 files changed, 234 insertions(+), 381 deletions(-) diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/case/common/api/connectors/mappings.ts index 844ca82327d17a8..4d2d229af4ffc79 100644 --- a/x-pack/plugins/case/common/api/connectors/mappings.ts +++ b/x-pack/plugins/case/common/api/connectors/mappings.ts @@ -24,13 +24,7 @@ import { ResilientFieldsRT } from './resilient'; import { ServiceNowITSMFieldsRT } from './servicenow_itsm'; import { JiraFieldsRT } from './jira'; import { ServiceNowSIRFieldsRT } from './servicenow_sir'; - -// Formerly imported from security_solution -export interface ElasticUser { - readonly email?: string | null; - readonly fullName?: string | null; - readonly username?: string | null; -} +import { CaseResponse } from '../cases'; export { JiraPushToServiceApiParams, @@ -79,21 +73,34 @@ const ConnectorFieldRt = rt.type({ required: rt.boolean, type: FieldTypeRT, }); + export type ConnectorField = rt.TypeOf; export const ConnectorRequestParamsRt = rt.type({ connector_id: rt.string, }); + export const GetFieldsRequestQueryRt = rt.type({ connector_type: rt.string, }); + const GetFieldsResponseRt = rt.type({ defaultMappings: rt.array(ConnectorMappingsAttributesRT), fields: rt.array(ConnectorFieldRt), }); + export type GetFieldsResponse = rt.TypeOf; export type ExternalServiceParams = Record; +export interface BasicParams { + title: CaseResponse['title']; + description: CaseResponse['description']; + createdAt: CaseResponse['created_at']; + createdBy: CaseResponse['created_by']; + updatedAt: CaseResponse['updated_at']; + updatedBy: CaseResponse['updated_by']; +} + export interface PipedField { actionType: string; key: string; @@ -106,10 +113,10 @@ export interface PrepareFieldsForTransformArgs { params: { title: string; description: string }; } export interface EntityInformation { - createdAt: string; - createdBy: ElasticUser; - updatedAt: string | null; - updatedBy: ElasticUser | null; + createdAt: CaseResponse['created_at']; + createdBy: CaseResponse['created_by']; + updatedAt: CaseResponse['updated_at']; + updatedBy: CaseResponse['updated_by']; } export interface TransformerArgs { date?: string; diff --git a/x-pack/plugins/case/server/client/mocks.ts b/x-pack/plugins/case/server/client/mocks.ts index 2db00ff8ca6d685..09f0efc6f9a593e 100644 --- a/x-pack/plugins/case/server/client/mocks.ts +++ b/x-pack/plugins/case/server/client/mocks.ts @@ -25,8 +25,11 @@ export type CaseClientMock = jest.Mocked; export const createCaseClientMock = (): CaseClientMock => ({ addComment: jest.fn(), create: jest.fn(), + get: jest.fn(), + getAlerts: jest.fn(), getFields: jest.fn(), getMappings: jest.fn(), + getUserActions: jest.fn(), update: jest.fn(), updateAlertsStatus: jest.fn(), }); @@ -66,7 +69,11 @@ export const createCaseClientWithMockSavedObjectsClient = async ({ getUserActions: jest.fn(), }; - const alertsService = { initialize: jest.fn(), updateAlertsStatus: jest.fn() }; + const alertsService = { + initialize: jest.fn(), + updateAlertsStatus: jest.fn(), + getAlerts: jest.fn(), + }; const context = { core: { diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts index 236deb9c7462cd0..3cab7af8f08e042 100644 --- a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts @@ -12,7 +12,6 @@ import { } from '../../../../common/api'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FindActionResult } from '../../../../../actions/server/types'; -import { params } from '../cases/configure/mock'; export const newCase: CasePostRequest = { title: 'My new case', @@ -128,8 +127,7 @@ export const newConfiguration: CasesConfigureRequest = { }; export const newPostPushRequest: PostPushRequest = { - params: params[ConnectorTypes.jira], - connector_type: ConnectorTypes.jira, + case_id: '123', }; export const executePushResponse = { diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts index e6f2403a107d6df..f7435cf852d6039 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts @@ -4,68 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ import { - ServiceConnectorCaseParams, - ServiceConnectorCommentParams, + BasicParams, + CommentResponse, + CommentType, ConnectorMappingsAttributes, - ConnectorTypes, -} from '../../../../../common/api/connectors'; +} from '../../../../../common/api'; + export const updateUser = { updatedAt: '2020-03-13T08:34:53.450Z', - updatedBy: { fullName: 'Another User', username: 'another' }, + updatedBy: { full_name: 'Another User', username: 'another' }, }; + const entity = { createdAt: '2020-03-13T08:34:53.450Z', - createdBy: { fullName: 'Elastic User', username: 'elastic' }, + createdBy: { full_name: 'Elastic User', username: 'elastic', email: 'elastic@elastic.co' }, updatedAt: null, updatedBy: null, }; -export const comment: ServiceConnectorCommentParams = { - comment: 'first comment', - commentId: 'b5b4c4d0-574e-11ea-9e2e-21b90f8a9631', - ...entity, + +export const comment: CommentResponse = { + id: 'mock-comment-1', + comment: 'Wow, good luck catching that bad meanie!', + type: CommentType.user as const, + created_at: '2019-11-25T21:55:00.177Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + pushed_at: null, + pushed_by: null, + updated_at: '2019-11-25T21:55:00.177Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + version: 'WzEsMV0=', }; + export const defaultPipes = ['informationCreated']; -const basicParams = { - comments: [comment], +export const basicParams: BasicParams = { description: 'a description', title: 'a title', - savedObjectId: '1231231231232', - externalId: null, -}; -export const params = { - [ConnectorTypes.jira]: { - ...basicParams, - issueType: '10003', - priority: 'Highest', - parent: '5002', - ...entity, - } as ServiceConnectorCaseParams, - [ConnectorTypes.resilient]: { - ...basicParams, - incidentTypes: ['10003'], - severityCode: '1', - ...entity, - } as ServiceConnectorCaseParams, - [ConnectorTypes.serviceNowITSM]: { - ...basicParams, - impact: '3', - severity: '1', - urgency: '2', - ...entity, - } as ServiceConnectorCaseParams, - [ConnectorTypes.serviceNowSIR]: { - ...basicParams, - category: 'Denial of Service', - destIp: '192.68.1.1', - sourceIp: '192.68.1.2', - malwareHash: '098f6bcd4621d373cade4e832627b4f6', - malwareUrl: 'https://attack.com', - priority: '1', - subcategory: '20', - ...entity, - } as ServiceConnectorCaseParams, - [ConnectorTypes.none]: {}, + ...entity, }; + export const mappings: ConnectorMappingsAttributes[] = [ { source: 'title', diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts index 4a2dd6532b2a377..a2b11d713dd1612 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts @@ -15,12 +15,10 @@ import { CASE_CONFIGURE_PUSH_URL } from '../../../../../common/constants'; import { ActionConnector, ConnectorRequestParamsRt, - CommentResponseAlertsType, PostPushRequestRt, throwErrors, - CommentType, } from '../../../../../common/api'; -import { createIncident } from './utils'; +import { createIncident, isCommentAlertType } from './utils'; export function initPostPushToService({ router }: RouteDeps) { router.post( @@ -58,10 +56,7 @@ export function initPostPushToService({ router }: RouteDeps) { const theCase = await caseClient.get({ id: body.case_id, includeComments: true }); const userActions = await caseClient.getUserActions({ caseId: body.case_id }); const alerts = await caseClient.getAlerts({ - ids: - theCase.comments - ?.filter((comment) => comment.type === CommentType.alert) - .map((comment) => comment.alertId) ?? [], + ids: theCase.comments?.filter(isCommentAlertType).map((comment) => comment.alertId) ?? [], }); const connectorMappings = await caseClient.getMappings({ diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts index ce78768436ff09e..7f85aaacdc0fcbd 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts @@ -4,34 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ +import { BasicParams, ExternalServiceParams, Incident } from '../../../../../common/api'; + import { - mapIncident, + createIncident, prepareFieldsForTransformation, - serviceFormatter, transformComments, transformers, transformFields, } from './utils'; -import { comment as commentObj, mappings, defaultPipes, params, updateUser } from './mock'; -import { - ConnectorTypes, - ExternalServiceParams, - Incident, - ServiceConnectorCaseParams, -} from '../../../../../common/api/connectors'; +import { comment as commentObj, mappings, defaultPipes, basicParams, updateUser } from './mock'; import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; -import { mappings as mappingsMock } from '../../../../client/configure/mock'; -const formatComment = { commentId: commentObj.commentId, comment: commentObj.comment }; -const serviceNowParams = params[ConnectorTypes.serviceNowITSM] as ServiceConnectorCaseParams; +import { flattenCaseSavedObject } from '../../utils'; +import { mockCases } from '../../__fixtures__'; + +const formatComment = { + commentId: commentObj.id, + comment: 'Wow, good luck catching that bad meanie!', +}; + +const params = { ...basicParams }; + describe('api/cases/configure/utils', () => { describe('prepareFieldsForTransformation', () => { test('prepare fields with defaults', () => { const res = prepareFieldsForTransformation({ defaultPipes, - params: serviceNowParams, + params, mappings, }); + expect(res).toEqual([ { actionType: 'overwrite', @@ -52,8 +55,9 @@ describe('api/cases/configure/utils', () => { const res = prepareFieldsForTransformation({ defaultPipes: ['myTestPipe'], mappings, - params: serviceNowParams, + params, }); + expect(res).toEqual([ { actionType: 'overwrite', @@ -70,16 +74,17 @@ describe('api/cases/configure/utils', () => { ]); }); }); + describe('transformFields', () => { test('transform fields for creation correctly', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params: serviceNowParams, + params, }); - const res = transformFields({ - params: serviceNowParams, + const res = transformFields({ + params, fields, }); @@ -91,18 +96,19 @@ describe('api/cases/configure/utils', () => { test('transform fields for update correctly', () => { const fields = prepareFieldsForTransformation({ - params: serviceNowParams, + params, mappings, defaultPipes: ['informationUpdated'], }); - const res = transformFields({ + const res = transformFields({ params: { - ...serviceNowParams, + ...params, updatedAt: '2020-03-15T08:34:53.450Z', updatedBy: { username: 'anotherUser', - fullName: 'Another User', + full_name: 'Another User', + email: 'elastic@elastic.co', }, }, fields, @@ -111,6 +117,7 @@ describe('api/cases/configure/utils', () => { description: 'first description (created at 2020-03-13T08:34:53.450Z by Elastic User)', }, }); + expect(res).toEqual({ short_description: 'a title (updated at 2020-03-15T08:34:53.450Z by Another User)', description: @@ -120,13 +127,13 @@ describe('api/cases/configure/utils', () => { test('add newline character to description', () => { const fields = prepareFieldsForTransformation({ - params: serviceNowParams, + params, mappings, defaultPipes: ['informationUpdated'], }); - const res = transformFields({ - params: serviceNowParams, + const res = transformFields({ + params, fields, currentIncident: { short_description: 'first title', @@ -140,13 +147,13 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params: serviceNowParams, + params, }); - const res = transformFields({ + const res = transformFields({ params: { - ...serviceNowParams, - createdBy: { fullName: '', username: 'elastic' }, + ...params, + createdBy: { full_name: '', username: 'elastic', email: 'elastic@elastic.co' }, }, fields, }); @@ -161,14 +168,14 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes: ['informationUpdated'], mappings, - params: serviceNowParams, + params, }); - const res = transformFields({ + const res = transformFields({ params: { - ...serviceNowParams, + ...params, updatedAt: '2020-03-15T08:34:53.450Z', - updatedBy: { username: 'anotherUser', fullName: '' }, + updatedBy: { username: 'anotherUser', full_name: '', email: 'elastic@elastic.co' }, }, fields, }); @@ -186,7 +193,7 @@ describe('api/cases/configure/utils', () => { expect(res).toEqual([ { ...formatComment, - comment: `${formatComment.comment} (created at ${comments[0].createdAt} by ${comments[0].createdBy.fullName})`, + comment: `${formatComment.comment} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, }, ]); }); @@ -202,7 +209,7 @@ describe('api/cases/configure/utils', () => { expect(res).toEqual([ { ...formatComment, - comment: `${formatComment.comment} (updated at ${updateUser.updatedAt} by ${updateUser.updatedBy.fullName})`, + comment: `${formatComment.comment} (updated at ${updateUser.updatedAt} by ${updateUser.updatedBy.full_name})`, }, ]); }); @@ -213,19 +220,19 @@ describe('api/cases/configure/utils', () => { expect(res).toEqual([ { ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].createdAt} by ${comments[0].createdBy.fullName})`, + comment: `${formatComment.comment} (added at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, }, ]); }); test('transform comments without fullname', () => { - const comments = [{ ...commentObj, createdBy: { username: commentObj.createdBy.username } }]; - // @ts-ignore testing no fullName + const comments = [{ ...commentObj, createdBy: { username: commentObj.created_by.username } }]; + // @ts-ignore testing no full_name const res = transformComments(comments, ['informationAdded']); expect(res).toEqual([ { ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].createdAt} by ${comments[0].createdBy.username})`, + comment: `${formatComment.comment} (added at ${comments[0].created_at} by ${comments[0].created_by.username})`, }, ]); }); @@ -235,14 +242,14 @@ describe('api/cases/configure/utils', () => { { ...commentObj, updatedAt: '2020-04-13T08:34:53.450Z', - updatedBy: { fullName: 'Elastic2', username: 'elastic' }, + updatedBy: { full_name: 'Elastic2', username: 'elastic' }, }, ]; const res = transformComments(comments, ['informationAdded']); expect(res).toEqual([ { ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].updatedAt} by ${comments[0].updatedBy.fullName})`, + comment: `${formatComment.comment} (added at ${comments[0].updatedAt} by ${comments[0].updatedBy.full_name})`, }, ]); }); @@ -252,7 +259,7 @@ describe('api/cases/configure/utils', () => { { ...commentObj, updatedAt: '2020-04-13T08:34:53.450Z', - updatedBy: { fullName: '', username: 'elastic2' }, + updatedBy: { full_name: '', username: 'elastic2' }, }, ]; const res = transformComments(comments, ['informationAdded']); @@ -388,16 +395,30 @@ describe('api/cases/configure/utils', () => { }); }); }); - describe('mapIncident', () => { + describe('createIncident', () => { let actionsMock = actionsClientMock.create(); + const theCase = flattenCaseSavedObject({ + savedObject: mockCases[0], + }); + it('maps an external incident', async () => { - const res = await mapIncident( - actionsMock, - '123', - ConnectorTypes.serviceNowITSM, - mappingsMock[ConnectorTypes.serviceNowITSM], - serviceNowParams - ); + const res = await createIncident({ + actionsClient: actionsMock, + theCase, + userActions: [], + connector: { + id: '456', + actionTypeId: '.jira', + name: 'Connector without isCaseOwned', + config: { + apiUrl: 'https://elastic.jira.com', + }, + isPreconfigured: false, + }, + mappings: [], + alerts: [], + }); + expect(res).toEqual({ incident: { description: 'a description (created at 2020-03-13T08:34:53.450Z by Elastic User)', @@ -415,18 +436,29 @@ describe('api/cases/configure/utils', () => { ], }); }); + it('throws error if invalid service', async () => { - await mapIncident( - actionsMock, - '123', - 'invalid', - mappingsMock[ConnectorTypes.serviceNowITSM], - serviceNowParams - ).catch((e) => { + await createIncident({ + actionsClient: actionsMock, + theCase, + userActions: [], + connector: { + id: '456', + actionTypeId: '.jira', + name: 'Connector without isCaseOwned', + config: { + apiUrl: 'https://elastic.jira.com', + }, + isPreconfigured: false, + }, + mappings: [], + alerts: [], + }).catch((e) => { expect(e).not.toBeNull(); expect(e).toEqual(new Error(`Invalid service`)); }); }); + it('updates an existing incident', async () => { const existingIncidentData = { description: 'fun description', @@ -435,15 +467,27 @@ describe('api/cases/configure/utils', () => { short_description: 'fun title', urgency: '3', }; + const execute = jest.fn().mockReturnValue(existingIncidentData); actionsMock = { ...actionsMock, execute }; - const res = await mapIncident( - actionsMock, - '123', - ConnectorTypes.serviceNowITSM, - mappingsMock[ConnectorTypes.serviceNowITSM], - { ...serviceNowParams, externalId: '123' } - ); + + const res = await createIncident({ + actionsClient: actionsMock, + theCase, + userActions: [], + connector: { + id: '456', + actionTypeId: '.jira', + name: 'Connector without isCaseOwned', + config: { + apiUrl: 'https://elastic.jira.com', + }, + isPreconfigured: false, + }, + mappings: [], + alerts: [], + }); + expect(res).toEqual({ incident: { description: 'a description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', @@ -461,18 +505,29 @@ describe('api/cases/configure/utils', () => { ], }); }); + it('throws error when existing incident throws', async () => { const execute = jest.fn().mockImplementation(() => { throw new Error('exception'); }); + actionsMock = { ...actionsMock, execute }; - await mapIncident( - actionsMock, - '123', - ConnectorTypes.serviceNowITSM, - mappingsMock[ConnectorTypes.serviceNowITSM], - { ...serviceNowParams, externalId: '123' } - ).catch((e) => { + await createIncident({ + actionsClient: actionsMock, + theCase, + userActions: [], + connector: { + id: '456', + actionTypeId: '.jira', + name: 'Connector without isCaseOwned', + config: { + apiUrl: 'https://elastic.jira.com', + }, + isPreconfigured: false, + }, + mappings: [], + alerts: [], + }).catch((e) => { expect(e).not.toBeNull(); expect(e).toEqual( new Error( @@ -482,48 +537,4 @@ describe('api/cases/configure/utils', () => { }); }); }); - - const connectors = [ - { - name: ConnectorTypes.jira, - result: { - incident: { - issueType: '10003', - parent: '5002', - priority: 'Highest', - }, - thirdPartyName: 'Jira', - }, - }, - { - name: ConnectorTypes.resilient, - result: { - incident: { - incidentTypes: ['10003'], - severityCode: '1', - }, - thirdPartyName: 'Resilient', - }, - }, - { - name: ConnectorTypes.serviceNowITSM, - result: { - incident: { - impact: '3', - severity: '1', - urgency: '2', - }, - thirdPartyName: 'ServiceNow', - }, - }, - ]; - describe('serviceFormatter', () => { - connectors.forEach((c) => - it(`formats ${c.name}`, () => { - const caseParams = params[c.name] as ServiceConnectorCaseParams; - const res = serviceFormatter(c.name, caseParams); - expect(res).toEqual(c.result); - }) - ); - }); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts index 64301f54b364d0d..411aebda76abada 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts @@ -7,10 +7,12 @@ import { i18n } from '@kbn/i18n'; import { flow } from 'lodash'; import { ActionConnector, + BasicParams, CaseResponse, CaseFullExternalService, CaseUserActionsResponse, CommentResponse, + CommentResponseAlertsType, CommentType, ConnectorMappingsAttributes, ConnectorTypes, @@ -129,7 +131,7 @@ export const createIncident = async ({ params, }); - const transformedFields = transformFields({ + const transformedFields = transformFields({ params, fields, currentIncident, @@ -162,12 +164,12 @@ export const createIncident = async ({ export const getEntity = (entity: EntityInformation): string => (entity.updatedBy != null - ? entity.updatedBy.fullName - ? entity.updatedBy.fullName + ? entity.updatedBy.full_name + ? entity.updatedBy.full_name : entity.updatedBy.username : entity.createdBy != null - ? entity.createdBy.fullName - ? entity.createdBy.fullName + ? entity.createdBy.full_name + ? entity.createdBy.full_name : entity.createdBy.username : '') ?? ''; @@ -283,3 +285,7 @@ export const transformComments = ( }).value, commentId: c.id, })); + +export const isCommentAlertType = ( + comment: CommentResponse +): comment is CommentResponseAlertsType => comment.type === CommentType.alert; diff --git a/x-pack/plugins/case/server/services/mocks.ts b/x-pack/plugins/case/server/services/mocks.ts index 65f2c845bb40074..a1b982906784c19 100644 --- a/x-pack/plugins/case/server/services/mocks.ts +++ b/x-pack/plugins/case/server/services/mocks.ts @@ -58,4 +58,5 @@ export const createUserActionServiceMock = (): CaseUserActionServiceMock => ({ export const createAlertServiceMock = (): AlertServiceMock => ({ initialize: jest.fn(), updateAlertsStatus: jest.fn(), + getAlerts: jest.fn(), }); diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx index 9017365eea02be0..033eb97d953ba38 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx @@ -10,7 +10,7 @@ import { EuiFormRow } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; -import { ActionConnector } from '../../../../../case/common/api/cases'; +import { ActionConnector } from '../../../../../case/common/api'; interface ConnectorSelectorProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx index 0a8e35e9da80f89..4fbc85699f15c90 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx @@ -22,7 +22,7 @@ import { noop } from 'lodash/fp'; import { Form, UseField, useForm } from '../../../shared_imports'; import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; import { ConnectorSelector } from '../connector_selector/form'; -import { ActionConnector } from '../../../../../case/common/api/cases'; +import { ActionConnector } from '../../../../../case/common/api'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { getConnectorById } from '../configure_cases/utils'; import { CaseUserActions } from '../../containers/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx index 5c2590825d1b2fb..430bd245db1409e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx @@ -22,7 +22,7 @@ import { Case, CaseUserActions } from '../../containers/types'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser } from '../../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; -import { ActionConnector, CommentType } from '../../../../../case/common/api/cases'; +import { ActionConnector, CommentType } from '../../../../../case/common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; import { Alert, OnUpdateFields } from '../case_view'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx index 9c23081bac53581..9ce0c2da57e8791 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx @@ -44,7 +44,7 @@ import { reporters, respReporters, serviceConnector, - casePushParams, + basicCaseId, tags, caseUserActionsSnake, casesStatusSnake, @@ -83,11 +83,13 @@ describe('Case Configuration API', () => { expect(resp).toEqual(''); }); }); + describe('getActionLicense', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(actionLicenses); }); + test('check url, method, signal', async () => { await getActionLicense(abortCtrl.signal); expect(fetchMock).toHaveBeenCalledWith(`/api/actions/list_action_types`, { @@ -101,6 +103,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(actionLicenses); }); }); + describe('getCase', () => { beforeEach(() => { fetchMock.mockClear(); @@ -122,6 +125,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(basicCase); }); }); + describe('getCases', () => { beforeEach(() => { fetchMock.mockClear(); @@ -144,6 +148,7 @@ describe('Case Configuration API', () => { signal: abortCtrl.signal, }); }); + test('correctly applies filters', async () => { await getCases({ filterOptions: { @@ -168,6 +173,7 @@ describe('Case Configuration API', () => { signal: abortCtrl.signal, }); }); + test('tags with weird chars get handled gracefully', async () => { const weirdTags: string[] = ['(', '"double"']; @@ -204,6 +210,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual({ ...allCases }); }); }); + describe('getCasesStatus', () => { beforeEach(() => { fetchMock.mockClear(); @@ -222,6 +229,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(casesStatus); }); }); + describe('getCaseUserActions', () => { beforeEach(() => { fetchMock.mockClear(); @@ -241,6 +249,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(caseUserActions); }); }); + describe('getReporters', () => { beforeEach(() => { fetchMock.mockClear(); @@ -260,6 +269,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(respReporters); }); }); + describe('getTags', () => { beforeEach(() => { fetchMock.mockClear(); @@ -279,6 +289,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(tags); }); }); + describe('patchCase', () => { beforeEach(() => { fetchMock.mockClear(); @@ -306,6 +317,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual({ ...[basicCase] }); }); }); + describe('patchCasesStatus', () => { beforeEach(() => { fetchMock.mockClear(); @@ -333,6 +345,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual({ ...cases }); }); }); + describe('patchComment', () => { beforeEach(() => { fetchMock.mockClear(); @@ -370,6 +383,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(basicCase); }); }); + describe('postCase', () => { beforeEach(() => { fetchMock.mockClear(); @@ -404,6 +418,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(basicCase); }); }); + describe('postComment', () => { beforeEach(() => { fetchMock.mockClear(); @@ -428,6 +443,7 @@ describe('Case Configuration API', () => { expect(resp).toEqual(basicCase); }); }); + describe('pushCase', () => { beforeEach(() => { fetchMock.mockClear(); @@ -448,31 +464,31 @@ describe('Case Configuration API', () => { expect(resp).toEqual(pushedCase); }); }); + describe('pushToService', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(actionTypeExecutorResult); }); + const connectorId = 'connectorId'; + test('check url, method, signal', async () => { - await pushToService(connectorId, ConnectorTypes.jira, casePushParams, abortCtrl.signal); + await pushToService(connectorId, basicCaseId, abortCtrl.signal); expect(fetchMock).toHaveBeenCalledWith(`${getCaseConfigurePushUrl(connectorId)}`, { method: 'POST', body: JSON.stringify({ connector_type: ConnectorTypes.jira, - params: casePushParams, + params: { + case_id: basicCaseId, + }, }), signal: abortCtrl.signal, }); }); test('happy path', async () => { - const resp = await pushToService( - connectorId, - ConnectorTypes.jira, - casePushParams, - abortCtrl.signal - ); + const resp = await pushToService(connectorId, basicCaseId, abortCtrl.signal); expect(resp).toEqual(serviceConnector); }); @@ -485,7 +501,7 @@ describe('Case Configuration API', () => { message: 'not it', }); await expect( - pushToService(connectorId, ConnectorTypes.jira, casePushParams, abortCtrl.signal) + await pushToService(connectorId, basicCaseId, abortCtrl.signal) ).rejects.toMatchObject({ message: theError }); }); @@ -497,7 +513,7 @@ describe('Case Configuration API', () => { message: theError, }); await expect( - pushToService(connectorId, ConnectorTypes.jira, casePushParams, abortCtrl.signal) + await pushToService(connectorId, basicCaseId, abortCtrl.signal) ).rejects.toMatchObject({ message: theError }); }); @@ -508,7 +524,7 @@ describe('Case Configuration API', () => { status: 'error', }); await expect( - pushToService(connectorId, ConnectorTypes.jira, casePushParams, abortCtrl.signal) + await pushToService(connectorId, basicCaseId, abortCtrl.signal) ).rejects.toMatchObject({ message: theError }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index 13424b852c167c2..e040e93c074cf7e 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -16,7 +16,6 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, - ServiceConnectorCaseParams, ServiceConnectorCaseResponse, User, } from '../../../../case/common/api'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index 3fb962df232bc09..494d0959cf721ea 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -157,19 +157,6 @@ const basicAction = { commentId: null, }; -export const casePushParams = { - savedObjectId: basicCaseId, - createdAt: basicCreatedAt, - createdBy: elasticUser, - externalId: null, - title: 'what a cool value', - commentId: null, - updatedAt: basicCreatedAt, - updatedBy: elasticUser, - description: 'nice', - comments: null, -}; - export const actionTypeExecutorResult = { actionId: 'string', status: 'ok', diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx index 645dd80a21e66d5..b1f68b0e9c27cd8 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx @@ -5,23 +5,11 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { - formatServiceRequestData, - usePostPushToService, - UsePostPushToService, -} from './use_post_push_to_service'; -import { - basicCase, - basicComment, - basicPush, - pushedCase, - serviceConnector, - serviceConnectorUser, -} from './mock'; +import { usePostPushToService, UsePostPushToService } from './use_post_push_to_service'; +import { basicComment, basicPush, pushedCase, serviceConnector } from './mock'; import * as api from './api'; -import { CaseServices } from './use_get_case_user_actions'; -import { CaseConnector, ConnectorTypes, CommentType } from '../../../../case/common/api'; -import moment from 'moment'; +import { CaseConnector, ConnectorTypes } from '../../../../case/common/api'; + jest.mock('./api'); jest.mock('../../common/components/link_to', () => { const originalModule = jest.requireActual('../../common/components/link_to'); @@ -31,10 +19,10 @@ jest.mock('../../common/components/link_to', () => { useFormatUrl: jest.fn().mockReturnValue({ formatUrl: jest.fn(), search: 'urlSearch' }), }; }); + describe('usePostPushToService', () => { const abortCtrl = new AbortController(); const updateCase = jest.fn(); - const formatUrl = jest.fn(); const samplePush = { caseId: pushedCase.id, @@ -69,49 +57,6 @@ describe('usePostPushToService', () => { }, }; - const sampleServiceRequestData = { - savedObjectId: pushedCase.id, - createdAt: pushedCase.createdAt, - createdBy: serviceConnectorUser, - comments: [ - { - commentId: basicComment.id, - comment: basicComment.type === CommentType.user ? basicComment.comment : '', - createdAt: basicComment.createdAt, - createdBy: serviceConnectorUser, - updatedAt: null, - updatedBy: null, - }, - ], - externalId: basicPush.externalId, - description: pushedCase.description, - title: pushedCase.title, - updatedAt: pushedCase.updatedAt, - updatedBy: serviceConnectorUser, - issueType: 'Task', - parent: null, - priority: 'Low', - }; - - const sampleCaseServices = { - '123': { - ...basicPush, - firstPushIndex: 1, - lastPushIndex: 1, - commentsToUpdate: [basicComment.id], - hasDataToPush: true, - }, - '456': { - ...basicPush, - connectorId: '456', - externalId: 'other_external_id', - firstPushIndex: 4, - commentsToUpdate: [basicComment.id], - lastPushIndex: 6, - hasDataToPush: false, - }, - }; - it('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => @@ -165,13 +110,6 @@ describe('usePostPushToService', () => { expect(spyOnPushToService).toBeCalledWith( samplePush.connector.id, samplePush.connector.type, - formatServiceRequestData({ - myCase: basicCase, - connector: samplePush.connector, - caseServices: sampleCaseServices as CaseServices, - alerts: samplePush.alerts, - formatUrl, - }), abortCtrl.signal ); }); @@ -202,13 +140,6 @@ describe('usePostPushToService', () => { expect(spyOnPushToService).toBeCalledWith( samplePush2.connector.id, samplePush2.connector.type, - formatServiceRequestData({ - myCase: basicCase, - connector: samplePush2.connector, - caseServices: {}, - alerts: samplePush.alerts, - formatUrl, - }), abortCtrl.signal ); }); @@ -243,95 +174,6 @@ describe('usePostPushToService', () => { }); }); - it('formatServiceRequestData - current connector', () => { - const caseServices = sampleCaseServices; - const result = formatServiceRequestData({ - myCase: pushedCase, - connector: samplePush.connector, - caseServices, - alerts: samplePush.alerts, - formatUrl, - }); - expect(result).toEqual(sampleServiceRequestData); - }); - - it('formatServiceRequestData - connector with history', () => { - const caseServices = sampleCaseServices; - const connector = { - id: '456', - name: 'connector 2', - type: ConnectorTypes.jira, - fields: { issueType: 'Task', priority: 'High', parent: 'RJ-01' }, - }; - const result = formatServiceRequestData({ - myCase: pushedCase, - connector: connector as CaseConnector, - caseServices, - alerts: samplePush.alerts, - formatUrl, - }); - expect(result).toEqual({ - ...sampleServiceRequestData, - ...connector.fields, - externalId: 'other_external_id', - }); - }); - - it('formatServiceRequestData - new connector', () => { - const caseServices = { - '123': sampleCaseServices['123'], - }; - - const connector = { - id: '456', - name: 'connector 2', - type: ConnectorTypes.jira, - fields: { issueType: 'Task', priority: 'High', parent: null }, - }; - - const result = formatServiceRequestData({ - myCase: pushedCase, - connector: connector as CaseConnector, - caseServices, - alerts: samplePush.alerts, - formatUrl, - }); - - expect(result).toEqual({ - ...sampleServiceRequestData, - ...connector.fields, - externalId: null, - }); - }); - - it('formatServiceRequestData - Alert comment content', () => { - const mockDuration = moment.duration(1); - jest.spyOn(moment, 'duration').mockReturnValue(mockDuration); - formatUrl.mockReturnValue('https://app.com/detections'); - const caseServices = sampleCaseServices; - const result = formatServiceRequestData({ - myCase: { - ...pushedCase, - comments: [ - { - ...pushedCase.comments[0], - type: CommentType.alert, - alertId: 'alert-id-1', - index: 'alert-index-1', - }, - ], - }, - connector: samplePush.connector, - caseServices, - alerts: samplePush.alerts, - formatUrl, - }); - - expect(result.comments![0].comment).toEqual( - '[Alert](https://app.com/detections?filters=!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,key:_id,negate:!f,params:(query:alert-id-1),type:phrase),query:(match:(_id:(query:alert-id-1,type:phrase)))))&sourcerer=(default:!())&timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-11-20T15:35:28.372Z%27,kind:absolute,to:%272020-11-20T15:35:28.373Z%27)),timeline:(linkTo:!(global),timerange:(from:%272020-11-20T15:35:28.372Z%27,kind:absolute,to:%272020-11-20T15:35:28.373Z%27)))) added to case.' - ); - }); - it('unhappy path', async () => { const spyOnPushToService = jest.spyOn(api, 'pushToService'); spyOnPushToService.mockImplementation(() => {