From c41178d2d6e952798548ccd7db691d5ceff62053 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Thu, 24 Oct 2024 18:41:19 +0200 Subject: [PATCH] [Security Solution] Remove `unifiedComponentsInTimelineDisabled` flag (#195959) ## Summary Handles https://github.com/elastic/security-team/issues/9645 Follow Up PR for removal of Old timeline Code : https://github.com/elastic/kibana/pull/196243 - This PR removes `unifiedComponentsInTimelineDisabled` flag. What this means that that unified components in Timeline are now enabled by default. - Consequently, the old timeline table code becomes obsolete and is also removed. ( https://github.com/elastic/kibana/pull/196243) ## Changes 1. Converted all cypress and jest tests that were testing old Timeline table to test new unified components in Timeline. If the test for new timeline already existed, old tests are also removed. --- .../common/experimental_features.ts | 4 - .../header_actions/header_actions.test.tsx | 59 +- .../header_actions/header_actions.tsx | 108 +- .../timeline/use_init_timeline_url_param.ts | 7 +- .../use_query_timeline_by_id_on_url_change.ts | 16 +- .../utils/timeline/use_timeline_click.tsx | 8 +- .../use_investigate_in_timeline.tsx | 20 +- .../rules/use_rule_from_timeline.tsx | 8 +- .../components/open_timeline_button.test.tsx | 7 - .../notes/components/open_timeline_button.tsx | 8 +- .../components/recent_timelines/index.tsx | 8 +- .../actions/new_timeline_button.test.tsx | 23 - .../netflow/__snapshots__/index.test.tsx.snap | 18 +- .../components/open_timeline/helpers.test.ts | 277 ++-- .../components/open_timeline/helpers.ts | 35 +- .../components/open_timeline/index.tsx | 23 +- .../timelines/components/timeline/index.tsx | 12 +- .../eql/__snapshots__/index.test.tsx.snap | 1165 --------------- .../timeline/tabs/eql/index.test.tsx | 145 +- .../components/timeline/tabs/eql/index.tsx | 166 +-- .../pinned/__snapshots__/index.test.tsx.snap | 1171 --------------- .../timeline/tabs/pinned/index.test.tsx | 95 +- .../components/timeline/tabs/pinned/index.tsx | 142 +- .../query/__snapshots__/index.test.tsx.snap | 1317 ----------------- .../timeline/tabs/query/header/index.tsx | 19 - .../timeline/tabs/query/index.test.tsx | 1284 +++++++++++++--- .../components/timeline/tabs/query/index.tsx | 142 +- .../query_tab_unified_components.test.tsx | 1210 --------------- .../use_timeline_columns.test.ts.snap | 2 - ...use_timeline_control_columns.test.tsx.snap | 2 +- .../tabs/shared/use_timeline_columns.test.ts | 30 - .../tabs/shared/use_timeline_columns.tsx | 17 +- .../use_timeline_control_columns.test.tsx | 2 +- .../shared/use_timeline_control_columns.tsx | 62 +- .../unified_components/index.test.tsx | 3 - .../timelines/hooks/use_create_timeline.tsx | 10 +- .../timelines/fields_browser.cy.ts | 108 -- .../investigations/timelines/pagination.cy.ts | 83 -- .../investigations/timelines/query_tab.cy.ts | 85 -- .../timelines/table_row_actions.cy.ts | 57 - 40 files changed, 1440 insertions(+), 6518 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/fields_browser.cy.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/pagination.cy.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/query_tab.cy.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/table_row_actions.cy.ts diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index a16b88f649618..ffb9e9748d9c1 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -183,10 +183,6 @@ export const allowedExperimentalValues = Object.freeze({ * */ timelineEsqlTabDisabled: false, - /* - * Disables experimental Discover components, UnifiedFieldList and UnifiedDataTable in Timeline. - */ - unifiedComponentsInTimelineDisabled: false, /* * Disables date pickers and sourcerer in analyzer if needed. diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.test.tsx index 5b48b4286fe63..22320b2a9a3db 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.test.tsx @@ -14,7 +14,6 @@ import { TimelineTabs } from '../../../../common/types'; import { HeaderActions } from './header_actions'; import { timelineActions } from '../../../timelines/store'; import { getColumnHeader } from '../../../timelines/components/timeline/body/column_headers/helpers'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; jest.mock('../../hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn(), @@ -141,51 +140,23 @@ describe('HeaderActions', () => { }); }); - describe('conditional components based on unifiedComponentsInTimelineDisabled', () => { - describe('when unifiedComponentsInTimelineDisabled is false', () => { - beforeEach(() => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); - }); - it('should not show the event renderer settings', () => { - const result = render( - - - - ); - expect(result.queryByTestId('show-row-renderers-gear')).toBeNull(); - }); - - it('should not show the sorting settings', () => { - const result = render( - - - - ); - expect(result.queryByTestId('timeline-sorting-fields')).toBeNull(); - }); + describe('Controls', () => { + it('should not show the event renderer settings', () => { + const result = render( + + + + ); + expect(result.queryByTestId('show-row-renderers-gear')).toBeNull(); }); - describe('when unifiedComponentsInTimelineDisabled is true', () => { - beforeEach(() => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - }); - it('should show the event renderer settings', () => { - const result = render( - - - - ); - result.getByTestId('show-row-renderers-gear'); - }); - - it('should show the sorting settings', () => { - const result = render( - - - - ); - result.getByTestId('timeline-sorting-fields'); - }); + it('should not show the sorting settings', () => { + const result = render( + + + + ); + expect(result.queryByTestId('timeline-sorting-fields')).toBeNull(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx index 7dabf3014da95..1341cb72104d8 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx @@ -6,13 +6,12 @@ */ import React, { useMemo, useCallback, memo } from 'react'; -import type { EuiDataGridSorting, EuiDataGridSchemaDetector } from '@elastic/eui'; -import { EuiButtonIcon, EuiToolTip, useDataGridColumnSorting, EuiCheckbox } from '@elastic/eui'; +import { EuiButtonIcon, EuiToolTip, EuiCheckbox } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import type { HeaderActionProps, SortDirection } from '../../../../common/types'; -import { TimelineTabs, TimelineId } from '../../../../common/types'; +import type { HeaderActionProps } from '../../../../common/types'; +import { TimelineId } from '../../../../common/types'; import { isFullScreen } from '../../../timelines/components/timeline/body/column_headers'; import { isActiveTimeline } from '../../../helpers'; import { getColumnHeader } from '../../../timelines/components/timeline/body/column_headers/helpers'; @@ -21,28 +20,12 @@ import { useGlobalFullScreen, useTimelineFullScreen } from '../../containers/use import { useKibana } from '../../lib/kibana'; import { DEFAULT_ACTION_BUTTON_WIDTH } from '.'; import { EventsTh, EventsThContent } from '../../../timelines/components/timeline/styles'; -import { StatefulRowRenderersBrowser } from '../../../timelines/components/row_renderers_browser'; import { EXIT_FULL_SCREEN } from '../exit_full_screen/translations'; import { EventsSelect } from '../../../timelines/components/timeline/body/column_headers/events_select'; import * as i18n from './translations'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { selectTimelineById } from '../../../timelines/store/selectors'; -const SortingColumnsContainer = styled.div` - button { - color: ${({ theme }) => theme.eui.euiColorPrimary}; - } - - .euiPopover .euiButtonEmpty { - padding: 0; - - .euiButtonEmpty__text { - display: none; - } - } -`; - const FieldBrowserContainer = styled.div` .euiToolTipAnchor { .euiButtonContent { @@ -66,23 +49,15 @@ const ActionsContainer = styled.div` display: flex; `; -// Defined statically to reduce rerenders -const emptySchema = {}; -const emptySchemaDetectors: EuiDataGridSchemaDetector[] = []; - const HeaderActionsComponent: React.FC = memo( ({ - width, browserFields, columnHeaders, - isEventViewer = false, isSelectAllChecked, onSelectAll, showEventsSelect, showSelectAllCheckbox, showFullScreenToggle = true, - sort, - tabType, timelineId, fieldBrowserOptions, }) => { @@ -91,10 +66,6 @@ const HeaderActionsComponent: React.FC = memo( const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const dispatch = useDispatch(); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const { defaultColumns } = useDeepEqualSelector((state) => selectTimelineById(state, timelineId) ); @@ -129,57 +100,6 @@ const HeaderActionsComponent: React.FC = memo( [onSelectAll] ); - const onSortColumns = useCallback( - (cols: EuiDataGridSorting['columns']) => - dispatch( - timelineActions.updateSort({ - id: timelineId, - sort: cols.map(({ id, direction }) => { - const columnHeader = columnHeaders.find((ch) => ch.id === id); - const columnType = columnHeader?.type ?? ''; - const esTypes = columnHeader?.esTypes ?? []; - - return { - columnId: id, - columnType, - esTypes, - sortDirection: direction as SortDirection, - }; - }), - }) - ), - [columnHeaders, dispatch, timelineId] - ); - - const sortedColumns = useMemo( - () => ({ - onSort: onSortColumns, - columns: - sort?.map<{ id: string; direction: 'asc' | 'desc' }>(({ columnId, sortDirection }) => ({ - id: columnId, - direction: sortDirection as 'asc' | 'desc', - })) ?? [], - }), - [onSortColumns, sort] - ); - const displayValues = useMemo( - () => - columnHeaders?.reduce((acc, ch) => ({ ...acc, [ch.id]: ch.displayAsText ?? ch.id }), {}) ?? - {}, - [columnHeaders] - ); - - const myColumns = useMemo( - () => - columnHeaders?.map(({ aggregatable, displayAsText, id, type }) => ({ - id, - isSortable: aggregatable, - displayAsText, - schema: type, - })) ?? [], - [columnHeaders] - ); - const onResetColumns = useCallback(() => { dispatch(timelineActions.updateColumns({ id: timelineId, columns: defaultColumns })); }, [defaultColumns, dispatch, timelineId]); @@ -206,14 +126,6 @@ const HeaderActionsComponent: React.FC = memo( [columnHeaders, dispatch, timelineId, defaultColumns] ); - const ColumnSorting = useDataGridColumnSorting({ - columns: myColumns, - sorting: sortedColumns, - schema: emptySchema, - schemaDetectors: emptySchemaDetectors, - displayValues, - }); - return ( {showSelectAllCheckbox && ( @@ -242,11 +154,6 @@ const HeaderActionsComponent: React.FC = memo( )} - {unifiedComponentsInTimelineDisabled && ( - - - - )} {showFullScreenToggle && ( @@ -275,15 +182,6 @@ const HeaderActionsComponent: React.FC = memo( )} - {tabType !== TimelineTabs.eql && unifiedComponentsInTimelineDisabled && ( - - - - {ColumnSorting} - - - - )} {showEventsSelect && ( diff --git a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts index d885787c14b12..5e032a9a97d07 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts @@ -19,10 +19,6 @@ import { URL_PARAM_KEY } from '../use_url_state'; import { useIsExperimentalFeatureEnabled } from '../use_experimental_features'; export const useInitTimelineFromUrlParam = () => { - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const isEsqlTabDisabled = useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled'); const queryTimelineById = useQueryTimelineById(); @@ -43,11 +39,10 @@ export const useInitTimelineFromUrlParam = () => { timelineId: initialState.id, openTimeline: initialState.isOpen, savedSearchId: initialState.savedSearchId, - unifiedComponentsInTimelineDisabled, }); } }, - [isEsqlTabDisabled, queryTimelineById, unifiedComponentsInTimelineDisabled] + [isEsqlTabDisabled, queryTimelineById] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_query_timeline_by_id_on_url_change.ts b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_query_timeline_by_id_on_url_change.ts index d4df6d67504d6..60d4ff10b8de1 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_query_timeline_by_id_on_url_change.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_query_timeline_by_id_on_url_change.ts @@ -21,7 +21,6 @@ import { getQueryStringFromLocation, } from '../../utils/global_query_string/helpers'; import { URL_PARAM_KEY } from '../use_url_state'; -import { useIsExperimentalFeatureEnabled } from '../use_experimental_features'; /** * After the initial load of the security solution, timeline is not updated when the timeline URL search value is changed @@ -42,10 +41,6 @@ export const useQueryTimelineByIdOnUrlChange = () => { const oldSearch = usePrevious(search); const timelineIdFromReduxStore = flyoutTimeline?.savedObjectId ?? ''; - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const [previousTimeline, currentTimeline] = useMemo(() => { const oldUrlStateString = getQueryStringKeyValue({ urlKey: URL_PARAM_KEY.timeline, @@ -74,18 +69,9 @@ export const useQueryTimelineByIdOnUrlChange = () => { graphEventId, timelineId: newId, openTimeline: true, - unifiedComponentsInTimelineDisabled, }); } - }, [ - timelineIdFromReduxStore, - oldId, - newId, - activeTab, - graphEventId, - queryTimelineById, - unifiedComponentsInTimelineDisabled, - ]); + }, [timelineIdFromReduxStore, oldId, newId, activeTab, graphEventId, queryTimelineById]); }; export const getQueryStringKeyValue = ({ search, urlKey }: { search: string; urlKey: string }) => diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_timeline_click.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_timeline_click.tsx index db42ca9b774bd..f5e866f240e16 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_timeline_click.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_timeline_click.tsx @@ -8,25 +8,19 @@ import { useCallback } from 'react'; import { useQueryTimelineById } from '../../../timelines/components/open_timeline/helpers'; import type { TimelineErrorCallback } from '../../../timelines/components/open_timeline/types'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; export const useTimelineClick = () => { const queryTimelineById = useQueryTimelineById(); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const handleTimelineClick = useCallback( (timelineId: string, onError: TimelineErrorCallback, graphEventId?: string) => { queryTimelineById({ graphEventId, timelineId, onError, - unifiedComponentsInTimelineDisabled, }); }, - [queryTimelineById, unifiedComponentsInTimelineDisabled] + [queryTimelineById] ); return handleTimelineClick; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index a68d773468e75..a67eb08496dd0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -19,7 +19,6 @@ import { useApi } from '@kbn/securitysolution-list-hooks'; import type { Filter } from '@kbn/es-query'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { isEmpty } from 'lodash'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { createHistoryEntry } from '../../../../common/utils/global_query_string/helpers'; import { useKibana } from '../../../../common/lib/kibana'; import { TimelineId } from '../../../../../common/types/timeline'; @@ -35,7 +34,6 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { ALERTS_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { defaultUdtHeaders } from '../../../../timelines/components/timeline/unified_components/default_headers'; -import { defaultHeaders } from '../../../../timelines/components/timeline/body/column_headers/default_headers'; interface UseInvestigateInTimelineActionProps { ecsRowData?: Ecs | Ecs[] | null; @@ -146,21 +144,13 @@ export const useInvestigateInTimeline = ({ timelineType: TimelineTypeEnum.default, }); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const updateTimeline = useUpdateTimeline(); const createTimeline = useCallback( async ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => { const newColumns = timeline.columns; const newColumnsOverride = - !newColumns || isEmpty(newColumns) - ? !unifiedComponentsInTimelineDisabled - ? defaultUdtHeaders - : defaultHeaders - : newColumns; + !newColumns || isEmpty(newColumns) ? defaultUdtHeaders : newColumns; await clearActiveTimeline(); updateTimelineIsLoading({ id: TimelineId.active, isLoading: false }); @@ -175,7 +165,6 @@ export const useInvestigateInTimeline = ({ indexNames: timeline.indexNames ?? [], show: true, excludedRowRendererIds: - !unifiedComponentsInTimelineDisabled && timeline.timelineType !== TimelineTypeEnum.template ? timeline.excludedRowRendererIds : [], @@ -184,12 +173,7 @@ export const useInvestigateInTimeline = ({ ruleNote, }); }, - [ - updateTimeline, - updateTimelineIsLoading, - clearActiveTimeline, - unifiedComponentsInTimelineDisabled, - ] + [updateTimeline, updateTimelineIsLoading, clearActiveTimeline] ); const investigateInTimelineAlertClick = useCallback(async () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx index a8eec62f2d58a..27c2699cdf195 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx @@ -10,7 +10,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; import type { EqlOptionsSelected } from '@kbn/timelines-plugin/common'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useSourcererDataView } from '../../../../sourcerer/containers'; @@ -48,10 +47,6 @@ export const useRuleFromTimeline = (setRuleQuery: SetRuleQuery): RuleFromTimelin SourcererScopeName.timeline ); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const isEql = useRef(false); // selectedTimeline = timeline to set rule from @@ -200,11 +195,10 @@ export const useRuleFromTimeline = (setRuleQuery: SetRuleQuery): RuleFromTimelin queryTimelineById({ timelineId, onOpenTimeline, - unifiedComponentsInTimelineDisabled, }); } }, - [onOpenTimeline, queryTimelineById, selectedTimeline, unifiedComponentsInTimelineDisabled] + [onOpenTimeline, queryTimelineById, selectedTimeline] ); const [urlStateInitialized, setUrlStateInitialized] = useState(false); diff --git a/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.test.tsx index 85ecfce68e5d9..c6a2629494758 100644 --- a/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.test.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { OpenTimelineButtonIcon } from './open_timeline_button'; import type { Note } from '../../../common/api/timeline'; import { OPEN_TIMELINE_BUTTON_TEST_ID } from './test_ids'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useQueryTimelineById } from '../../timelines/components/open_timeline/helpers'; jest.mock('../../common/hooks/use_experimental_features'); @@ -40,11 +39,6 @@ describe('OpenTimelineButtonIcon', () => { const openTimeline = jest.fn(); (useQueryTimelineById as jest.Mock).mockReturnValue(openTimeline); - const unifiedComponentsInTimelineDisabled = false; - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue( - unifiedComponentsInTimelineDisabled - ); - const { getByTestId } = render(); const button = getByTestId(`${OPEN_TIMELINE_BUTTON_TEST_ID}-${index}`); @@ -55,7 +49,6 @@ describe('OpenTimelineButtonIcon', () => { onOpenTimeline: undefined, timelineId: note.timelineId, timelineType: undefined, - unifiedComponentsInTimelineDisabled, }); }); }); diff --git a/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.tsx b/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.tsx index b44ffd55a767a..91f3a722ebeeb 100644 --- a/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/notes/components/open_timeline_button.tsx @@ -8,7 +8,6 @@ import React, { memo, useCallback } from 'react'; import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useQueryTimelineById } from '../../timelines/components/open_timeline/helpers'; import { OPEN_TIMELINE_BUTTON_TEST_ID } from './test_ids'; import type { Note } from '../../../common/api/timeline'; @@ -32,10 +31,6 @@ export interface OpenTimelineButtonIconProps { * Renders a button to open the timeline associated with a note */ export const OpenTimelineButtonIcon = memo(({ note, index }: OpenTimelineButtonIconProps) => { - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const queryTimelineById = useQueryTimelineById(); const openTimeline = useCallback( ({ timelineId }: { timelineId: string }) => @@ -44,9 +39,8 @@ export const OpenTimelineButtonIcon = memo(({ note, index }: OpenTimelineButtonI onOpenTimeline: undefined, timelineId, timelineType: undefined, - unifiedComponentsInTimelineDisabled, }), - [queryTimelineById, unifiedComponentsInTimelineDisabled] + [queryTimelineById] ); return ( diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx index a167791a7b06c..97310f8a90f23 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx @@ -8,7 +8,6 @@ import { EuiHorizontalRule, EuiText } from '@elastic/eui'; import React, { useCallback, useMemo, useEffect } from 'react'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { SortFieldTimelineEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { useGetAllTimeline } from '../../../timelines/containers/all'; import { useQueryTimelineById } from '../../../timelines/components/open_timeline/helpers'; @@ -33,10 +32,6 @@ interface Props { const PAGE_SIZE = 3; const StatefulRecentTimelinesComponent: React.FC = ({ filterBy }) => { - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const { formatUrl } = useFormatUrl(SecurityPageName.timelines); const { navigateToApp } = useKibana().services.application; @@ -47,10 +42,9 @@ const StatefulRecentTimelinesComponent: React.FC = ({ filterBy }) => { queryTimelineById({ duplicate, timelineId, - unifiedComponentsInTimelineDisabled, }); }, - [queryTimelineById, unifiedComponentsInTimelineDisabled] + [queryTimelineById] ); const goToTimelines = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx index e1fdbe8817041..c26b34dffcaf4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx @@ -10,9 +10,7 @@ import React from 'react'; import { NewTimelineButton } from './new_timeline_button'; import { TimelineId } from '../../../../../common/types'; import { timelineActions } from '../../../store'; -import { defaultHeaders } from '../../timeline/body/column_headers/default_headers'; import { TestProviders } from '../../../../common/mock'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { RowRendererValues } from '../../../../../common/api/timeline'; import { defaultUdtHeaders } from '../../timeline/unified_components/default_headers'; @@ -76,26 +74,5 @@ describe('NewTimelineButton', () => { excludedRowRendererIds: RowRendererValues, }); }); - - // disable unified components in timeline - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - - getByTestId('timeline-modal-new-timeline-dropdown-button').click(); - getByTestId('timeline-modal-new-timeline').click(); - - spy.mockClear(); - - await waitFor(() => { - expect(spy).toHaveBeenCalledWith({ - columns: defaultHeaders, - dataViewId, - id: TimelineId.test, - indexNames: selectedPatterns, - show: true, - timelineType: 'default', - updated: undefined, - excludedRowRendererIds: [], - }); - }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/netflow/__snapshots__/index.test.tsx.snap index e881d9d5d1ce1..1267a66e0f6d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/netflow/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/__snapshots__/index.test.tsx.snap @@ -119,12 +119,13 @@ tr:hover .c3:focus::before { margin-right: 5px; } -.c7 { - margin-right: 3px; +.c15 { + position: relative; + top: 1px; } -.c8 { - margin: 0 5px; +.c14 { + margin-right: 5px; } .c17 { @@ -155,13 +156,12 @@ tr:hover .c3:focus::before { margin: 0 5px; } -.c15 { - position: relative; - top: 1px; +.c7 { + margin-right: 3px; } -.c14 { - margin-right: 5px; +.c8 { + margin: 0 5px; } .c12 { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index a3428ae6f2e1d..5da977c30a410 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -36,12 +36,8 @@ import { } from './__mocks__'; import { resolveTimeline } from '../../containers/api'; import { defaultUdtHeaders } from '../timeline/unified_components/default_headers'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import type { ExperimentalFeatures } from '../../../../common'; -import { allowedExperimentalValues } from '../../../../common'; jest.mock('../../../common/hooks/use_experimental_features'); -const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('react-redux', () => { const actual = jest.requireActual('react-redux'); @@ -146,14 +142,6 @@ describe('helpers', () => { beforeEach(() => { mockResults = cloneDeep(mockTimelineResults); - - (useIsExperimentalFeatureEnabledMock as jest.Mock).mockImplementation( - (featureFlag: keyof ExperimentalFeatures) => { - return featureFlag === 'unifiedComponentsInTimelineDisabled' - ? false - : allowedExperimentalValues[featureFlag]; - } - ); }); describe('#getPinnedEventCount', () => { @@ -500,8 +488,7 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel( timeline, false, - TimelineTypeEnum.template, - false + TimelineTypeEnum.template ); expect(newTimeline).toEqual({ ...defaultTimeline, @@ -523,12 +510,7 @@ describe('helpers', () => { timelineType: TimelineTypeEnum.default, }; - const newTimeline = defaultTimelineToTimelineModel( - timeline, - false, - TimelineTypeEnum.default, - false - ); + const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineTypeEnum.default); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, @@ -538,7 +520,7 @@ describe('helpers', () => { }); }); - test('should produce correct model if unifiedComponentsInTimelineDisabled is false', () => { + test('should produce correct model', () => { const timeline = { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', @@ -547,12 +529,7 @@ describe('helpers', () => { timelineType: TimelineTypeEnum.default, }; - const newTimeline = defaultTimelineToTimelineModel( - timeline, - false, - TimelineTypeEnum.default, - false - ); + const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineTypeEnum.default); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, @@ -564,7 +541,7 @@ describe('helpers', () => { }); }); - test('should produce correct model if unifiedComponentsInTimelineDisabled == false and custom set of columns is passed', () => { + test('should produce correct model if custom set of columns is passed', () => { const customColumns = defaultUdtHeaders.slice(0, 2); const timeline = { savedObjectId: 'savedObject-1', @@ -575,12 +552,7 @@ describe('helpers', () => { columns: customColumns as ColumnHeaderResult[], }; - const newTimeline = defaultTimelineToTimelineModel( - timeline, - false, - TimelineTypeEnum.default, - false - ); + const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineTypeEnum.default); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, @@ -592,7 +564,7 @@ describe('helpers', () => { }); }); - test('should produce correct model if unifiedComponentsInTimelineDisabled == false and custom set of excludedRowRendererIds is passed', () => { + test('should produce correct model if custom set of excludedRowRendererIds is passed', () => { const excludedRowRendererIds: RowRendererId[] = ['zeek']; const timeline = { savedObjectId: 'savedObject-1', @@ -603,12 +575,7 @@ describe('helpers', () => { excludedRowRendererIds, }; - const newTimeline = defaultTimelineToTimelineModel( - timeline, - false, - TimelineTypeEnum.default, - false - ); + const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineTypeEnum.default); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, @@ -649,7 +616,7 @@ describe('helpers', () => { }); }); - describe('open a timeline', () => { + describe('open a timeline 1', () => { const selectedTimeline = { ...mockSelectedTimeline, }; @@ -715,7 +682,8 @@ describe('helpers', () => { describe('update a timeline', () => { const selectedTimeline = { ...mockSelectedTimeline }; - + const untitledTimeline = { ...mockSelectedTimeline, title: '' }; + const onOpenTimeline = jest.fn(); const args: QueryTimelineById = { duplicate: false, graphEventId: '', @@ -724,146 +692,76 @@ describe('helpers', () => { openTimeline: true, }; - beforeAll(async () => { + beforeEach(async () => { (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); - renderHook(async () => { - const queryTimelineById = useQueryTimelineById(); - await queryTimelineById(args); - }); }); - afterAll(() => { + afterEach(() => { jest.clearAllMocks(); }); - test('dispatch updateIsLoading to true', () => { - expect(dispatchUpdateIsLoading).toBeCalledWith({ - id: TimelineId.active, - isLoading: true, - }); - }); - - test('get timeline by Id', () => { - expect(resolveTimeline).toHaveBeenCalled(); - }); - - test('should not override daterange if TimelineStatus is active', () => { - const { timeline } = formatTimelineResponseToModel( - omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), - args.duplicate, - args.timelineType - ); - - expect(mockUpdateTimeline).toBeCalledWith({ - timeline: { - ...timeline, - graphEventId: '', - show: true, - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - }, - preventSettingQuery: true, - duplicate: false, - from: '2020-07-07T08:20:18.966Z', - to: '2020-07-08T08:20:18.966Z', - notes: [], - id: TimelineId.active, - resolveTimelineConfig: { - outcome: 'exactMatch', - alias_target_id: undefined, - }, - }); - }); - - test('dispatch updateIsLoading to false', () => { - expect(dispatchUpdateIsLoading).toBeCalledWith({ - id: TimelineId.active, - isLoading: false, - }); - }); - }); - - describe('open an immutable template', () => { - const template = { ...mockSelectedTemplate }; - const onOpenTimeline = jest.fn(); - const args = { - duplicate: false, - graphEventId: '', - timelineId: '', - timelineType: TimelineTypeEnum.template, - onOpenTimeline, - openTimeline: true, - }; - - beforeAll(async () => { - (resolveTimeline as jest.Mock).mockResolvedValue(template); + test('should get timeline by Id with correct statuses', async () => { renderHook(async () => { const queryTimelineById = useQueryTimelineById(); - queryTimelineById(args); + await queryTimelineById(args); }); - }); - afterAll(() => { - (resolveTimeline as jest.Mock).mockReset(); - jest.clearAllMocks(); - }); - - test('dispatch updateIsLoading to true', () => { expect(dispatchUpdateIsLoading).toBeCalledWith({ id: TimelineId.active, isLoading: true, }); - }); - test('get timeline by Id', () => { - expect(resolveTimeline).toHaveBeenCalled(); - }); - - test('override daterange if TimelineStatus is immutable', () => { + // expect(resolveTimeline).toHaveBeenCalled(); const { timeline } = formatTimelineResponseToModel( - omitTypenameInTimeline(getOr({}, 'data.timeline', template)), + omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType ); - expect(onOpenTimeline).toBeCalledWith({ - ...timeline, - dateRange: { - end: '2020-10-28T11:37:31.655Z', - start: '2020-10-27T11:37:31.655Z', - }, + + await waitFor(() => { + expect(mockUpdateTimeline).toHaveBeenCalledWith({ + timeline: { + ...timeline, + graphEventId: '', + show: true, + dateRange: { + start: '2020-07-07T08:20:18.966Z', + end: '2020-07-08T08:20:18.966Z', + }, + }, + preventSettingQuery: true, + duplicate: false, + from: '2020-07-07T08:20:18.966Z', + to: '2020-07-08T08:20:18.966Z', + notes: [], + id: TimelineId.active, + resolveTimelineConfig: { + outcome: 'exactMatch', + alias_target_id: undefined, + }, + }); }); - }); - test('dispatch updateIsLoading to false', () => { expect(dispatchUpdateIsLoading).toBeCalledWith({ id: TimelineId.active, isLoading: false, }); }); - }); - describe('open a timeline when unifiedComponentsInTimelineDisabled is false', () => { - const untitledTimeline = { ...mockSelectedTimeline, title: '' }; - const onOpenTimeline = jest.fn(); - afterEach(() => { - jest.clearAllMocks(); - }); - it('should update timeline correctly when timeline is untitled', async () => { - const args: QueryTimelineById = { + test('should update timeline correctly when timeline is untitled', async () => { + (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); + const newArgs: QueryTimelineById = { duplicate: false, graphEventId: '', timelineId: undefined, timelineType: TimelineTypeEnum.default, onOpenTimeline, openTimeline: true, - unifiedComponentsInTimelineDisabled: false, }; (resolveTimeline as jest.Mock).mockResolvedValue(untitledTimeline); renderHook(async () => { const queryTimelineById = useQueryTimelineById(); - queryTimelineById(args); + queryTimelineById(newArgs); }); expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({ @@ -886,17 +784,7 @@ describe('helpers', () => { }); }); - it('should update timeline correctly when timeline is already saved and onOpenTimeline is not provided', async () => { - const args: QueryTimelineById = { - duplicate: false, - graphEventId: '', - timelineId: TimelineId.active, - timelineType: TimelineTypeEnum.default, - onOpenTimeline: undefined, - openTimeline: true, - unifiedComponentsInTimelineDisabled: false, - }; - + test('should update timeline correctly when timeline is already saved and onOpenTimeline is not provided', async () => { (resolveTimeline as jest.Mock).mockResolvedValue(mockSelectedTimeline); renderHook(async () => { const queryTimelineById = useQueryTimelineById(); @@ -925,17 +813,7 @@ describe('helpers', () => { }); }); - it('should update timeline correctly when timeline is already saved and onOpenTimeline IS provided', async () => { - const args: QueryTimelineById = { - duplicate: false, - graphEventId: '', - timelineId: TimelineId.active, - timelineType: TimelineTypeEnum.default, - onOpenTimeline, - openTimeline: true, - unifiedComponentsInTimelineDisabled: false, - }; - + test('should update timeline correctly when timeline is already saved and onOpenTimeline IS provided', async () => { (resolveTimeline as jest.Mock).mockResolvedValue(mockSelectedTimeline); renderHook(async () => { const queryTimelineById = useQueryTimelineById(); @@ -956,16 +834,75 @@ describe('helpers', () => { }); }); }); + + describe('open an immutable template', () => { + const template = { ...mockSelectedTemplate }; + const onOpenTimeline = jest.fn(); + const args = { + duplicate: false, + graphEventId: '', + timelineId: '', + timelineType: TimelineTypeEnum.template, + onOpenTimeline, + openTimeline: true, + }; + + beforeAll(async () => { + (resolveTimeline as jest.Mock).mockResolvedValue(template); + renderHook(async () => { + const queryTimelineById = useQueryTimelineById(); + queryTimelineById(args); + }); + }); + + afterAll(() => { + (resolveTimeline as jest.Mock).mockReset(); + jest.clearAllMocks(); + }); + + test('dispatch updateIsLoading to true', () => { + expect(dispatchUpdateIsLoading).toBeCalledWith({ + id: TimelineId.active, + isLoading: true, + }); + }); + + test('get timeline by Id', () => { + expect(resolveTimeline).toHaveBeenCalled(); + }); + + test('override daterange if TimelineStatus is immutable', () => { + const { timeline } = formatTimelineResponseToModel( + omitTypenameInTimeline(getOr({}, 'data.timeline', template)), + args.duplicate, + args.timelineType + ); + expect(onOpenTimeline).toBeCalledWith({ + ...timeline, + dateRange: { + end: '2020-10-28T11:37:31.655Z', + start: '2020-10-27T11:37:31.655Z', + }, + }); + }); + + test('dispatch updateIsLoading to false', () => { + expect(dispatchUpdateIsLoading).toBeCalledWith({ + id: TimelineId.active, + isLoading: false, + }); + }); + }); }); describe('omitTypenameInTimeline', () => { - test('it does not modify the passed in timeline if no __typename exists', () => { + test('should not modify the passed in timeline if no __typename exists', () => { const result = omitTypenameInTimeline(mockGetOneTimelineResult); expect(result).toEqual(mockGetOneTimelineResult); }); - test('it returns timeline with __typename removed when it exists', () => { + test('should return timeline with __typename removed when it exists', () => { const mockTimeline = { ...mockGetOneTimelineResult, __typename: 'something, something', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 000a7b226561e..b3f84a82d4ed0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -35,10 +35,7 @@ import { useUpdateTimeline } from './use_update_timeline'; import type { TimelineModel } from '../../store/model'; import { timelineDefaults } from '../../store/defaults'; -import { - defaultColumnHeaderType, - defaultHeaders, -} from '../timeline/body/column_headers/default_headers'; +import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers'; import type { OpenTimelineResult, TimelineErrorCallback } from './types'; import { IS_OPERATOR } from '../timeline/data_providers/data_provider'; @@ -238,13 +235,10 @@ export const getTimelineStatus = ( export const defaultTimelineToTimelineModel = ( timeline: TimelineResponse, duplicate: boolean, - timelineType?: TimelineType, - unifiedComponentsInTimelineDisabled?: boolean + timelineType?: TimelineType ): TimelineModel => { const isTemplate = timeline.timelineType === TimelineTypeEnum.template; - const defaultHeadersValue = !unifiedComponentsInTimelineDisabled - ? defaultUdtHeaders - : defaultHeaders; + const defaultHeadersValue = defaultUdtHeaders; const timelineEntries = { ...timeline, @@ -294,18 +288,12 @@ export const defaultTimelineToTimelineModel = ( export const formatTimelineResponseToModel = ( timelineToOpen: TimelineResponse, duplicate: boolean = false, - timelineType?: TimelineType, - unifiedComponentsInTimelineDisabled?: boolean + timelineType?: TimelineType ): { notes: Note[] | null | undefined; timeline: TimelineModel } => { const { notes, ...timelineModel } = timelineToOpen; return { notes, - timeline: defaultTimelineToTimelineModel( - timelineModel, - duplicate, - timelineType, - unifiedComponentsInTimelineDisabled - ), + timeline: defaultTimelineToTimelineModel(timelineModel, duplicate, timelineType), }; }; @@ -319,11 +307,6 @@ export interface QueryTimelineById { onOpenTimeline?: (timeline: TimelineModel) => void; openTimeline?: boolean; savedSearchId?: string; - /* - * Below feature flag will be removed once - * unified components have been fully migrated - * */ - unifiedComponentsInTimelineDisabled?: boolean; } export const useQueryTimelineById = () => { @@ -347,7 +330,6 @@ export const useQueryTimelineById = () => { onOpenTimeline, openTimeline = true, savedSearchId, - unifiedComponentsInTimelineDisabled = false, }: QueryTimelineById) => { updateIsLoading({ id: TimelineId.active, isLoading: true }); if (timelineId == null) { @@ -359,14 +341,14 @@ export const useQueryTimelineById = () => { to: DEFAULT_TO_MOMENT.toISOString(), timeline: { ...timelineDefaults, - columns: !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders, + columns: defaultUdtHeaders, id: TimelineId.active, activeTab: activeTimelineTab, show: openTimeline, initialized: true, savedSearchId: savedSearchId ?? null, excludedRowRendererIds: - !unifiedComponentsInTimelineDisabled && timelineType !== TimelineTypeEnum.template + timelineType !== TimelineTypeEnum.template ? timelineDefaults.excludedRowRendererIds : [], }, @@ -384,8 +366,7 @@ export const useQueryTimelineById = () => { const { timeline, notes } = formatTimelineResponseToModel( timelineToOpen, duplicate, - timelineType, - unifiedComponentsInTimelineDisabled + timelineType ); if (onOpenTimeline != null) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index cdb61ecf61f6e..6afd900185af7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -9,7 +9,6 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { encode } from '@kbn/rison'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { RULE_FROM_EQL_URL_PARAM, RULE_FROM_TIMELINE_URL_PARAM, @@ -25,8 +24,6 @@ import { createTimeline as dispatchCreateNewTimeline } from '../../store/actions import { useGetAllTimeline } from '../../containers/all'; -import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; - import { OpenTimeline } from './open_timeline'; import { OPEN_TIMELINE_CLASS_NAME, useQueryTimelineById } from './helpers'; import { OpenTimelineModalBody } from './open_timeline_modal/open_timeline_modal_body'; @@ -160,9 +157,6 @@ export const StatefulOpenTimelineComponent = React.memo( ); const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); const { customTemplateTimelineCount, @@ -251,13 +245,11 @@ export const StatefulOpenTimelineComponent = React.memo( dispatch( dispatchCreateNewTimeline({ id: TimelineId.active, - columns: !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders, + columns: defaultUdtHeaders, dataViewId, indexNames: selectedPatterns, show: false, - excludedRowRendererIds: !unifiedComponentsInTimelineDisabled - ? timelineDefaults.excludedRowRendererIds - : [], + excludedRowRendererIds: timelineDefaults.excludedRowRendererIds, }) ); } @@ -265,15 +257,7 @@ export const StatefulOpenTimelineComponent = React.memo( await deleteTimelinesByIds(timelineIds, searchIds); refetch(); }, - [ - startTransaction, - timelineSavedObjectId, - refetch, - dispatch, - dataViewId, - selectedPatterns, - unifiedComponentsInTimelineDisabled, - ] + [startTransaction, timelineSavedObjectId, refetch, dispatch, dataViewId, selectedPatterns] ); const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback( @@ -374,7 +358,6 @@ export const StatefulOpenTimelineComponent = React.memo( onOpenTimeline, timelineId, timelineType: timelineTypeToOpen, - unifiedComponentsInTimelineDisabled, }); }, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index f502c1b8884aa..991355b1177bb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -12,11 +12,9 @@ import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { isTab } from '@kbn/timelines-plugin/public'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useUserPrivileges } from '../../../common/components/user_privileges'; import { timelineActions, timelineSelectors } from '../../store'; import { timelineDefaults } from '../../store/defaults'; -import { defaultHeaders } from './body/column_headers/default_headers'; import type { CellValueElementProps } from './cell_rendering'; import { SourcererScopeName } from '../../../sourcerer/store/model'; import { TimelineModalHeader } from '../modal/header'; @@ -75,10 +73,6 @@ const StatefulTimelineComponent: React.FC = ({ }) => { const dispatch = useDispatch(); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const containerElement = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const selectedPatternsSourcerer = useSelector((state: State) => { @@ -129,13 +123,11 @@ const StatefulTimelineComponent: React.FC = ({ dispatch( timelineActions.createTimeline({ id: timelineId, - columns: !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders, + columns: defaultUdtHeaders, dataViewId: selectedDataViewIdSourcerer, indexNames: selectedPatternsSourcerer, show: false, - excludedRowRendererIds: !unifiedComponentsInTimelineDisabled - ? timelineDefaults.excludedRowRendererIds - : [], + excludedRowRendererIds: timelineDefaults.excludedRowRendererIds, }) ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/__snapshots__/index.test.tsx.snap deleted file mode 100644 index e238e24ebce7a..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,1165 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Timeline rendering renders correctly against snapshot 1`] = ` - -`; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.test.tsx index 034f812373ec2..7c8949c1b6121 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; import useResizeObserver from 'use-resize-observer/polyfilled'; import type { Dispatch } from 'redux'; @@ -17,7 +16,6 @@ import { TestProviders } from '../../../../../common/mock/test_providers'; import type { Props as EqlTabContentComponentProps } from '.'; import { EqlTabContentComponent } from '.'; -import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../../containers'; import { useTimelineEventsDetails } from '../../../../containers/details'; @@ -26,6 +24,7 @@ import { mockSourcererScope } from '../../../../../sourcerer/containers/mocks'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import type { ExperimentalFeatures } from '../../../../../../common'; import { allowedExperimentalValues } from '../../../../../../common'; +import { render, screen } from '@testing-library/react'; jest.mock('../../../../containers', () => ({ useTimelineEvents: jest.fn(), @@ -54,18 +53,25 @@ mockUseResizeObserver.mockImplementation(() => ({})); jest.mock('../../../../../common/lib/kibana'); -describe('Timeline', () => { +describe('EQL Tab', () => { let props = {} as EqlTabContentComponentProps; const startDate = '2018-03-23T18:49:23.132Z'; const endDate = '2018-03-24T03:33:52.253Z'; - const mount = useMountAppended(); + beforeAll(() => { + // https://github.com/atlassian/react-beautiful-dnd/blob/4721a518356f72f1dac45b5fd4ee9d466aa2996b/docs/guides/setup-problem-detection-and-error-recovery.md#disable-logging + Object.defineProperty(window, '__@hello-pangea/dnd-disable-dev-warnings', { + get() { + return true; + }, + }); + }); beforeEach(() => { (useTimelineEvents as jest.Mock).mockReturnValue([ false, { - events: mockTimelineData, + events: mockTimelineData.slice(0, 1), pageInfo: { activePage: 0, totalPages: 10, @@ -78,9 +84,6 @@ describe('Timeline', () => { (useIsExperimentalFeatureEnabledMock as jest.Mock).mockImplementation( (feature: keyof ExperimentalFeatures) => { - if (feature === 'unifiedComponentsInTimelineDisabled') { - return true; - } return allowedExperimentalValues[feature]; } ); @@ -105,133 +108,45 @@ describe('Timeline', () => { }); describe('rendering', () => { - test('renders correctly against snapshot', () => { - const wrapper = shallow( - - - - ); - - expect(wrapper.find('EqlTabContentComponent')).toMatchSnapshot(); - }); - - test('it renders the timeline header', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true); - }); - - test('it renders the timeline table', () => { - const wrapper = mount( + test('should render the timeline table', async () => { + render( ); - expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual( - true - ); - }); - - test('it renders the timeline column headers', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper - .find( - `[data-test-subj="${TimelineTabs.eql}-events-table"] [data-test-subj="column-headers"]` - ) - .exists() - ).toEqual(true); - }); - - test('it does NOT renders the timeline global sorting icon in headers', () => { - const wrapper = mount( - - - - ); - expect( - wrapper - .find( - `[data-test-subj="${TimelineTabs.eql}-events-table"] [data-test-subj="column-headers"] [data-test-subj="timeline-sorting-fields"]` - ) - .exists() - ).toEqual(false); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); }); - test('it does render the timeline table when the source is loading with no events', () => { - (useSourcererDataView as jest.Mock).mockReturnValue({ - browserFields: {}, - loading: true, - indexPattern: {}, - selectedPatterns: [], - missingPatterns: [], - }); - const wrapper = mount( + test('it renders the timeline column headers', async () => { + render( ); - expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual( - true - ); - expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); - }); - - test('it does NOT render the timeline table when start is empty', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual( - true - ); - expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); }); - test('it does NOT render the timeline table when end is empty', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual( - true - ); - expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); - }); + test('should render correct placeholder when there are not results', async () => { + (useTimelineEvents as jest.Mock).mockReturnValue([ + false, + { + events: [], + pageInfo: { + activePage: 0, + totalPages: 10, + }, + }, + ]); - it('it does NOT render the timeline footer when query is empty', () => { - const wrapper = mount( + render( ); - expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(false); - }); - - it('it shows the timeline footer when query is non-empty', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(true); + expect(await screen.findByText('No results found')).toBeVisible(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx index e2881c8c24458..e41d9017d49be 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx @@ -17,23 +17,17 @@ import type { EuiDataGridControlColumn } from '@elastic/eui'; import { DataLoadingState } from '@kbn/unified-data-table'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import type { RunTimeMappings } from '@kbn/timelines-plugin/common/search_strategy'; +import { InputsModelId } from '../../../../../common/store/inputs/constants'; import { useKibana } from '../../../../../common/lib/kibana'; import { DocumentDetailsLeftPanelKey, DocumentDetailsRightPanelKey, } from '../../../../../flyout/document_details/shared/constants/panel_keys'; -import { InputsModelId } from '../../../../../common/store/inputs/constants'; -import type { ControlColumnProps } from '../../../../../../common/types'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { timelineActions, timelineSelectors } from '../../../../store'; import { useTimelineEvents } from '../../../../containers'; -import { StatefulBody } from '../../body'; -import { Footer, footerHeight } from '../../footer'; -import { calculateTotalPages } from '../../helpers'; -import { TimelineRefetch } from '../../refetch_timeline'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; -import { EventDetailsWidthProvider } from '../../../../../common/components/events_viewer/event_details_width_context'; import type { inputsModel, State } from '../../../../../common/store'; import { inputsSelectors } from '../../../../../common/store'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; @@ -42,19 +36,8 @@ import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useEqlEventsCountPortal } from '../../../../../common/hooks/use_timeline_events_count'; import type { TimelineModel } from '../../../../store/model'; import { useTimelineFullScreen } from '../../../../../common/containers/use_full_screen'; -import { - EventsCountBadge, - FullWidthFlexGroup, - ScrollableFlexItem, - StyledEuiFlyoutBody, - StyledEuiFlyoutFooter, -} from '../shared/layout'; -import { - TIMELINE_EMPTY_EVENTS, - isTimerangeSame, - timelineEmptyTrailingControlColumns, - TIMELINE_NO_SORTING, -} from '../shared/utils'; +import { EventsCountBadge, FullWidthFlexGroup } from '../shared/layout'; +import { isTimerangeSame, TIMELINE_NO_SORTING } from '../shared/utils'; import type { TimelineTabCommonProps } from '../shared/types'; import { UnifiedTimelineBody } from '../../body/unified_timeline_body'; import { EqlTabHeader } from './header'; @@ -63,6 +46,7 @@ import { useTimelineControlColumn } from '../shared/use_timeline_control_columns import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left'; import { useNotesInFlyout } from '../../properties/use_notes_in_flyout'; import { NotesFlyout } from '../../properties/notes_flyout'; +import { TimelineRefetch } from '../../refetch_timeline'; export type Props = TimelineTabCommonProps & PropsFromRedux; @@ -72,10 +56,8 @@ export const EqlTabContentComponent: React.FC = ({ end, eqlOptions, timelineId, - isLive, itemsPerPage, itemsPerPageOptions, - renderCellValue, rowRenderers, start, timerangeKind, @@ -88,7 +70,6 @@ export const EqlTabContentComponent: React.FC = ({ const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal(); const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen(); const { - browserFields, dataViewId, loading: loadingSourcerer, selectedPatterns, @@ -96,10 +77,6 @@ export const EqlTabContentComponent: React.FC = ({ } = useSourcererDataView(SourcererScopeName.timeline); const { augmentedColumnHeaders, timelineQueryFieldsFromColumns } = useTimelineColumns(columns); - const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( - 'unifiedComponentsInTimelineDisabled' - ); - const getManageTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const currentTimeline = useDeepEqualSelector((state) => @@ -132,7 +109,7 @@ export const EqlTabContentComponent: React.FC = ({ id: timelineId, indexNames: selectedPatterns, language: 'eql', - limit: !unifiedComponentsInTimelineDisabled ? sampleSize : itemsPerPage, + limit: sampleSize, runtimeMappings: sourcererDataView?.runtimeFieldMap as RunTimeMappings, skip: !canQueryTimeline(), startDate: start, @@ -267,103 +244,42 @@ export const EqlTabContentComponent: React.FC = ({ return ( <> - {!unifiedComponentsInTimelineDisabled ? ( - <> - - {totalCount >= 0 ? ( - {totalCount} - ) : null} - - {NotesFlyoutMemo} - - - - - ) : ( - <> - - {totalCount >= 0 ? {totalCount} : null} - - {NotesFlyoutMemo} - - - {unifiedHeader} - - - - - - - - {!isBlankTimeline && ( -