diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx index 7ef698ae05b36..1e8525f0519ed 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx @@ -6,14 +6,38 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import { QueryBarDefineRule } from './index'; -import { useFormFieldMock } from '../../../../common/mock'; +import { + TestProviders, + useFormFieldMock, + mockOpenTimelineQueryResults, +} from '../../../../common/mock'; +import { mockHistory, Router } from '../../../../cases/components/__mock__/router'; +import { useGetAllTimeline, getAllTimeline } from '../../../../timelines/containers/all'; jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../timelines/containers/all', () => { + const originalModule = jest.requireActual('../../../../timelines/containers/all'); + return { + ...originalModule, + useGetAllTimeline: jest.fn(), + }; +}); + describe('QueryBarDefineRule', () => { + beforeEach(() => { + ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + fetchAllTimeline: jest.fn(), + timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), + loading: false, + totalCount: mockOpenTimelineQueryResults.totalCount, + refetch: jest.fn(), + }); + }); + it('renders correctly', () => { const Component = () => { const field = useFormFieldMock(); @@ -32,7 +56,35 @@ describe('QueryBarDefineRule', () => { ); }; const wrapper = shallow(); - expect(wrapper.dive().find('[data-test-subj="query-bar-define-rule"]')).toHaveLength(1); }); + + it('renders import query from saved timeline modal actions hidden correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="create-from-template"]').exists()).toBeFalsy(); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index f45ff5f1ea1a1..6bda4a0e0f6b8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -6,7 +6,7 @@ */ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -50,6 +50,8 @@ interface QueryBarDefineRuleProps { onValidityChange?: (arg: boolean) => void; } +const actionTimelineToHide: ActionTimelineToShow[] = ['duplicate', 'createFrom']; + const StyledEuiFormRow = styled(EuiFormRow)` .kbnTypeahead__items { max-height: 45vh !important; @@ -253,8 +255,6 @@ export const QueryBarDefineRule = ({ } }; - const actionTimelineToHide = useMemo(() => ['duplicate'], []); - return ( <> { + const originalModule = jest.requireActual('../../open_timeline/use_timeline_status'); + return { + ...originalModule, + useTimelineStatus: jest.fn().mockReturnValue({ + timelineStatus: 'active', + templateTimelineFilter: [], + installPrepackagedTimelines: jest.fn(), + }), + }; +}); -jest.mock('../../../../common/lib/kibana', () => ({ - useKibana: jest.fn(), - useUiSetting$: jest.fn().mockReturnValue([]), -})); +jest.mock('../../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../../common/lib/kibana'); + return { + ...originalModule, + useKibana: jest.fn(), + useUiSetting$: jest.fn().mockReturnValue([]), + }; +}); + +jest.mock('../../../containers/all', () => { + const originalModule = jest.requireActual('../../../containers/all'); + return { + ...originalModule, + useGetAllTimeline: jest.fn(), + }; +}); jest.mock('../../timeline/properties/new_template_timeline', () => ({ NewTemplateTimeline: jest.fn(() =>
), @@ -35,8 +62,7 @@ jest.mock('../../../../common/components/inspect', () => ({ InspectButtonContainer: jest.fn(({ children }) =>
{children}
), })); -// FLAKY: https://github.com/elastic/kibana/issues/96691 -describe.skip('AddTimelineButton', () => { +describe('AddTimelineButton', () => { let wrapper: ReactWrapper; const props = { timelineId: TimelineId.active, @@ -67,24 +93,24 @@ describe.skip('AddTimelineButton', () => { }); test('it renders create timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy() + ); }); test('it renders create timeline template btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy() + ); }); test('it renders Open timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); }); }); @@ -113,24 +139,86 @@ describe.skip('AddTimelineButton', () => { }); test('it renders create timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy() + ); }); test('it renders create timeline template btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy() + ); }); test('it renders Open timeline btn', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); + }); + }); + + describe('open modal', () => { + beforeEach(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + getUrlForApp: jest.fn(), + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + + ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + fetchAllTimeline: jest.fn(), + timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), + loading: false, + totalCount: mockOpenTimelineQueryResults.totalCount, + refetch: jest.fn(), + }); + + wrapper = mount( + + + + + + ); + }); + + afterEach(() => { + (useKibana as jest.Mock).mockReset(); + }); + + it('should render timelines table', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy(); }); + + wrapper.find('[data-test-subj="open-timeline-button"]').first().simulate('click'); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="timelines-table"]').exists()).toBeTruthy(); + }); + }); + + it('should render correct actions', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); + + wrapper.find('[data-test-subj="open-timeline-button"]').first().simulate('click'); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="create-from-template"]').exists()).toBeFalsy(); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx index 90b1cf09cb6cd..5ea1b60e4f156 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx @@ -10,6 +10,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button'; import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal'; +import { ActionTimelineToShow } from '../../open_timeline/types'; import * as i18n from '../../timeline/properties/translations'; import { NewTimeline } from '../../timeline/properties/helpers'; import { NewTemplateTimeline } from '../../timeline/properties/new_template_timeline'; @@ -20,6 +21,8 @@ interface AddTimelineButtonComponentProps { export const ADD_TIMELINE_BUTTON_CLASS_NAME = 'add-timeline-button'; +const actionTimelineToHide: ActionTimelineToShow[] = ['createFrom']; + const AddTimelineButtonComponent: React.FC = ({ timelineId }) => { const [showActions, setShowActions] = useState(false); const [showTimelineModal, setShowTimelineModal] = useState(false); @@ -83,7 +86,9 @@ const AddTimelineButtonComponent: React.FC = ({ - {showTimelineModal ? : null} + {showTimelineModal ? ( + + ) : null} ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx index c1b30f3e68cf4..4aa6fd469de26 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx @@ -83,13 +83,15 @@ export const getTimelinesTableColumns = ({ }), ...getExtendedColumns(showExtendedColumns), ...getIconHeaderColumns({ timelineType }), - ...getActionsColumns({ - actionTimelineToShow, - deleteTimelines, - enableExportTimelineDownloader, - onOpenDeleteTimelineModal, - onOpenTimeline, - }), + ...(actionTimelineToShow.length + ? getActionsColumns({ + actionTimelineToShow, + deleteTimelines, + enableExportTimelineDownloader, + onOpenDeleteTimelineModal, + onOpenTimeline, + }) + : []), ]; };