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';