diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx
new file mode 100644
index 0000000000000..adac26a8ac92b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 from 'react';
+import { mount } from 'enzyme';
+/* eslint-disable @kbn/eslint/module_migration */
+import routeData from 'react-router';
+/* eslint-enable @kbn/eslint/module_migration */
+import { InsertTimelinePopoverComponent } from './';
+
+const mockDispatch = jest.fn();
+jest.mock('react-redux', () => ({
+ useDispatch: () => mockDispatch,
+}));
+const mockLocation = {
+ pathname: '/apath',
+ hash: '',
+ search: '',
+ state: '',
+};
+const mockLocationWithState = {
+ ...mockLocation,
+ state: {
+ insertTimeline: {
+ timelineId: 'timeline-id',
+ timelineTitle: 'Timeline title',
+ },
+ },
+};
+
+const onTimelineChange = jest.fn();
+const defaultProps = {
+ isDisabled: false,
+ onTimelineChange,
+};
+
+describe('Insert timeline popover ', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should insert a timeline when passed in the router state', () => {
+ jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocationWithState);
+ mount();
+ expect(mockDispatch).toBeCalledWith({
+ payload: { id: 'timeline-id', show: false },
+ type: 'x-pack/siem/local/timeline/SHOW_TIMELINE',
+ });
+ expect(onTimelineChange).toBeCalledWith('Timeline title', 'timeline-id');
+ });
+ it('should do nothing when router state', () => {
+ jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
+ mount();
+ expect(mockDispatch).toHaveBeenCalledTimes(0);
+ expect(onTimelineChange).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
index 84bd8c1f302c3..fa474c4d601ad 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
@@ -5,11 +5,14 @@
*/
import { EuiButtonIcon, EuiPopover, EuiSelectableOption } from '@elastic/eui';
-import React, { memo, useCallback, useMemo, useState } from 'react';
+import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
+import { useLocation } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
import { OpenTimelineResult } from '../../open_timeline/types';
import { SelectableTimeline } from '../selectable_timeline';
import * as i18n from '../translations';
+import { timelineActions } from '../../../store/timeline';
interface InsertTimelinePopoverProps {
isDisabled: boolean;
@@ -17,12 +20,37 @@ interface InsertTimelinePopoverProps {
onTimelineChange: (timelineTitle: string, timelineId: string | null) => void;
}
-const InsertTimelinePopoverComponent: React.FC = ({
+interface RouterState {
+ insertTimeline: {
+ timelineId: string;
+ timelineTitle: string;
+ };
+}
+
+type Props = InsertTimelinePopoverProps;
+
+export const InsertTimelinePopoverComponent: React.FC = ({
isDisabled,
hideUntitled = false,
onTimelineChange,
}) => {
+ const dispatch = useDispatch();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const { state } = useLocation();
+ const [routerState, setRouterState] = useState(state ?? null);
+
+ useEffect(() => {
+ if (routerState && routerState.insertTimeline) {
+ dispatch(
+ timelineActions.showTimeline({ id: routerState.insertTimeline.timelineId, show: false })
+ );
+ onTimelineChange(
+ routerState.insertTimeline.timelineTitle,
+ routerState.insertTimeline.timelineId
+ );
+ setRouterState(null);
+ }
+ }, [routerState]);
const handleClosePopover = useCallback(() => {
setIsPopoverOpen(false);
@@ -65,6 +93,7 @@ const InsertTimelinePopoverComponent: React.FC = ({
return (
(({ timelineId, title, updateTitle }) =
));
Name.displayName = 'Name';
+interface NewCaseProps {
+ onClosePopover: () => void;
+ timelineId: string;
+ timelineTitle: string;
+}
+
+export const NewCase = React.memo(({ onClosePopover, timelineId, timelineTitle }) => {
+ const history = useHistory();
+ const handleClick = useCallback(() => {
+ onClosePopover();
+ history.push({
+ pathname: `/${SiemPageName.case}/create`,
+ state: {
+ insertTimeline: {
+ timelineId,
+ timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
+ },
+ },
+ });
+ }, [onClosePopover, history, timelineId, timelineTitle]);
+
+ return (
+
+ {i18n.ATTACH_TIMELINE_TO_NEW_CASE}
+
+ );
+});
+NewCase.displayName = 'NewCase';
+
interface NewTimelineProps {
createTimeline: CreateTimeline;
onClosePopover: () => void;
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
index 8549784b8ecd6..0080fcb1e6924 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
@@ -141,6 +141,7 @@ export const Properties = React.memo(
showTimelineModal={showTimelineModal}
showUsersView={title.length > 0}
timelineId={timelineId}
+ title={title}
updateDescription={updateDescription}
updateNote={updateNote}
usersViewing={usersViewing}
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx
index b21ab5063441e..59d268487cca7 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx
@@ -14,7 +14,7 @@ import {
EuiToolTip,
EuiAvatar,
} from '@elastic/eui';
-import { NewTimeline, Description, NotesButton } from './helpers';
+import { NewTimeline, Description, NotesButton, NewCase } from './helpers';
import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button';
import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal';
import { InspectButton, InspectButtonContainer } from '../../inspect';
@@ -79,6 +79,7 @@ interface Props {
onCloseTimelineModal: () => void;
onOpenTimelineModal: () => void;
showTimelineModal: boolean;
+ title: string;
updateNote: UpdateNote;
}
@@ -104,6 +105,7 @@ const PropertiesRightComponent: React.FC = ({
showTimelineModal,
onCloseTimelineModal,
onOpenTimelineModal,
+ title,
}) => (
@@ -135,6 +137,14 @@ const PropertiesRightComponent: React.FC = ({
+
+
+
+
{
.find('[data-test-subj="timeline-title"]')
.first()
.props().placeholder
- ).toContain('Untitled Timeline');
+ ).toContain('Untitled timeline');
});
test('it renders the timeline table', () => {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
index 41100ec6d50f1..3f4a83d1bff33 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
@@ -7,6 +7,9 @@
import React from 'react';
import { Router } from 'react-router-dom';
import { mount } from 'enzyme';
+/* eslint-disable @kbn/eslint/module_migration */
+import routeData from 'react-router';
+/* eslint-enable @kbn/eslint/module_migration */
import { CaseComponent } from './';
import { caseProps, caseClosedProps, data, dataClosed } from './__mock__';
import { TestProviders } from '../../../../mock';
@@ -35,6 +38,13 @@ const mockHistory = {
listen: jest.fn(),
};
+const mockLocation = {
+ pathname: '/welcome',
+ hash: '',
+ search: '',
+ state: '',
+};
+
describe('CaseView ', () => {
const updateCaseProperty = jest.fn();
/* eslint-disable no-console */
@@ -59,6 +69,7 @@ describe('CaseView ', () => {
beforeEach(() => {
jest.resetAllMocks();
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
+ jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
});
it('should render CaseComponent', () => {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
index 74a1b98c29eef..9ace36eea1e9e 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
@@ -58,7 +58,7 @@ const renderUsers = (