From 267ca1fb93d8e750e1a35d3c930938f90d3d7a5a Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 10 Dec 2020 16:17:47 +0200 Subject: [PATCH] [Security Solution][Case] Alerts comment UI (#84450) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> --- .../common/types/timeline/index.ts | 1 + .../cases/components/case_view/helpers.ts | 29 +++++ .../cases/components/case_view/index.test.tsx | 42 ++++++- .../cases/components/case_view/index.tsx | 113 +++++++++++++++++- .../components/user_action_tree/helpers.tsx | 56 ++++++++- .../user_action_tree/index.test.tsx | 4 + .../components/user_action_tree/index.tsx | 30 ++++- .../user_action_tree/translations.ts | 28 +++++ .../user_action_alert_comment_event.tsx | 48 ++++++++ .../user_action_show_alert.tsx | 49 ++++++++ .../public/cases/containers/mock.ts | 3 +- .../public/cases/containers/types.ts | 8 +- .../use_post_push_to_service.test.tsx | 4 +- .../containers/use_post_push_to_service.tsx | 3 +- .../public/common/components/link_to/index.ts | 2 +- 15 files changed, 398 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts create mode 100644 x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 4b26b4157da0c..e447a004fb51c 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -276,6 +276,7 @@ export enum TimelineId { detectionsPage = 'detections-page', networkPageExternalAlerts = 'network-page-external-alerts', active = 'timeline-1', + casePage = 'timeline-case', test = 'test', // Reserved for testing purposes } diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts new file mode 100644 index 0000000000000..1d8354de74d82 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CommentType } from '../../../../../case/common/api'; +import { Comment } from '../../containers/types'; + +export const getRuleIdsFromComments = (comments: Comment[]) => + comments.reduce((ruleIds, comment: Comment) => { + if (comment.type === CommentType.alert) { + return [...ruleIds, comment.alertId]; + } + + return ruleIds; + }, []); + +export const buildAlertsQuery = (ruleIds: string[]) => ({ + query: { + bool: { + filter: { + bool: { + should: ruleIds.map((_id) => ({ match: { _id } })), + minimum_should_match: 1, + }, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index 4dbfaa9669ece..34b71dd301d10 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -19,21 +19,32 @@ import { act, waitFor } from '@testing-library/react'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; - import { usePostPushToService } from '../../containers/use_post_push_to_service'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); + jest.mock('../../containers/use_update_case'); jest.mock('../../containers/use_get_case_user_actions'); jest.mock('../../containers/use_get_case'); jest.mock('../../containers/configure/use_connectors'); jest.mock('../../containers/use_post_push_to_service'); +jest.mock('../../../detections/containers/detection_engine/alerts/use_query'); jest.mock('../user_action_tree/user_action_timestamp'); const useUpdateCaseMock = useUpdateCase as jest.Mock; const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock; const useConnectorsMock = useConnectors as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; +const useQueryAlertsMock = useQueryAlerts as jest.Mock; export const caseProps: CaseProps = { caseId: basicCase.id, @@ -99,6 +110,10 @@ describe('CaseView ', () => { useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions); usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService })); useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false })); + useQueryAlertsMock.mockImplementation(() => ({ + isLoading: false, + alerts: { hits: { hists: [] } }, + })); }); it('should render CaseComponent', async () => { @@ -435,6 +450,7 @@ describe('CaseView ', () => { ).toBeTruthy(); }); }); + // TO DO fix when the useEffects in edit_connector are cleaned up it.skip('should revert to the initial connector in case of failure', async () => { updateCaseProperty.mockImplementation(({ onError }) => { @@ -486,6 +502,7 @@ describe('CaseView ', () => { ).toBe(connectorName); }); }); + // TO DO fix when the useEffects in edit_connector are cleaned up it.skip('should update connector', async () => { const wrapper = mount( @@ -539,4 +556,27 @@ describe('CaseView ', () => { }, }); }); + + it('it should create a new timeline on mount', async () => { + mount( + + + + + + ); + + await waitFor(() => { + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'x-pack/security_solution/local/timeline/CREATE_TIMELINE', + payload: { + columns: [], + expandedEvent: {}, + id: 'timeline-case', + indexNames: [], + show: false, + }, + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index a338f4af6cda3..e5a673b03449f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { useDispatch } from 'react-redux'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash/fp'; import { EuiFlexGroup, EuiFlexItem, @@ -11,9 +15,6 @@ import { EuiLoadingSpinner, EuiHorizontalRule, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import styled from 'styled-components'; -import { isEmpty } from 'lodash/fp'; import { CaseStatuses } from '../../../../../case/common/api'; import { Case, CaseConnector } from '../../containers/types'; @@ -40,6 +41,13 @@ import { normalizeActionConnector, getNoneConnector, } from '../configure_cases/utils'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; +import { buildAlertsQuery, getRuleIdsFromComments } from './helpers'; +import { EventDetailsFlyout } from '../../../common/components/events_viewer/event_details_flyout'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { TimelineId } from '../../../../common/types/timeline'; +import { timelineActions } from '../../../timelines/store/timeline'; import { StatusActionButton } from '../status/button'; import * as i18n from './translations'; @@ -78,12 +86,34 @@ export interface CaseProps extends Props { updateCase: (newCase: Case) => void; } +interface Signal { + rule: { + id: string; + name: string; + }; +} + +interface SignalHit { + _id: string; + _index: string; + _source: { + signal: Signal; + }; +} + +export type Alert = { + _id: string; + _index: string; +} & Signal; + export const CaseComponent = React.memo( ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { + const dispatch = useDispatch(); const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const allCasesLink = getCaseUrl(search); const caseDetailsLink = formatUrl(getCaseDetailsUrl({ id: caseId }), { absolute: true }); const [initLoadingData, setInitLoadingData] = useState(true); + const init = useRef(true); const { caseUserActions, @@ -98,6 +128,39 @@ export const CaseComponent = React.memo( caseId, }); + const alertsQuery = useMemo(() => buildAlertsQuery(getRuleIdsFromComments(caseData.comments)), [ + caseData.comments, + ]); + + /** + * For the future developer: useSourcererScope is security solution dependent. + * You can use useSignalIndex as an alternative. + */ + const { browserFields, docValueFields, selectedPatterns } = useSourcererScope( + SourcererScopeName.detections + ); + + const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts( + alertsQuery, + selectedPatterns[0] + ); + + const alerts = useMemo( + () => + alertsData?.hits.hits.reduce>( + (acc, { _id, _index, _source }) => ({ + ...acc, + [_id]: { + _id, + _index, + ..._source.signal, + }, + }), + {} + ) ?? {}, + [alertsData?.hits.hits] + ); + // Update Fields const onUpdateField = useCallback( ({ key, value, onSuccess, onError }: OnUpdateFields) => { @@ -266,10 +329,10 @@ export const CaseComponent = React.memo( ); useEffect(() => { - if (initLoadingData && !isLoadingUserActions) { + if (initLoadingData && !isLoadingUserActions && !isLoadingAlerts) { setInitLoadingData(false); } - }, [initLoadingData, isLoadingUserActions]); + }, [initLoadingData, isLoadingAlerts, isLoadingUserActions]); const backOptions = useMemo( () => ({ @@ -281,6 +344,39 @@ export const CaseComponent = React.memo( [allCasesLink] ); + const showAlert = useCallback( + (alertId: string, index: string) => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId: TimelineId.casePage, + event: { + eventId: alertId, + indexName: index, + loading: false, + }, + }) + ); + }, + [dispatch] + ); + + // useEffect used for component's initialization + useEffect(() => { + if (init.current) { + init.current = false; + // We need to create a timeline to show the details view + dispatch( + timelineActions.createTimeline({ + id: TimelineId.casePage, + columns: [], + indexNames: [], + expandedEvent: {}, + show: false, + }) + ); + } + }, [dispatch]); + return ( <> @@ -327,6 +423,8 @@ export const CaseComponent = React.memo( onUpdateField={onUpdateField} updateCase={updateCase} userCanCrud={userCanCrud} + alerts={alerts} + onShowAlertDetails={showAlert} /> @@ -381,6 +479,11 @@ export const CaseComponent = React.memo( + ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx index 533a55426831e..0fef2accb2e21 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiCommentProps } from '@elastic/eui'; import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiCommentProps, EuiIconTip } from '@elastic/eui'; import { CaseFullExternalService, @@ -21,7 +21,10 @@ import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionCopyLink } from './user_action_copy_link'; import { UserActionMoveToReference } from './user_action_move_to_reference'; import { Status, statuses } from '../status'; -import * as i18n from '../case_view/translations'; +import { UserActionShowAlert } from './user_action_show_alert'; +import * as i18n from './translations'; +import { Alert } from '../case_view'; +import { AlertCommentEvent } from './user_action_alert_comment_event'; interface LabelTitle { action: CaseUserActions; @@ -182,3 +185,52 @@ export const getUpdateAction = ({ ), }); + +export const getAlertComment = ({ + action, + alert, + onShowAlertDetails, +}: { + action: CaseUserActions; + alert: Alert | undefined; + onShowAlertDetails: (alertId: string, index: string) => void; +}): EuiCommentProps => { + return { + username: ( + + ), + className: 'comment-alert', + type: 'update', + event: , + 'data-test-subj': `${action.actionField[0]}-${action.action}-action-${action.actionId}`, + timestamp: , + timelineIcon: 'bell', + actions: ( + + + + + + {alert != null ? ( + + ) : ( + + )} + + + ), + }; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx index 4d9b7d030fec0..d9636e0113690 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx @@ -18,6 +18,8 @@ import { TestProviders } from '../../../common/mock'; const fetchUserActions = jest.fn(); const onUpdateField = jest.fn(); const updateCase = jest.fn(); +const onShowAlertDetails = jest.fn(); + const defaultProps = { caseServices: {}, caseUserActions: [], @@ -29,6 +31,8 @@ const defaultProps = { onUpdateField, updateCase, userCanCrud: true, + alerts: {}, + onShowAlertDetails, }; const useUpdateCommentMock = useUpdateComment as jest.Mock; jest.mock('../../containers/use_update_comment'); 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 01709ae55f483..5c2590825d1b2 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,16 +22,17 @@ 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 } from '../../../../../case/common/api/cases'; +import { ActionConnector, CommentType } from '../../../../../case/common/api/cases'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; -import { OnUpdateFields } from '../case_view'; +import { Alert, OnUpdateFields } from '../case_view'; import { getConnectorLabelTitle, getLabelTitle, getPushedServiceLabelTitle, getPushInfo, getUpdateAction, + getAlertComment, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; import { UserActionMarkdown } from './user_action_markdown'; @@ -50,6 +51,8 @@ export interface UserActionTreeProps { onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; updateCase: (newCase: Case) => void; userCanCrud: boolean; + alerts: Record; + onShowAlertDetails: (alertId: string, index: string) => void; } const MyEuiFlexGroup = styled(EuiFlexGroup)` @@ -78,6 +81,17 @@ const MyEuiCommentList = styled(EuiCommentList)` display: none; } } + + & .comment-alert .euiCommentEvent { + background-color: ${theme.eui.euiColorLightestShade}; + border: ${theme.eui.euiFlyoutBorder}; + padding: 10px; + border-radius: ${theme.eui.paddingSizes.xs}; + } + + & .comment-alert .euiCommentEvent__headerData { + flex-grow: 1; + } `} `; @@ -96,6 +110,8 @@ export const UserActionTree = React.memo( onUpdateField, updateCase, userCanCrud, + alerts, + onShowAlertDetails, }: UserActionTreeProps) => { const { commentId } = useParams<{ commentId?: string }>(); const handlerTimeoutId = useRef(0); @@ -105,6 +121,7 @@ export const UserActionTree = React.memo( const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); + const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { @@ -264,7 +281,7 @@ export const UserActionTree = React.memo( // Comment creation if (action.commentId != null && action.action === 'create') { const comment = caseData.comments.find((c) => c.id === action.commentId); - if (comment != null) { + if (comment != null && comment.type === CommentType.user) { return [ ...comments, { @@ -316,6 +333,9 @@ export const UserActionTree = React.memo( ), }, ]; + } else if (comment != null && comment.type === CommentType.alert) { + const alert = alerts[comment.alertId]; + return [...comments, getAlertComment({ action, alert, onShowAlertDetails })]; } } @@ -380,7 +400,7 @@ export const UserActionTree = React.memo( ]; } - // title, description, comments, tags, status + // title, description, comment updates, tags if ( action.actionField.length === 1 && ['title', 'description', 'comment', 'tags', 'status'].includes(action.actionField[0]) @@ -412,6 +432,8 @@ export const UserActionTree = React.memo( manageMarkdownEditIds, selectedOutlineCommentId, userCanCrud, + alerts, + onShowAlertDetails, ] ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts index 73c94477b4e73..f725b4249d7e3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts @@ -33,3 +33,31 @@ export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate( defaultMessage: 'Highlight the referenced comment', } ); + +export const ALERT_COMMENT_LABEL_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.alertCommentLabelTitle', + { + defaultMessage: 'added an alert from', + } +); + +export const ALERT_RULE_DELETED_COMMENT_LABEL = i18n.translate( + 'xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle', + { + defaultMessage: 'added an alert', + } +); + +export const SHOW_ALERT_TOOLTIP = i18n.translate( + 'xpack.securitySolution.case.caseView.showAlertTooltip', + { + defaultMessage: 'Show alert details', + } +); + +export const ALERT_NOT_FOUND_TOOLTIP = i18n.translate( + 'xpack.securitySolution.case.caseView.showAlertDeletedTooltip', + { + defaultMessage: 'Alert not found', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx new file mode 100644 index 0000000000000..148ad275b756e --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, useCallback } from 'react'; +import { EuiLink } from '@elastic/eui'; + +import { APP_ID } from '../../../../common/constants'; +import { useKibana } from '../../../common/lib/kibana'; +import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; +import { SecurityPageName } from '../../../app/types'; + +import { Alert } from '../case_view'; +import * as i18n from './translations'; + +interface Props { + alert: Alert | undefined; +} + +const AlertCommentEventComponent: React.FC = ({ alert }) => { + const ruleName = alert?.rule?.name ?? null; + const ruleId = alert?.rule?.id ?? null; + const { navigateToApp } = useKibana().services.application; + const { formatUrl } = useFormatUrl(SecurityPageName.detections); + + const onLinkClick = useCallback( + (ev: { preventDefault: () => void }) => { + ev.preventDefault(); + navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + path: formatUrl(getRuleDetailsUrl(ruleId ?? '')), + }); + }, + [ruleId, formatUrl, navigateToApp] + ); + + return ruleId != null && ruleName != null ? ( + <> + {`${i18n.ALERT_COMMENT_LABEL_TITLE} `} + {ruleName} + + ) : ( + <>{i18n.ALERT_RULE_DELETED_COMMENT_LABEL} + ); +}; + +export const AlertCommentEvent = memo(AlertCommentEventComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx new file mode 100644 index 0000000000000..f710856414521 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, useCallback } from 'react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import deepEqual from 'fast-deep-equal'; + +import { Alert } from '../case_view'; +import * as i18n from './translations'; + +interface UserActionShowAlertProps { + id: string; + alert: Alert; + onShowAlertDetails: (alertId: string, index: string) => void; +} + +const UserActionShowAlertComponent = ({ + id, + alert, + onShowAlertDetails, +}: UserActionShowAlertProps) => { + const onClick = useCallback(() => onShowAlertDetails(alert._id, alert._index), [ + alert._id, + alert._index, + onShowAlertDetails, + ]); + return ( + {i18n.SHOW_ALERT_TOOLTIP}

}> + +
+ ); +}; + +export const UserActionShowAlert = memo( + UserActionShowAlertComponent, + (prevProps, nextProps) => + prevProps.id === nextProps.id && + deepEqual(prevProps.alert, nextProps.alert) && + prevProps.onShowAlertDetails === nextProps.onShowAlertDetails +); 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 151d0953dcb8e..40312a8713783 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -191,8 +191,8 @@ export const elasticUserSnake = { email: 'leslie.knope@elastic.co', }; export const basicCommentSnake: CommentResponse = { - ...basicComment, comment: 'Solve this fast!', + type: CommentType.user, id: basicCommentId, created_at: basicCreatedAt, created_by: elasticUserSnake, @@ -200,6 +200,7 @@ export const basicCommentSnake: CommentResponse = { pushed_by: null, updated_at: null, updated_by: null, + version: 'WzQ3LDFc', }; export const basicCaseSnake: CaseResponse = { diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index 4458ee83f40d3..ec1eaa939fe31 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -9,24 +9,22 @@ import { UserActionField, UserAction, CaseConnector, - CommentType, + CommentRequest, CaseStatuses, } from '../../../../case/common/api'; export { CaseConnector, ActionConnector } from '../../../../case/common/api'; -export interface Comment { +export type Comment = CommentRequest & { id: string; createdAt: string; createdBy: ElasticUser; - comment: string; - type: CommentType.user; pushedAt: string | null; pushedBy: string | null; updatedAt: string | null; updatedBy: ElasticUser | null; version: string; -} +}; export interface CaseUserActions { actionId: string; actionField: UserActionField; 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 80cd77192a4a0..fe8c793817509 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 @@ -20,7 +20,7 @@ import { } from './mock'; import * as api from './api'; import { CaseServices } from './use_get_case_user_actions'; -import { CaseConnector, ConnectorTypes } from '../../../../case/common/api/connectors'; +import { CaseConnector, ConnectorTypes, CommentType } from '../../../../case/common/api'; jest.mock('./api'); @@ -53,7 +53,7 @@ describe('usePostPushToService', () => { comments: [ { commentId: basicComment.id, - comment: basicComment.comment, + comment: basicComment.type === CommentType.user ? basicComment.comment : '', createdAt: basicComment.createdAt, createdBy: serviceConnectorUser, updatedAt: null, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx index b2d865122c759..d78799d5baafc 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx @@ -10,6 +10,7 @@ import { ServiceConnectorCaseResponse, ServiceConnectorCaseParams, CaseConnector, + CommentType, } from '../../../../case/common/api'; import { errorToToaster, @@ -177,7 +178,7 @@ export const formatServiceRequestData = ( ) .map((c) => ({ commentId: c.id, - comment: c.comment, + comment: c.type === CommentType.user ? c.comment : '', createdAt: c.createdAt, createdBy: { fullName: c.createdBy.fullName ?? null, diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts index 89fcc67bcd15f..06a7f83bcb6a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts @@ -12,7 +12,7 @@ import { navTabs } from '../../../app/home/home_navigations'; import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../lib/kibana'; -export { getDetectionEngineUrl } from './redirect_to_detection_engine'; +export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detection_engine'; export { getAppOverviewUrl } from './redirect_to_overview'; export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts'; export { getNetworkUrl, getNetworkDetailsUrl } from './redirect_to_network';