From 209b0c52cb905cc0b62db9ef9f425d1119cdc549 Mon Sep 17 00:00:00 2001
From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
Date: Wed, 10 Jul 2024 09:55:02 -0400
Subject: [PATCH] [Security Solution] [Timelines] Notes table links (#187868)
## Summary
This pr changes the timeline id cell to be a link to open the saved
timeline a note is a part of if timelineId exists, instead of just
showing the id as a plain string. Also updates the event column to a
link that opens a new timeline containing just the event a note is
associated with.
![image](https://github.com/elastic/kibana/assets/56408403/e1c577e6-deb6-4daf-8d94-78fcc400c041)
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
---
.../components/open_event_in_timeline.tsx | 24 ++++++
.../public/notes/components/translations.ts | 20 ++++-
.../notes/pages/note_management_page.tsx | 77 +++++++++++--------
.../open_timeline/open_timeline.tsx | 2 +-
.../public/timelines/links.ts | 2 -
.../public/timelines/routes.tsx | 32 +-------
6 files changed, 91 insertions(+), 66 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/notes/components/open_event_in_timeline.tsx
diff --git a/x-pack/plugins/security_solution/public/notes/components/open_event_in_timeline.tsx b/x-pack/plugins/security_solution/public/notes/components/open_event_in_timeline.tsx
new file mode 100644
index 0000000000000..43f039836ccad
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/notes/components/open_event_in_timeline.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { memo } from 'react';
+import { EuiLink } from '@elastic/eui';
+import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
+import { useInvestigateInTimeline } from '../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
+import * as i18n from './translations';
+
+export const OpenEventInTimeline: React.FC<{ eventId?: string | null }> = memo(({ eventId }) => {
+ const ecsRowData = { event: { id: [eventId] }, _id: eventId } as Ecs;
+ const { investigateInTimelineAlertClick } = useInvestigateInTimeline({ ecsRowData });
+
+ return (
+
+ {i18n.VIEW_EVENT_IN_TIMELINE}
+
+ );
+});
+
+OpenEventInTimeline.displayName = 'OpenEventInTimeline';
diff --git a/x-pack/plugins/security_solution/public/notes/components/translations.ts b/x-pack/plugins/security_solution/public/notes/components/translations.ts
index 471c28cbc9d4c..e952e5e4ac715 100644
--- a/x-pack/plugins/security_solution/public/notes/components/translations.ts
+++ b/x-pack/plugins/security_solution/public/notes/components/translations.ts
@@ -31,14 +31,14 @@ export const CREATED_BY_COLUMN = i18n.translate(
export const EVENT_ID_COLUMN = i18n.translate(
'xpack.securitySolution.notes.management.eventIdColumnTitle',
{
- defaultMessage: 'Document ID',
+ defaultMessage: 'View Document',
}
);
export const TIMELINE_ID_COLUMN = i18n.translate(
- 'xpack.securitySolution.notes.management.timelineIdColumnTitle',
+ 'xpack.securitySolution.notes.management.timelineColumnTitle',
{
- defaultMessage: 'Timeline ID',
+ defaultMessage: 'Timeline',
}
);
@@ -102,3 +102,17 @@ export const DELETE_SELECTED = i18n.translate(
export const REFRESH = i18n.translate('xpack.securitySolution.notes.management.refresh', {
defaultMessage: 'Refresh',
});
+
+export const OPEN_TIMELINE = i18n.translate(
+ 'xpack.securitySolution.notes.management.openTimeline',
+ {
+ defaultMessage: 'Open timeline',
+ }
+);
+
+export const VIEW_EVENT_IN_TIMELINE = i18n.translate(
+ 'xpack.securitySolution.notes.management.viewEventInTimeline',
+ {
+ defaultMessage: 'View event in timeline',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx
index 1c39265a1b02f..4e3f776d26027 100644
--- a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx
+++ b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx
@@ -7,7 +7,7 @@
import React, { useCallback, useMemo, useEffect } from 'react';
import type { DefaultItemAction, EuiBasicTableColumn } from '@elastic/eui';
-import { EuiBasicTable, EuiEmptyPrompt } from '@elastic/eui';
+import { EuiBasicTable, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { useDispatch, useSelector } from 'react-redux';
// TODO unify this type from the api with the one in public/common/lib/note
import type { Note } from '../../../common/api/timeline';
@@ -33,32 +33,45 @@ import { SearchRow } from '../components/search_row';
import { NotesUtilityBar } from '../components/utility_bar';
import { DeleteConfirmModal } from '../components/delete_confirm_modal';
import * as i18n from '../components/translations';
-
-const columns: Array> = [
- {
- field: 'created',
- name: i18n.CREATED_COLUMN,
- sortable: true,
- render: (created: Note['created']) => ,
- },
- {
- field: 'createdBy',
- name: i18n.CREATED_BY_COLUMN,
- },
- {
- field: 'eventId',
- name: i18n.EVENT_ID_COLUMN,
- sortable: true,
- },
- {
- field: 'timelineId',
- name: i18n.TIMELINE_ID_COLUMN,
- },
- {
- field: 'note',
- name: i18n.NOTE_CONTENT_COLUMN,
- },
-];
+import type { OpenTimelineProps } from '../../timelines/components/open_timeline/types';
+import { OpenEventInTimeline } from '../components/open_event_in_timeline';
+
+const columns: (
+ onOpenTimeline: OpenTimelineProps['onOpenTimeline']
+) => Array> = (onOpenTimeline) => {
+ return [
+ {
+ field: 'created',
+ name: i18n.CREATED_COLUMN,
+ sortable: true,
+ render: (created: Note['created']) => ,
+ },
+ {
+ field: 'createdBy',
+ name: i18n.CREATED_BY_COLUMN,
+ },
+ {
+ field: 'eventId',
+ name: i18n.EVENT_ID_COLUMN,
+ sortable: true,
+ render: (eventId: Note['eventId']) => ,
+ },
+ {
+ field: 'timelineId',
+ name: i18n.TIMELINE_ID_COLUMN,
+ render: (timelineId: Note['timelineId']) =>
+ timelineId ? (
+ onOpenTimeline({ timelineId, duplicate: false })}>
+ {i18n.OPEN_TIMELINE}
+
+ ) : null,
+ },
+ {
+ field: 'note',
+ name: i18n.NOTE_CONTENT_COLUMN,
+ },
+ ];
+};
const pageSizeOptions = [50, 25, 10, 0];
@@ -67,7 +80,11 @@ const pageSizeOptions = [50, 25, 10, 0];
* This component uses the same slices of state as the notes functionality of the rest of the Security Solution applicaiton.
* Therefore, changes made in this page (like fetching or deleting notes) will have an impact everywhere.
*/
-export const NoteManagementPage = () => {
+export const NoteManagementPage = ({
+ onOpenTimeline,
+}: {
+ onOpenTimeline: OpenTimelineProps['onOpenTimeline'];
+}) => {
const dispatch = useDispatch();
const notes = useSelector(selectAllNotes);
const pagination = useSelector(selectNotesPagination);
@@ -147,13 +164,13 @@ export const NoteManagementPage = () => {
},
];
return [
- ...columns,
+ ...columns(onOpenTimeline),
{
name: 'actions',
actions,
},
];
- }, [selectRowForDeletion]);
+ }, [selectRowForDeletion, onOpenTimeline]);
const currentPagination = useMemo(() => {
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx
index 3dc686229e4fa..015a36717475e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx
@@ -312,7 +312,7 @@ export const OpenTimeline = React.memo(
/>
>
) : (
-
+
)}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/links.ts b/x-pack/plugins/security_solution/public/timelines/links.ts
index 97667c0ce8aa3..169ef6da01910 100644
--- a/x-pack/plugins/security_solution/public/timelines/links.ts
+++ b/x-pack/plugins/security_solution/public/timelines/links.ts
@@ -37,8 +37,6 @@ export const links: LinkItem = {
defaultMessage: 'Visualize and delete notes.',
}),
path: `${TIMELINES_PATH}/notes`,
- skipUrlState: true,
- hideTimeline: true,
experimentalKey: 'securitySolutionNotesEnabled',
},
],
diff --git a/x-pack/plugins/security_solution/public/timelines/routes.tsx b/x-pack/plugins/security_solution/public/timelines/routes.tsx
index a7d6373007192..e7f28eb9d71e3 100644
--- a/x-pack/plugins/security_solution/public/timelines/routes.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/routes.tsx
@@ -5,39 +5,15 @@
* 2.0.
*/
-import React, { memo } from 'react';
+import React from 'react';
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
-import { Switch } from 'react-router-dom';
-import { Route } from '@kbn/shared-ux-router';
-import { SpyRoute } from '../common/utils/route/spy_routes';
-import { NotFoundPage } from '../app/404';
-import { NoteManagementPage } from '../notes/pages/note_management_page';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { SecurityPageName } from '../app/types';
import type { SecuritySubPluginRoutes } from '../app/types';
-import { NOTES_MANAGEMENT_PATH, TIMELINES_PATH } from '../../common/constants';
+import { TIMELINES_PATH } from '../../common/constants';
import { Timelines } from './pages';
-const NoteManagementTelemetry = () => (
-
-
-
-
-
-
-);
-
-const NoteManagementContainer = memo(() => {
- return (
-
-
-
-
- );
-});
-NoteManagementContainer.displayName = 'NoteManagementContainer';
-
const TimelinesRoutes = () => (
@@ -51,8 +27,4 @@ export const routes: SecuritySubPluginRoutes = [
path: TIMELINES_PATH,
component: TimelinesRoutes,
},
- {
- path: NOTES_MANAGEMENT_PATH,
- component: NoteManagementContainer,
- },
];