Skip to content

Commit

Permalink
[Security Solution] Modal for saving timeline (#81802) (#82137)
Browse files Browse the repository at this point in the history
* init modal for saving timeline

* disable auto save

* unit test

* fix type error

* update translation

* add unit tests

* rename constant

* break components into files

* autoFocus and close modal on finish

* rename constant

* fix description label

* update wording

* review

* fix dependency

* remove classname

* update wording

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
angorayc and kibanamachine committed Oct 30, 2020
1 parent 82db4ef commit e73c0fd
Show file tree
Hide file tree
Showing 19 changed files with 1,064 additions and 62 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,5 @@ export const showAllOthersBucket: string[] = [
'destination.ip',
'user.name',
];

export const ENABLE_NEW_TIMELINE = false;
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,27 @@ const makeMapStateToProps = () => {

const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({
associateNote: (noteId: string) => dispatch(timelineActions.addNote({ id: timelineId, noteId })),
updateDescription: ({ id, description }: { id: string; description: string }) =>
dispatch(timelineActions.updateDescription({ id, description })),
updateDescription: ({
id,
description,
disableAutoSave,
}: {
id: string;
description: string;
disableAutoSave?: boolean;
}) => dispatch(timelineActions.updateDescription({ id, description, disableAutoSave })),
updateIsFavorite: ({ id, isFavorite }: { id: string; isFavorite: boolean }) =>
dispatch(timelineActions.updateIsFavorite({ id, isFavorite })),
updateNote: (note: Note) => dispatch(appActions.updateNote({ note })),
updateTitle: ({ id, title }: { id: string; title: string }) =>
dispatch(timelineActions.updateTitle({ id, title })),
updateTitle: ({
id,
title,
disableAutoSave,
}: {
id: string;
title: string;
disableAutoSave?: boolean;
}) => dispatch(timelineActions.updateTitle({ id, title, disableAutoSave })),
toggleLock: ({ linkToId }: { linkToId: InputsModelId }) =>
dispatch(inputsActions.toggleTimelineLinkTo({ linkToId })),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 { shallow, mount } from 'enzyme';

import { SaveTimelineButton } from './save_timeline_button';
import { act } from '@testing-library/react-hooks';

jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...actual,
useDispatch: jest.fn(),
};
});

jest.mock('./title_and_description');

describe('SaveTimelineButton', () => {
const props = {
timelineId: 'timeline-1',
showOverlay: false,
toolTip: 'tooltip message',
toggleSaveTimeline: jest.fn(),
onSaveTimeline: jest.fn(),
updateTitle: jest.fn(),
updateDescription: jest.fn(),
};
test('Show tooltip', () => {
const component = shallow(<SaveTimelineButton {...props} />);
expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(true);
});

test('Hide tooltip', () => {
const testProps = {
...props,
showOverlay: true,
};
const component = mount(<SaveTimelineButton {...testProps} />);
component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click');

act(() => {
expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(
false
);
});
});

test('should show a button with pencil icon', () => {
const component = shallow(<SaveTimelineButton {...props} />);
expect(component.find('[data-test-subj="save-timeline-button-icon"]').prop('iconType')).toEqual(
'pencil'
);
});

test('should not show a modal when showOverlay equals false', () => {
const component = shallow(<SaveTimelineButton {...props} />);
expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(false);
});

test('should show a modal when showOverlay equals true', () => {
const testProps = {
...props,
showOverlay: true,
};
const component = mount(<SaveTimelineButton {...testProps} />);
component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click');
act(() => {
expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(true);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 { EuiButtonIcon, EuiOverlayMask, EuiModal, EuiToolTip } from '@elastic/eui';

import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { timelineActions } from '../../../store/timeline';
import { NOTES_PANEL_WIDTH } from '../properties/notes_size';

import { TimelineTitleAndDescription } from './title_and_description';
import { EDIT } from './translations';

export interface SaveTimelineComponentProps {
timelineId: string;
toolTip?: string;
}

export const SaveTimelineButton = React.memo<SaveTimelineComponentProps>(
({ timelineId, toolTip }) => {
const [showSaveTimelineOverlay, setShowSaveTimelineOverlay] = useState<boolean>(false);
const onToggleSaveTimeline = useCallback(() => {
setShowSaveTimelineOverlay((prevShowSaveTimelineOverlay) => !prevShowSaveTimelineOverlay);
}, [setShowSaveTimelineOverlay]);

const dispatch = useDispatch();
const updateTitle = useCallback(
({ id, title, disableAutoSave }: { id: string; title: string; disableAutoSave?: boolean }) =>
dispatch(timelineActions.updateTitle({ id, title, disableAutoSave })),
[dispatch]
);

const updateDescription = useCallback(
({
id,
description,
disableAutoSave,
}: {
id: string;
description: string;
disableAutoSave?: boolean;
}) => dispatch(timelineActions.updateDescription({ id, description, disableAutoSave })),
[dispatch]
);

const saveTimelineButtonIcon = useMemo(
() => (
<EuiButtonIcon
aria-label={EDIT}
onClick={onToggleSaveTimeline}
iconType="pencil"
data-test-subj="save-timeline-button-icon"
/>
),
[onToggleSaveTimeline]
);

return showSaveTimelineOverlay ? (
<>
{saveTimelineButtonIcon}
<EuiOverlayMask>
<EuiModal
data-test-subj="save-timeline-modal"
maxWidth={NOTES_PANEL_WIDTH}
onClose={onToggleSaveTimeline}
>
<TimelineTitleAndDescription
timelineId={timelineId}
toggleSaveTimeline={onToggleSaveTimeline}
updateTitle={updateTitle}
updateDescription={updateDescription}
/>
</EuiModal>
</EuiOverlayMask>
</>
) : (
<EuiToolTip content={toolTip ?? ''} data-test-subj="save-timeline-btn-tooltip">
{saveTimelineButtonIcon}
</EuiToolTip>
);
}
);

SaveTimelineButton.displayName = 'SaveTimelineButton';
Loading

0 comments on commit e73c0fd

Please sign in to comment.