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, - }, ];