Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.15] [Security Solution] [Timelines] Notes table links (#187868) #187986

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<EuiLink onClick={investigateInTimelineAlertClick} data-test-subj="open-event-in-timeline">
{i18n.VIEW_EVENT_IN_TIMELINE}
</EuiLink>
);
});

OpenEventInTimeline.displayName = 'OpenEventInTimeline';
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
);

Expand Down Expand Up @@ -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',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<EuiBasicTableColumn<Note>> = [
{
field: 'created',
name: i18n.CREATED_COLUMN,
sortable: true,
render: (created: Note['created']) => <FormattedRelativePreferenceDate value={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<EuiBasicTableColumn<Note>> = (onOpenTimeline) => {
return [
{
field: 'created',
name: i18n.CREATED_COLUMN,
sortable: true,
render: (created: Note['created']) => <FormattedRelativePreferenceDate value={created} />,
},
{
field: 'createdBy',
name: i18n.CREATED_BY_COLUMN,
},
{
field: 'eventId',
name: i18n.EVENT_ID_COLUMN,
sortable: true,
render: (eventId: Note['eventId']) => <OpenEventInTimeline eventId={eventId} />,
},
{
field: 'timelineId',
name: i18n.TIMELINE_ID_COLUMN,
render: (timelineId: Note['timelineId']) =>
timelineId ? (
<EuiLink onClick={() => onOpenTimeline({ timelineId, duplicate: false })}>
{i18n.OPEN_TIMELINE}
</EuiLink>
) : null,
},
{
field: 'note',
name: i18n.NOTE_CONTENT_COLUMN,
},
];
};

const pageSizeOptions = [50, 25, 10, 0];

Expand All @@ -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);
Expand Down Expand Up @@ -147,13 +164,13 @@ export const NoteManagementPage = () => {
},
];
return [
...columns,
...columns(onOpenTimeline),
{
name: 'actions',
actions,
},
];
}, [selectRowForDeletion]);
}, [selectRowForDeletion, onOpenTimeline]);

const currentPagination = useMemo(() => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
/>
</>
) : (
<NoteManagementPage />
<NoteManagementPage onOpenTimeline={onOpenTimeline} />
)}
</div>
</>
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/security_solution/public/timelines/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ export const links: LinkItem = {
defaultMessage: 'Visualize and delete notes.',
}),
path: `${TIMELINES_PATH}/notes`,
skipUrlState: true,
hideTimeline: true,
experimentalKey: 'securitySolutionNotesEnabled',
},
],
Expand Down
32 changes: 2 additions & 30 deletions x-pack/plugins/security_solution/public/timelines/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.notesManagement}>
<NoteManagementPage />
<SpyRoute pageName={SecurityPageName.notesManagement} />
</TrackApplicationView>
</PluginTemplateWrapper>
);

const NoteManagementContainer = memo(() => {
return (
<Switch>
<Route path={NOTES_MANAGEMENT_PATH} exact component={NoteManagementTelemetry} />
<Route component={NotFoundPage} />
</Switch>
);
});
NoteManagementContainer.displayName = 'NoteManagementContainer';

const TimelinesRoutes = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.timelines}>
Expand All @@ -51,8 +27,4 @@ export const routes: SecuritySubPluginRoutes = [
path: TIMELINES_PATH,
component: TimelinesRoutes,
},
{
path: NOTES_MANAGEMENT_PATH,
component: NoteManagementContainer,
},
];