[8.16] [Security Solution] Remove `unifiedComponentsInTimelineDisabled` flag (#195959) (#197692)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Security Solution] Remove
`unifiedComponentsInTimelineDisabled` flag
(#195959)](https://github.com/elastic/kibana/pull/195959)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jatin
Kathuria","email":"jatin.kathuria@elastic.co"},"sourceCommit":{"committedDate":"2024-10-24T16:41:19Z","message":"[Security
Solution] Remove `unifiedComponentsInTimelineDisabled` flag
(#195959)\n\n## Summary\r\n\r\nHandles
https://github.com/elastic/security-team/issues/9645\r\n\r\nFollow Up PR
for removal of Old timeline Code
:\r\nhttps://github.com/elastic/kibana/pull/196243\r\n\r\n- This PR
removes `unifiedComponentsInTimelineDisabled` flag. What this\r\nmeans
that that unified components in Timeline are now enabled
by\r\ndefault.\r\n\r\n- Consequently, the old timeline table code
becomes obsolete and is also\r\nremoved. (
https://github.com/elastic/kibana/pull/196243)\r\n\r\n##
Changes\r\n\r\n1. Converted all cypress and jest tests that were testing
old Timeline\r\ntable to test new unified components in Timeline. If the
test for new\r\ntimeline already existed, old tests are also
removed.","sha":"c41178d2d6e952798548ccd7db691d5ceff62053","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","v9.0.0","Team:Threat
Hunting:Investigations","v8.16.0"],"title":"[Security Solution] Remove
`unifiedComponentsInTimelineDisabled`
flag","number":195959,"url":"https://github.com/elastic/kibana/pull/195959","mergeCommit":{"message":"[Security
Solution] Remove `unifiedComponentsInTimelineDisabled` flag
(#195959)\n\n## Summary\r\n\r\nHandles
https://github.com/elastic/security-team/issues/9645\r\n\r\nFollow Up PR
for removal of Old timeline Code
:\r\nhttps://github.com/elastic/kibana/pull/196243\r\n\r\n- This PR
removes `unifiedComponentsInTimelineDisabled` flag. What this\r\nmeans
that that unified components in Timeline are now enabled
by\r\ndefault.\r\n\r\n- Consequently, the old timeline table code
becomes obsolete and is also\r\nremoved. (
https://github.com/elastic/kibana/pull/196243)\r\n\r\n##
Changes\r\n\r\n1. Converted all cypress and jest tests that were testing
old Timeline\r\ntable to test new unified components in Timeline. If the
test for new\r\ntimeline already existed, old tests are also
removed.","sha":"c41178d2d6e952798548ccd7db691d5ceff62053"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195959","number":195959,"mergeCommit":{"message":"[Security
Solution] Remove `unifiedComponentsInTimelineDisabled` flag
(#195959)\n\n## Summary\r\n\r\nHandles
https://github.com/elastic/security-team/issues/9645\r\n\r\nFollow Up PR
for removal of Old timeline Code
:\r\nhttps://github.com/elastic/kibana/pull/196243\r\n\r\n- This PR
removes `unifiedComponentsInTimelineDisabled` flag. What this\r\nmeans
that that unified components in Timeline are now enabled
by\r\ndefault.\r\n\r\n- Consequently, the old timeline table code
becomes obsolete and is also\r\nremoved. (
https://github.com/elastic/kibana/pull/196243)\r\n\r\n##
Changes\r\n\r\n1. Converted all cypress and jest tests that were testing
old Timeline\r\ntable to test new unified components in Timeline. If the
test for new\r\ntimeline already existed, old tests are also
removed.","sha":"c41178d2d6e952798548ccd7db691d5ceff62053"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>
This commit is contained in:
Kibana Machine 2024-10-25 06:07:20 +11:00 committed by GitHub
parent d51d60e8a9
commit 87a2285fbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1481 additions and 6559 deletions

View file

@ -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.

View file

@ -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(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
expect(result.queryByTestId('show-row-renderers-gear')).toBeNull();
});
it('should not show the sorting settings', () => {
const result = render(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
expect(result.queryByTestId('timeline-sorting-fields')).toBeNull();
});
describe('Controls', () => {
it('should not show the event renderer settings', () => {
const result = render(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
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(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
result.getByTestId('show-row-renderers-gear');
});
it('should show the sorting settings', () => {
const result = render(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
result.getByTestId('timeline-sorting-fields');
});
it('should not show the sorting settings', () => {
const result = render(
<TestProviders>
<HeaderActions {...defaultProps} />
</TestProviders>
);
expect(result.queryByTestId('timeline-sorting-fields')).toBeNull();
});
});
});

View file

@ -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<HeaderActionProps> = memo(
({
width,
browserFields,
columnHeaders,
isEventViewer = false,
isSelectAllChecked,
onSelectAll,
showEventsSelect,
showSelectAllCheckbox,
showFullScreenToggle = true,
sort,
tabType,
timelineId,
fieldBrowserOptions,
}) => {
@ -91,10 +66,6 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = 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<HeaderActionProps> = 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<HeaderActionProps> = memo(
[columnHeaders, dispatch, timelineId, defaultColumns]
);
const ColumnSorting = useDataGridColumnSorting({
columns: myColumns,
sorting: sortedColumns,
schema: emptySchema,
schemaDetectors: emptySchemaDetectors,
displayValues,
});
return (
<ActionsContainer data-test-subj="header-actions-container">
{showSelectAllCheckbox && (
@ -242,11 +154,6 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = memo(
</EventsTh>
)}
{unifiedComponentsInTimelineDisabled && (
<EventsTh role="button">
<StatefulRowRenderersBrowser timelineId={timelineId} />
</EventsTh>
)}
{showFullScreenToggle && (
<EventsTh role="button">
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
@ -275,15 +182,6 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = memo(
</EventsThContent>
</EventsTh>
)}
{tabType !== TimelineTabs.eql && unifiedComponentsInTimelineDisabled && (
<EventsTh role="button" data-test-subj="timeline-sorting-fields">
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
<EuiToolTip content={i18n.SORT_FIELDS}>
<SortingColumnsContainer>{ColumnSorting}</SortingColumnsContainer>
</EuiToolTip>
</EventsThContent>
</EventsTh>
)}
{showEventsSelect && (
<EventsTh role="button">

View file

@ -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(() => {

View file

@ -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 }) =>

View file

@ -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;

View file

@ -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 () => {

View file

@ -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);

View file

@ -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(<OpenTimelineButtonIcon note={note} index={index} />);
const button = getByTestId(`${OPEN_TIMELINE_BUTTON_TEST_ID}-${index}`);
@ -55,7 +49,6 @@ describe('OpenTimelineButtonIcon', () => {
onOpenTimeline: undefined,
timelineId: note.timelineId,
timelineType: undefined,
unifiedComponentsInTimelineDisabled,
});
});
});

View file

@ -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 (

View file

@ -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<Props> = ({ filterBy }) => {
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const { formatUrl } = useFormatUrl(SecurityPageName.timelines);
const { navigateToApp } = useKibana().services.application;
@ -47,10 +42,9 @@ const StatefulRecentTimelinesComponent: React.FC<Props> = ({ filterBy }) => {
queryTimelineById({
duplicate,
timelineId,
unifiedComponentsInTimelineDisabled,
});
},
[queryTimelineById, unifiedComponentsInTimelineDisabled]
[queryTimelineById]
);
const goToTimelines = useCallback(

View file

@ -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: [],
});
});
});
});

View file

@ -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 {

View file

@ -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,65 +692,147 @@ describe('helpers', () => {
openTimeline: true,
};
beforeAll(async () => {
beforeEach(async () => {
(resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline);
});
afterEach(() => {
jest.clearAllMocks();
});
test('should get timeline by Id with correct statuses', async () => {
renderHook(async () => {
const queryTimelineById = useQueryTimelineById();
await queryTimelineById(args);
});
});
afterAll(() => {
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', () => {
// expect(resolveTimeline).toHaveBeenCalled();
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',
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,
},
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,
});
});
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,
};
(resolveTimeline as jest.Mock).mockResolvedValue(untitledTimeline);
renderHook(async () => {
const queryTimelineById = useQueryTimelineById();
queryTimelineById(newArgs);
});
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
id: TimelineId.active,
timeline: expect.objectContaining({
columns: defaultUdtHeaders,
}),
})
);
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: 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();
queryTimelineById(args);
});
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
await waitFor(() => {
expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
timeline: expect.objectContaining({
columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({
columnHeaderType: col.columnHeaderType,
id: col.id,
initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id)
?.initialWidth,
})),
}),
})
);
});
});
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();
queryTimelineById(args);
});
waitFor(() => {
expect(onOpenTimeline).toHaveBeenCalledWith(
expect.objectContaining({
columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({
columnHeaderType: col.columnHeaderType,
id: col.id,
initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id)
?.initialWidth,
})),
})
);
});
});
});
describe('open an immutable template', () => {
@ -843,129 +893,16 @@ describe('helpers', () => {
});
});
});
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 = {
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);
});
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
id: TimelineId.active,
timeline: expect.objectContaining({
columns: defaultUdtHeaders,
}),
})
);
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: false,
});
});
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,
};
(resolveTimeline as jest.Mock).mockResolvedValue(mockSelectedTimeline);
renderHook(async () => {
const queryTimelineById = useQueryTimelineById();
queryTimelineById(args);
});
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
await waitFor(() => {
expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
timeline: expect.objectContaining({
columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({
columnHeaderType: col.columnHeaderType,
id: col.id,
initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id)
?.initialWidth,
})),
}),
})
);
});
});
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,
};
(resolveTimeline as jest.Mock).mockResolvedValue(mockSelectedTimeline);
renderHook(async () => {
const queryTimelineById = useQueryTimelineById();
queryTimelineById(args);
});
waitFor(() => {
expect(onOpenTimeline).toHaveBeenCalledWith(
expect.objectContaining({
columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({
columnHeaderType: col.columnHeaderType,
id: col.id,
initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id)
?.initialWidth,
})),
})
);
});
});
});
});
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',

View file

@ -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) {

View file

@ -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<OpenTimelineOwnProps>(
);
const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline);
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const {
customTemplateTimelineCount,
@ -251,13 +245,11 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
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<OpenTimelineOwnProps>(
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<OpenTimelineOwnProps>(
onOpenTimeline,
timelineId,
timelineType: timelineTypeToOpen,
unifiedComponentsInTimelineDisabled,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -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<Props> = ({
}) => {
const dispatch = useDispatch();
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const containerElement = useRef<HTMLDivElement | null>(null);
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const selectedPatternsSourcerer = useSelector((state: State) => {
@ -129,13 +123,11 @@ const StatefulTimelineComponent: React.FC<Props> = ({
dispatch(
timelineActions.createTimeline({
id: timelineId,
columns: !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders,
columns: defaultUdtHeaders,
dataViewId: selectedDataViewIdSourcerer,
indexNames: selectedPatternsSourcerer,
show: false,
excludedRowRendererIds: !unifiedComponentsInTimelineDisabled
? timelineDefaults.excludedRowRendererIds
: [],
excludedRowRendererIds: timelineDefaults.excludedRowRendererIds,
})
);
}

View file

@ -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(
test('should render the timeline table', async () => {
render(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
expect(wrapper.find('EqlTabContentComponent')).toMatchSnapshot();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
});
test('it renders the timeline header', () => {
const wrapper = mount(
test('it renders the timeline column headers', async () => {
render(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true);
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
});
test('it renders the timeline table', () => {
const wrapper = mount(
test('should render correct placeholder when there are not results', async () => {
(useTimelineEvents as jest.Mock).mockReturnValue([
false,
{
events: [],
pageInfo: {
activePage: 0,
totalPages: 10,
},
},
]);
render(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
true
);
});
test('it renders the timeline column headers', () => {
const wrapper = mount(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
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(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
expect(
wrapper
.find(
`[data-test-subj="${TimelineTabs.eql}-events-table"] [data-test-subj="column-headers"] [data-test-subj="timeline-sorting-fields"]`
)
.exists()
).toEqual(false);
});
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(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
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(
<TestProviders>
<EqlTabContentComponent {...props} start={''} />
</TestProviders>
);
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 end is empty', () => {
const wrapper = mount(
<TestProviders>
<EqlTabContentComponent {...props} end={''} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
true
);
expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false);
});
it('it does NOT render the timeline footer when query is empty', () => {
const wrapper = mount(
<TestProviders>
<EqlTabContentComponent {...props} />
</TestProviders>
);
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(
<TestProviders>
<EqlTabContentComponent {...{ ...props, eqlOptions: { query: 'query' } }} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(true);
expect(await screen.findByText('No results found')).toBeVisible();
});
});
});

View file

@ -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<Props> = ({
end,
eqlOptions,
timelineId,
isLive,
itemsPerPage,
itemsPerPageOptions,
renderCellValue,
rowRenderers,
start,
timerangeKind,
@ -88,7 +70,6 @@ export const EqlTabContentComponent: React.FC<Props> = ({
const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal();
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
const {
browserFields,
dataViewId,
loading: loadingSourcerer,
selectedPatterns,
@ -96,10 +77,6 @@ export const EqlTabContentComponent: React.FC<Props> = ({
} = 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<Props> = ({
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<Props> = ({
return (
<>
{!unifiedComponentsInTimelineDisabled ? (
<>
<InPortal node={eqlEventsCountPortalNode}>
{totalCount >= 0 ? (
<EventsCountBadge data-test-subj="eql-events-count">{totalCount}</EventsCountBadge>
) : null}
</InPortal>
{NotesFlyoutMemo}
<FullWidthFlexGroup>
<UnifiedTimelineBody
header={unifiedHeader}
columns={augmentedColumnHeaders}
isSortEnabled={false}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={TIMELINE_NO_SORTING}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={isBlankTimeline ? 0 : totalCount}
onChangePage={loadPage}
activeTab={activeTab}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
/>
</FullWidthFlexGroup>
</>
) : (
<>
<InPortal node={eqlEventsCountPortalNode}>
{totalCount >= 0 ? <EventsCountBadge>{totalCount}</EventsCountBadge> : null}
</InPortal>
{NotesFlyoutMemo}
<TimelineRefetch
id={`${timelineId}-${TimelineTabs.eql}`}
inputId={InputsModelId.timeline}
inspect={inspect}
loading={isQueryLoading}
refetch={refetch}
/>
<FullWidthFlexGroup gutterSize="s" direction="column">
<ScrollableFlexItem grow={false}>{unifiedHeader}</ScrollableFlexItem>
<ScrollableFlexItem grow={true}>
<EventDetailsWidthProvider>
<StyledEuiFlyoutBody
data-test-subj={`${TimelineTabs.eql}-tab-flyout-body`}
className="timeline-flyout-body"
>
<StatefulBody
activePage={pageInfo.activePage}
browserFields={browserFields}
data={isBlankTimeline ? TIMELINE_EMPTY_EVENTS : events}
id={timelineId}
refetch={refetch}
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
sort={TIMELINE_NO_SORTING}
tabType={TimelineTabs.eql}
totalPages={calculateTotalPages({
itemsCount: totalCount,
itemsPerPage,
})}
leadingControlColumns={leadingControlColumns as ControlColumnProps[]}
trailingControlColumns={timelineEmptyTrailingControlColumns}
/>
</StyledEuiFlyoutBody>
<StyledEuiFlyoutFooter
data-test-subj={`${TimelineTabs.eql}-tab-flyout-footer`}
className="timeline-flyout-footer"
>
{!isBlankTimeline && (
<Footer
activePage={pageInfo?.activePage ?? 0}
data-test-subj="timeline-footer"
updatedAt={refreshedAt}
height={footerHeight}
id={timelineId}
isLive={isLive}
isLoading={isQueryLoading || loadingSourcerer}
itemsCount={isBlankTimeline ? 0 : events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangePage={loadPage}
totalCount={isBlankTimeline ? 0 : totalCount}
/>
)}
</StyledEuiFlyoutFooter>
</EventDetailsWidthProvider>
</ScrollableFlexItem>
</FullWidthFlexGroup>
</>
)}
<TimelineRefetch
id={`${timelineId}-${TimelineTabs.eql}`}
inputId={InputsModelId.timeline}
inspect={inspect}
loading={isQueryLoading}
refetch={refetch}
skip={!canQueryTimeline}
/>
<InPortal node={eqlEventsCountPortalNode}>
{totalCount >= 0 ? (
<EventsCountBadge data-test-subj="eql-events-count">{totalCount}</EventsCountBadge>
) : null}
</InPortal>
{NotesFlyoutMemo}
<FullWidthFlexGroup>
<UnifiedTimelineBody
header={unifiedHeader}
columns={augmentedColumnHeaders}
isSortEnabled={false}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={TIMELINE_NO_SORTING}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={isBlankTimeline ? 0 : totalCount}
onChangePage={loadPage}
activeTab={activeTab}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
/>
</FullWidthFlexGroup>
</>
);
};

View file

@ -5,18 +5,17 @@
* 2.0.
*/
import { shallow } from 'enzyme';
import React from 'react';
import useResizeObserver from 'use-resize-observer/polyfilled';
import type { Dispatch } from 'redux';
import { render, screen } from '@testing-library/react';
import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer';
import { defaultHeaders, mockTimelineData } from '../../../../../common/mock';
import { TestProviders } from '../../../../../common/mock/test_providers';
import { defaultRowRenderers } from '../../body/renderers';
import type { Sort } from '../../body/sort';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
import { TimelineId } from '../../../../../../common/types/timeline';
import { useTimelineEvents } from '../../../../containers';
import { useTimelineEventsDetails } from '../../../../containers/details';
import { useSourcererDataView } from '../../../../../sourcerer/containers';
@ -24,10 +23,11 @@ import { mockSourcererScope } from '../../../../../sourcerer/containers/mocks';
import type { Props as PinnedTabContentComponentProps } from '.';
import { PinnedTabContentComponent } from '.';
import { Direction } from '../../../../../../common/search_strategy';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import type { ExperimentalFeatures } from '../../../../../../common';
import { allowedExperimentalValues } from '../../../../../../common';
import { useKibana } from '../../../../../common/lib/kibana';
import { createStartServicesMock } from '../../../../../common/lib/kibana/kibana_react.mock';
jest.mock('../../../../containers', () => ({
useTimelineEvents: jest.fn(),
@ -51,48 +51,21 @@ const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer/polyfilled');
mockUseResizeObserver.mockImplementation(() => ({}));
const useAddToTimeline = () => ({
beginDrag: jest.fn(),
cancelDrag: jest.fn(),
dragToLocation: jest.fn(),
endDrag: jest.fn(),
hasDraggableLock: jest.fn(),
startDragToTimeline: jest.fn(),
});
jest.mock('../../../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../../../common/lib/kibana');
return {
...originalModule,
useKibana: jest.fn().mockReturnValue({
services: {
application: {
navigateToApp: jest.fn(),
getUrlForApp: jest.fn(),
},
cases: {
ui: {
getCasesContext: () => mockCasesContext,
},
},
uiSettings: {
get: jest.fn(),
},
savedObjects: {
client: {},
},
timelines: {
getLastUpdated: jest.fn(),
getFieldBrowser: jest.fn(),
getUseAddToTimeline: () => useAddToTimeline,
},
triggersActionsUi: { getFieldBrowser: jest.fn() },
},
}),
useKibana: jest.fn(),
useGetUserSavedObjectPermissions: jest.fn(),
};
});
const kibanaMockResult = {
services: createStartServicesMock(),
};
const useKibanaMock = useKibana as jest.Mock;
describe('PinnedTabContent', () => {
let props = {} as PinnedTabContentComponentProps;
const sort: Sort[] = [
@ -104,16 +77,23 @@ describe('PinnedTabContent', () => {
},
];
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,
totalPages: 1,
},
},
]);
@ -123,13 +103,12 @@ describe('PinnedTabContent', () => {
(useIsExperimentalFeatureEnabledMock as jest.Mock).mockImplementation(
(feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineDisabled') {
return true;
}
return allowedExperimentalValues[feature];
}
);
useKibanaMock.mockReturnValue(kibanaMockResult);
props = {
dispatch: {} as Dispatch,
columns: defaultHeaders,
@ -145,36 +124,14 @@ describe('PinnedTabContent', () => {
});
describe('rendering', () => {
test('renders correctly against snapshot', () => {
const wrapper = shallow(
test('should render timeline table correctly', async () => {
render(
<TestProviders>
<PinnedTabContentComponent {...props} />
</TestProviders>
);
expect(wrapper.find('PinnedTabContentComponent')).toMatchSnapshot();
});
test('it renders the timeline table', () => {
const wrapper = mount(
<TestProviders>
<PinnedTabContentComponent {...props} />
</TestProviders>
);
expect(
wrapper.find(`[data-test-subj="${TimelineTabs.pinned}-events-table"]`).exists()
).toEqual(true);
});
it('it shows the timeline footer', () => {
const wrapper = mount(
<TestProviders>
<PinnedTabContentComponent {...props} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(true);
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
});
});
});

View file

@ -7,45 +7,30 @@
import { isEmpty } from 'lodash/fp';
import React, { useMemo, useCallback, memo } from 'react';
import styled from 'styled-components';
import type { ConnectedProps } from 'react-redux';
import { connect } from 'react-redux';
import deepEqual from 'fast-deep-equal';
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 {
DocumentDetailsLeftPanelKey,
DocumentDetailsRightPanelKey,
} from '../../../../../flyout/document_details/shared/constants/panel_keys';
import type { ControlColumnProps } from '../../../../../../common/types';
import { useKibana } from '../../../../../common/lib/kibana';
import { timelineSelectors } from '../../../../store';
import type { Direction } from '../../../../../../common/search_strategy';
import { useTimelineEvents } from '../../../../containers';
import { defaultHeaders } from '../../body/column_headers/default_headers';
import { StatefulBody } from '../../body';
import { Footer, footerHeight } from '../../footer';
import { requiredFieldsForActions } from '../../../../../detections/components/alerts_table/default_config';
import { EventDetailsWidthProvider } from '../../../../../common/components/events_viewer/event_details_width_context';
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
import { timelineDefaults } from '../../../../store/defaults';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { useSourcererDataView } from '../../../../../sourcerer/containers';
import { useTimelineFullScreen } from '../../../../../common/containers/use_full_screen';
import type { TimelineModel } from '../../../../store/model';
import type { State } from '../../../../../common/store';
import { calculateTotalPages } from '../../helpers';
import { TimelineTabs } from '../../../../../../common/types/timeline';
import { ExitFullScreen } from '../../../../../common/components/exit_full_screen';
import { UnifiedTimelineBody } from '../../body/unified_timeline_body';
import {
FullWidthFlexGroup,
ScrollableFlexItem,
StyledEuiFlyoutBody,
StyledEuiFlyoutFooter,
} from '../shared/layout';
import type { TimelineTabCommonProps } from '../shared/types';
import { useTimelineColumns } from '../shared/use_timeline_columns';
import { useTimelineControlColumn } from '../shared/use_timeline_control_columns';
@ -53,10 +38,6 @@ import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left';
import { useNotesInFlyout } from '../../properties/use_notes_in_flyout';
import { NotesFlyout } from '../../properties/notes_flyout';
const ExitFullScreenContainer = styled.div`
width: 180px;
`;
interface PinnedFilter {
bool: {
should: Array<{ match_phrase: { _id: string } }>;
@ -66,8 +47,6 @@ interface PinnedFilter {
export type Props = TimelineTabCommonProps & PropsFromRedux;
const trailingControlColumns: ControlColumnProps[] = []; // stable reference
const rowDetailColumn = [
{
id: 'row-details',
@ -84,22 +63,13 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
itemsPerPage,
itemsPerPageOptions,
pinnedEventIds,
renderCellValue,
rowRenderers,
sort,
eventIdToNoteIds,
}) => {
const { telemetry } = useKibana().services;
const {
browserFields,
dataViewId,
loading: loadingSourcerer,
sourcererDataView,
selectedPatterns,
} = useSourcererDataView(SourcererScopeName.timeline);
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
const { dataViewId, sourcererDataView, selectedPatterns } = useSourcererDataView(
SourcererScopeName.timeline
);
const filterQuery = useMemo(() => {
@ -257,11 +227,6 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
onToggleShowNotes,
});
const isQueryLoading = useMemo(
() => [DataLoadingState.loading, DataLoadingState.loadingMore].includes(queryLoadingState),
[queryLoadingState]
);
const NotesFlyoutMemo = useMemo(() => {
return (
<NotesFlyout
@ -276,92 +241,29 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
);
}, [associateNote, closeNotesFlyout, isNotesFlyoutVisible, noteEventId, notes, timelineId]);
if (!unifiedComponentsInTimelineDisabled) {
return (
<>
{NotesFlyoutMemo}
<UnifiedTimelineBody
header={<></>}
columns={augmentedColumnHeaders}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={queryLoadingState}
totalCount={events.length}
onChangePage={loadPage}
activeTab={TimelineTabs.pinned}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
trailingControlColumns={rowDetailColumn}
/>
</>
);
}
return (
<>
{NotesFlyoutMemo}
<FullWidthFlexGroup data-test-subj={`${TimelineTabs.pinned}-tab`}>
<ScrollableFlexItem grow={2}>
{timelineFullScreen && setTimelineFullScreen != null && (
<ExitFullScreenContainer>
<ExitFullScreen
fullScreen={timelineFullScreen}
setFullScreen={setTimelineFullScreen}
/>
</ExitFullScreenContainer>
)}
<EventDetailsWidthProvider>
<StyledEuiFlyoutBody
data-test-subj={`${TimelineTabs.pinned}-tab-flyout-body`}
className="timeline-flyout-body"
>
<StatefulBody
activePage={pageInfo.activePage}
browserFields={browserFields}
data={events}
id={timelineId}
refetch={refetch}
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
sort={sort}
tabType={TimelineTabs.pinned}
totalPages={calculateTotalPages({
itemsCount: totalCount,
itemsPerPage,
})}
leadingControlColumns={leadingControlColumns as ControlColumnProps[]}
trailingControlColumns={trailingControlColumns}
/>
</StyledEuiFlyoutBody>
<StyledEuiFlyoutFooter
data-test-subj={`${TimelineTabs.pinned}-tab-flyout-footer`}
className="timeline-flyout-footer"
>
<Footer
activePage={pageInfo.activePage}
data-test-subj="timeline-footer"
updatedAt={refreshedAt}
height={footerHeight}
id={timelineId}
isLive={false}
isLoading={isQueryLoading || loadingSourcerer}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangePage={loadPage}
totalCount={totalCount}
/>
</StyledEuiFlyoutFooter>
</EventDetailsWidthProvider>
</ScrollableFlexItem>
</FullWidthFlexGroup>
<UnifiedTimelineBody
header={<></>}
columns={augmentedColumnHeaders}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={queryLoadingState}
totalCount={totalCount}
onChangePage={loadPage}
activeTab={TimelineTabs.pinned}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
trailingControlColumns={rowDetailColumn}
/>
</>
);
};

View file

@ -12,10 +12,7 @@ import { InPortal } from 'react-reverse-portal';
import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import styled from '@emotion/styled';
import { euiThemeVars } from '@kbn/ui-theme';
import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features';
import { useTimelineEventsCountPortal } from '../../../../../../common/hooks/use_timeline_events_count';
import { useTimelineFullScreen } from '../../../../../../common/containers/use_full_screen';
import { ExitFullScreen } from '../../../../../../common/components/exit_full_screen';
import {
type TimelineStatus,
TimelineStatusEnum,
@ -70,11 +67,7 @@ const QueryTabHeaderComponent: React.FC<Props> = ({
showEventsCountBadge,
totalCount,
}) => {
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const { portalNode: timelineEventsCountPortalNode } = useTimelineEventsCountPortal();
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const getIsDataProviderVisible = useMemo(
@ -101,18 +94,6 @@ const QueryTabHeaderComponent: React.FC<Props> = ({
) : null}
</InPortal>
<EuiFlexGroup gutterSize="s" direction="column">
{unifiedComponentsInTimelineDisabled &&
timelineFullScreen &&
setTimelineFullScreen != null && (
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="s">
<ExitFullScreen
fullScreen={timelineFullScreen}
setFullScreen={setTimelineFullScreen}
/>
</EuiFlexGroup>
</EuiFlexItem>
)}
<EuiFlexItem data-test-subj="timeline-date-picker-container">
<TabHeaderContainer data-test-subj="timelineHeader">
<EuiFlexGroup gutterSize="s" direction="column">

View file

@ -27,18 +27,13 @@ import { InputsModelId } from '../../../../../common/store/inputs/constants';
import { useInvalidFilterQuery } from '../../../../../common/hooks/use_invalid_filter_query';
import { timelineActions, timelineSelectors } from '../../../../store';
import type { Direction } from '../../../../../../common/search_strategy';
import type { ControlColumnProps } from '../../../../../../common/types';
import { useTimelineEvents } from '../../../../containers';
import { useKibana } from '../../../../../common/lib/kibana';
import { StatefulBody } from '../../body';
import { Footer, footerHeight } from '../../footer';
import { QueryTabHeader } from './header';
import { calculateTotalPages } from '../../helpers';
import { combineQueries } from '../../../../../common/lib/kuery';
import { TimelineRefetch } from '../../refetch_timeline';
import type { KueryFilterQueryKind } from '../../../../../../common/types/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';
@ -48,17 +43,7 @@ import { isActiveTimeline } from '../../../../../helpers';
import type { TimelineModel } from '../../../../store/model';
import { UnifiedTimelineBody } from '../../body/unified_timeline_body';
import {
FullWidthFlexGroup,
ScrollableFlexItem,
StyledEuiFlyoutBody,
StyledEuiFlyoutFooter,
} from '../shared/layout';
import {
TIMELINE_EMPTY_EVENTS,
isTimerangeSame,
timelineEmptyTrailingControlColumns,
} from '../shared/utils';
import { isTimerangeSame } from '../shared/utils';
import type { TimelineTabCommonProps } from '../shared/types';
import { useTimelineColumns } from '../shared/use_timeline_columns';
import { useTimelineControlColumn } from '../shared/use_timeline_control_columns';
@ -113,10 +98,6 @@ export const QueryTabContentComponent: React.FC<Props> = ({
query: { filterManager: timelineFilterManager },
} = timelineDataService;
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const getManageTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const currentTimeline = useDeepEqualSelector((state) =>
@ -195,7 +176,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
id: timelineId,
indexNames: selectedPatterns,
language: kqlQuery.language,
limit: !unifiedComponentsInTimelineDisabled ? sampleSize : itemsPerPage,
limit: sampleSize,
runtimeMappings: sourcererDataView?.runtimeFieldMap as RunTimeMappings,
skip: !canQueryTimeline,
sort: timelineQuerySortField,
@ -347,53 +328,6 @@ export const QueryTabContentComponent: React.FC<Props> = ({
);
}, [associateNote, closeNotesFlyout, isNotesFlyoutVisible, noteEventId, notes, timelineId]);
if (!unifiedComponentsInTimelineDisabled) {
return (
<>
<TimelineRefetch
id={`${timelineId}-${TimelineTabs.query}`}
inputId={InputsModelId.timeline}
inspect={inspect}
loading={isQueryLoading}
refetch={refetch}
skip={!canQueryTimeline}
/>
{NotesFlyoutMemo}
<UnifiedTimelineBody
header={
<QueryTabHeader
activeTab={activeTab}
filterManager={timelineFilterManager}
show={show && activeTab === TimelineTabs.query}
showCallOutUnauthorizedMsg={showCallOutUnauthorizedMsg}
status={status}
timelineId={timelineId}
showEventsCountBadge={showEventsCountBadge}
totalCount={totalCount}
/>
}
columns={augmentedColumnHeaders}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={isBlankTimeline ? 0 : totalCount}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
onChangePage={loadPage}
activeTab={activeTab}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
/>
</>
);
}
return (
<>
<TimelineRefetch
@ -405,8 +339,9 @@ export const QueryTabContentComponent: React.FC<Props> = ({
skip={!canQueryTimeline}
/>
{NotesFlyoutMemo}
<FullWidthFlexGroup gutterSize="none">
<ScrollableFlexItem grow={2}>
<UnifiedTimelineBody
header={
<QueryTabHeader
activeTab={activeTab}
filterManager={timelineFilterManager}
@ -417,55 +352,24 @@ export const QueryTabContentComponent: React.FC<Props> = ({
showEventsCountBadge={showEventsCountBadge}
totalCount={totalCount}
/>
<EventDetailsWidthProvider>
<StyledEuiFlyoutBody
data-test-subj={`${TimelineTabs.query}-tab-flyout-body`}
className="timeline-flyout-body"
>
<StatefulBody
activePage={pageInfo.activePage}
browserFields={browserFields}
data={isBlankTimeline ? TIMELINE_EMPTY_EVENTS : events}
id={timelineId}
refetch={refetch}
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
sort={sort}
tabType={TimelineTabs.query}
totalPages={calculateTotalPages({
itemsCount: totalCount,
itemsPerPage,
})}
leadingControlColumns={leadingControlColumns as ControlColumnProps[]}
trailingControlColumns={timelineEmptyTrailingControlColumns}
onToggleShowNotes={onToggleShowNotes}
/>
</StyledEuiFlyoutBody>
<StyledEuiFlyoutFooter
data-test-subj={`${TimelineTabs.query}-tab-flyout-footer`}
className="timeline-flyout-footer"
>
{!isBlankTimeline && (
<Footer
activePage={pageInfo?.activePage ?? 0}
data-test-subj="timeline-footer"
updatedAt={refreshedAt}
height={footerHeight}
id={timelineId}
isLive={isLive}
isLoading={isQueryLoading || loadingSourcerer}
itemsCount={isBlankTimeline ? 0 : events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangePage={loadPage}
totalCount={isBlankTimeline ? 0 : totalCount}
/>
)}
</StyledEuiFlyoutFooter>
</EventDetailsWidthProvider>
</ScrollableFlexItem>
</FullWidthFlexGroup>
}
columns={augmentedColumnHeaders}
rowRenderers={rowRenderers}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={isBlankTimeline ? 0 : totalCount}
leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]}
onChangePage={loadPage}
activeTab={activeTab}
updatedAt={refreshedAt}
isTextBasedQuery={false}
pageInfo={pageInfo}
/>
</>
);
};

View file

@ -1,7 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`useTimelineColumns augmentedColumnHeaders should return the default columns 1`] = `Array []`;
exports[`useTimelineColumns augmentedColumnHeaders should return the default unified data table (udt) columns 1`] = `Array []`;
exports[`useTimelineColumns augmentedColumnHeaders should return the provided columns 1`] = `

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`useTimelineColumns leadingControlColumns should return the leading control columns 1`] = `
exports[`useTimelineControlColumns leadingControlColumns should return the leading control columns 1`] = `
Array [
Object {
"headerCellRender": [Function],

View file

@ -6,19 +6,15 @@
*/
import { TestProviders } from '../../../../../common/mock';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { renderHook } from '@testing-library/react-hooks';
import { useTimelineColumns } from './use_timeline_columns';
import { defaultUdtHeaders } from '../../unified_components/default_headers';
import { defaultHeaders } from '../../body/column_headers/default_headers';
import type { ColumnHeaderOptions } from '../../../../../../common/types/timeline/columns';
jest.mock('../../../../../common/hooks/use_experimental_features', () => ({
useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true),
}));
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
describe('useTimelineColumns', () => {
const mockColumns: ColumnHeaderOptions[] = [
{
@ -33,15 +29,7 @@ describe('useTimelineColumns', () => {
},
];
describe('defaultColumns', () => {
it('should return the default columns', () => {
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});
expect(result.current.defaultColumns).toEqual(defaultHeaders);
});
it('should return the default unified data table (udt) columns', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});
@ -50,16 +38,7 @@ describe('useTimelineColumns', () => {
});
describe('localColumns', () => {
it('should return the default columns', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});
expect(result.current.localColumns).toEqual([]);
});
it('should return the default unified data table (udt) columns', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});
@ -75,16 +54,7 @@ describe('useTimelineColumns', () => {
});
describe('augmentedColumnHeaders', () => {
it('should return the default columns', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});
expect(result.current.augmentedColumnHeaders).toMatchSnapshot();
});
it('should return the default unified data table (udt) columns', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
const { result } = renderHook(() => useTimelineColumns([]), {
wrapper: TestProviders,
});

View file

@ -8,8 +8,6 @@
import { useMemo } from 'react';
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
import { useSourcererDataView } from '../../../../../sourcerer/containers';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { defaultHeaders } from '../../body/column_headers/default_headers';
import { requiredFieldsForActions } from '../../../../../detections/components/alerts_table/default_config';
import { defaultUdtHeaders } from '../../unified_components/default_headers';
import type { ColumnHeaderOptions } from '../../../../../../common/types';
@ -18,16 +16,7 @@ import { memoizedGetTimelineColumnHeaders } from './utils';
export const useTimelineColumns = (columns: ColumnHeaderOptions[]) => {
const { browserFields } = useSourcererDataView(SourcererScopeName.timeline);
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const defaultColumns = useMemo(
() => (!unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders),
[unifiedComponentsInTimelineDisabled]
);
const localColumns = useMemo(() => columns ?? defaultColumns, [columns, defaultColumns]);
const localColumns = useMemo(() => columns ?? defaultUdtHeaders, [columns]);
const augmentedColumnHeaders = memoizedGetTimelineColumnHeaders(
localColumns,
@ -43,11 +32,11 @@ export const useTimelineColumns = (columns: ColumnHeaderOptions[]) => {
return useMemo(
() => ({
defaultColumns,
defaultColumns: defaultUdtHeaders,
localColumns,
augmentedColumnHeaders,
timelineQueryFieldsFromColumns,
}),
[augmentedColumnHeaders, defaultColumns, timelineQueryFieldsFromColumns, localColumns]
[augmentedColumnHeaders, timelineQueryFieldsFromColumns, localColumns]
);
};

View file

@ -21,7 +21,7 @@ jest.mock('../../../../../common/hooks/use_license', () => ({
const useLicenseMock = useLicense as jest.Mock;
describe('useTimelineColumns', () => {
describe('useTimelineControlColumns', () => {
const mockColumns: ColumnHeaderOptions[] = [
{
columnHeaderType: 'not-filtered',

View file

@ -13,7 +13,6 @@ import { JEST_ENVIRONMENT } from '../../../../../../common/constants';
import { useLicense } from '../../../../../common/hooks/use_license';
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
import { useSourcererDataView } from '../../../../../sourcerer/containers';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { getDefaultControlColumn } from '../../body/control_columns';
import type { UnifiedActionProps } from '../../unified_components/data_table/control_column_cell_render';
import type { TimelineTabs } from '../../../../../../common/types/timeline';
@ -53,12 +52,8 @@ export const useTimelineControlColumn = ({
}: UseTimelineControlColumnArgs) => {
const { browserFields } = useSourcererDataView(SourcererScopeName.timeline);
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const isEnterprisePlus = useLicense().isEnterprise();
const ACTION_BUTTON_COUNT = isEnterprisePlus ? 6 : 5;
const ACTION_BUTTON_COUNT = useMemo(() => (isEnterprisePlus ? 6 : 5), [isEnterprisePlus]);
const { localColumns } = useTimelineColumns(columns);
const RowCellRender = useMemo(
@ -116,45 +111,36 @@ export const useTimelineControlColumn = ({
// We need one less when the unified components are enabled because the document expand is provided by the unified data table
const UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT = ACTION_BUTTON_COUNT - 1;
return useMemo(() => {
if (!unifiedComponentsInTimelineDisabled) {
return getDefaultControlColumn(UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT).map((x) => ({
...x,
headerCellRender: function HeaderCellRender(props: UnifiedActionProps) {
return (
<HeaderActions
width={x.width}
browserFields={browserFields}
columnHeaders={localColumns}
isEventViewer={false}
isSelectAllChecked={false}
onSelectAll={noSelectAll}
showEventsSelect={false}
showSelectAllCheckbox={false}
showFullScreenToggle={false}
sort={sort}
tabType={activeTab}
{...props}
timelineId={timelineId}
/>
);
},
rowCellRender: JEST_ENVIRONMENT ? RowCellRender : React.memo(RowCellRender),
}));
} else {
return getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
...x,
headerCellRender: HeaderActions,
})) as unknown as ColumnHeaderOptions[];
}
return getDefaultControlColumn(UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT).map((x) => ({
...x,
headerCellRender: function HeaderCellRender(props: UnifiedActionProps) {
return (
<HeaderActions
width={x.width}
browserFields={browserFields}
columnHeaders={localColumns}
isEventViewer={false}
isSelectAllChecked={false}
onSelectAll={noSelectAll}
showEventsSelect={false}
showSelectAllCheckbox={false}
showFullScreenToggle={false}
sort={sort}
tabType={activeTab}
{...props}
timelineId={timelineId}
/>
);
},
rowCellRender: JEST_ENVIRONMENT ? RowCellRender : React.memo(RowCellRender),
}));
}, [
unifiedComponentsInTimelineDisabled,
UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT,
browserFields,
localColumns,
sort,
activeTab,
timelineId,
ACTION_BUTTON_COUNT,
RowCellRender,
]);
};

View file

@ -67,9 +67,6 @@ jest.mock('react-router-dom', () => ({
}));
const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineDisabled') {
return false;
}
return allowedExperimentalValues[feature];
});

View file

@ -8,7 +8,6 @@
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { InputsModelId } from '../../common/store/inputs/constants';
import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers';
import { timelineActions } from '../store';
import { useTimelineFullScreen } from '../../common/containers/use_full_screen';
import { TimelineId } from '../../../common/types/timeline';
@ -20,7 +19,6 @@ import { SourcererScopeName } from '../../sourcerer/store/model';
import { appActions } from '../../common/store/app';
import type { TimeRange } from '../../common/store/inputs/model';
import { useDiscoverInTimelineContext } from '../../common/components/discover_in_timeline/use_discover_in_timeline_context';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
import { defaultUdtHeaders } from '../components/timeline/unified_components/default_headers';
import { timelineDefaults } from '../store/defaults';
@ -50,9 +48,6 @@ export const useCreateTimeline = ({
onClick,
}: UseCreateTimelineParams): ((options?: { timeRange?: TimeRange }) => Promise<void>) => {
const dispatch = useDispatch();
const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled(
'unifiedComponentsInTimelineDisabled'
);
const { id: dataViewId, patternList: selectedPatterns } = useSelector(
sourcererSelectors.defaultDataView
) ?? { id: '', patternList: [] };
@ -87,7 +82,7 @@ export const useCreateTimeline = ({
dispatch(
timelineActions.createTimeline({
columns: !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders,
columns: defaultUdtHeaders,
dataViewId,
id,
indexNames: selectedPatterns,
@ -95,7 +90,7 @@ export const useCreateTimeline = ({
timelineType,
updated: undefined,
excludedRowRendererIds:
!unifiedComponentsInTimelineDisabled && timelineType !== TimelineTypeEnum.template
timelineType !== TimelineTypeEnum.template
? timelineDefaults.excludedRowRendererIds
: [],
})
@ -132,7 +127,6 @@ export const useCreateTimeline = ({
setTimelineFullScreen,
timelineFullScreen,
timelineType,
unifiedComponentsInTimelineDisabled,
]
);

View file

@ -1,108 +0,0 @@
/*
* 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 {
FIELDS_BROWSER_HOST_GEO_CITY_NAME_HEADER,
FIELDS_BROWSER_HEADER_HOST_GEO_CONTINENT_NAME_HEADER,
FIELDS_BROWSER_MESSAGE_HEADER,
FIELDS_BROWSER_FILTER_INPUT,
} from '../../../screens/fields_browser';
import { TIMELINE_FIELDS_BUTTON } from '../../../screens/timeline';
import {
addsHostGeoCityNameToTimeline,
addsHostGeoContinentNameToTimeline,
closeFieldsBrowser,
filterFieldsBrowser,
removesMessageField,
resetFields,
} from '../../../tasks/fields_browser';
import { login } from '../../../tasks/login';
import { visitWithTimeRange } from '../../../tasks/navigation';
import { openTimelineUsingToggle } from '../../../tasks/security_main';
import { openTimelineFieldsBrowser } from '../../../tasks/timeline';
import { hostsUrl } from '../../../urls/navigation';
describe(
'Fields Browser',
{
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
env: {
ftrConfig: {
kbnServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'unifiedComponentsInTimelineDisabled',
])}`,
],
},
},
},
() => {
beforeEach(() => {
login();
visitWithTimeRange(hostsUrl('allHosts'));
openTimelineUsingToggle();
openTimelineFieldsBrowser();
});
describe('Editing the timeline', () => {
it('should add/remove columns from the alerts table when the user checks/un-checks them', () => {
const filterInput = 'host.geo.c';
cy.log('removing the message column');
cy.get(FIELDS_BROWSER_MESSAGE_HEADER).should('exist');
removesMessageField();
closeFieldsBrowser();
cy.get(FIELDS_BROWSER_MESSAGE_HEADER).should('not.exist');
cy.log('add host.geo.city_name column');
cy.get(FIELDS_BROWSER_HOST_GEO_CITY_NAME_HEADER).should('not.exist');
openTimelineFieldsBrowser();
filterFieldsBrowser(filterInput);
addsHostGeoCityNameToTimeline();
closeFieldsBrowser();
cy.get(FIELDS_BROWSER_HOST_GEO_CITY_NAME_HEADER).should('exist');
});
it('should reset all fields in the timeline when `Reset Fields` is clicked', () => {
const filterInput = 'host.geo.c';
filterFieldsBrowser(filterInput);
cy.get(FIELDS_BROWSER_HEADER_HOST_GEO_CONTINENT_NAME_HEADER).should('not.exist');
addsHostGeoContinentNameToTimeline();
closeFieldsBrowser();
cy.get(FIELDS_BROWSER_HEADER_HOST_GEO_CONTINENT_NAME_HEADER).should('exist');
openTimelineFieldsBrowser();
resetFields();
cy.get(FIELDS_BROWSER_HEADER_HOST_GEO_CONTINENT_NAME_HEADER).should('not.exist');
cy.log('restores focus to the Customize Columns button when `Reset Fields` is clicked');
cy.get(TIMELINE_FIELDS_BUTTON).should('have.focus');
cy.log('restores focus to the Customize Columns button when Esc is pressed');
openTimelineFieldsBrowser();
cy.get(FIELDS_BROWSER_FILTER_INPUT).type('{esc}');
cy.get(TIMELINE_FIELDS_BUTTON).should('have.focus');
});
});
}
);

View file

@ -1,83 +0,0 @@
/*
* 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 {
TIMELINE_EVENT,
TIMELINE_EVENTS_COUNT_NEXT_PAGE,
TIMELINE_EVENTS_COUNT_PER_PAGE,
TIMELINE_EVENTS_COUNT_PER_PAGE_BTN,
TIMELINE_EVENTS_COUNT_PER_PAGE_OPTION,
TIMELINE_EVENTS_COUNT_PREV_PAGE,
TIMELINE_FLYOUT,
} from '../../../screens/timeline';
import { login } from '../../../tasks/login';
import { visitWithTimeRange } from '../../../tasks/navigation';
import { openTimelineUsingToggle } from '../../../tasks/security_main';
import { populateTimeline } from '../../../tasks/timeline';
import { hostsUrl } from '../../../urls/navigation';
// Flaky on serverless
const defaultPageSize = 25;
describe(
'Timeline Pagination',
{
/*
* Tests with feature flag should not be enabled on serverless mki
* so skipping it. When you remove the feature flag, remove the
* skipInServerlessMKI tag as well.
* */
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
env: {
ftrConfig: {
kbnServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'unifiedComponentsInTimelineDisabled',
])}`,
],
},
},
},
() => {
beforeEach(() => {
cy.task('esArchiverLoad', { archiveName: 'timeline' });
login();
visitWithTimeRange(hostsUrl('allHosts'));
openTimelineUsingToggle();
populateTimeline();
});
afterEach(() => {
cy.task('esArchiverUnload', { archiveName: 'timeline' });
});
it(`should paginate records correctly`, () => {
// should have ${defaultPageSize} events in the page by default
cy.get(TIMELINE_EVENT).should('have.length', defaultPageSize);
// should be able to go to next / previous page
cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_EVENTS_COUNT_NEXT_PAGE}`).first().click();
cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_EVENTS_COUNT_PREV_PAGE}`).first().click();
// should select ${defaultPageSize} items per page by default
cy.get(TIMELINE_EVENTS_COUNT_PER_PAGE).should('contain.text', defaultPageSize);
// should be able to change items count per page with the dropdown
const itemsPerPage = 100;
cy.get(TIMELINE_EVENTS_COUNT_PER_PAGE_BTN).first().click();
cy.get(TIMELINE_EVENTS_COUNT_PER_PAGE_OPTION(itemsPerPage)).click();
cy.get(TIMELINE_EVENTS_COUNT_PER_PAGE).should('not.have.text', '0');
cy.get(TIMELINE_EVENTS_COUNT_PER_PAGE)
.invoke('text')
.then((events) => {
cy.wrap(parseInt(events, 10)).should('be.gt', defaultPageSize);
});
});
}
);

View file

@ -1,85 +0,0 @@
/*
* 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 { getTimeline } from '../../../objects/timeline';
import {
UNLOCKED_ICON,
PIN_EVENT,
TIMELINE_FILTER,
TIMELINE_QUERY,
NOTE_CARD_CONTENT,
} from '../../../screens/timeline';
import { deleteTimelines } from '../../../tasks/api_calls/timelines';
import { addNoteToTimeline } from '../../../tasks/api_calls/notes';
import { createTimeline } from '../../../tasks/api_calls/timelines';
import { login } from '../../../tasks/login';
import { visit } from '../../../tasks/navigation';
import {
addFilter,
addNoteToFirstRowEvent,
openTimelineById,
pinFirstEvent,
} from '../../../tasks/timeline';
import { TIMELINES_URL } from '../../../urls/navigation';
const mockTimeline = getTimeline();
describe(
'Timeline query tab',
{
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
env: {
ftrConfig: {
kbnServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'unifiedComponentsInTimelineDisabled',
])}`,
],
},
},
},
() => {
beforeEach(() => {
login();
visit(TIMELINES_URL);
deleteTimelines();
createTimeline(mockTimeline)
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
cy.wrap(timelineId).as('timelineId');
addNoteToTimeline(mockTimeline.notes, timelineId);
openTimelineById(timelineId);
pinFirstEvent();
addFilter(mockTimeline.filter);
});
});
it('should display the right query and filters', () => {
cy.get(TIMELINE_QUERY).should('have.text', `${mockTimeline.query}`);
cy.get(TIMELINE_FILTER(mockTimeline.filter)).should('exist');
});
it('should be able to add event note', () => {
const note = 'event note';
addNoteToFirstRowEvent(note);
cy.get(NOTE_CARD_CONTENT).should('contain', 'event note');
});
it('should display pinned events', () => {
cy.get(PIN_EVENT)
.should('have.attr', 'aria-label')
.and('match', /Unpin the event in row 2/);
});
it('should have an unlock icon', { tags: '@skipInServerless' }, () => {
cy.get(UNLOCKED_ICON).should('be.visible');
});
}
);

View file

@ -1,57 +0,0 @@
/*
* 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 { getNewRule } from '../../../objects/rule';
import { deleteAlertsAndRules } from '../../../tasks/api_calls/common';
import { createRule } from '../../../tasks/api_calls/rules';
import { login } from '../../../tasks/login';
import { visitWithTimeRange } from '../../../tasks/navigation';
import { openTimelineUsingToggle } from '../../../tasks/security_main';
import { ALERTS_URL } from '../../../urls/navigation';
import {
createNewTimeline,
executeTimelineKQL,
executeTimelineSearch,
openTimelineEventContextMenu,
} from '../../../tasks/timeline';
import { MARK_ALERT_ACKNOWLEDGED_BTN } from '../../../screens/alerts';
import { GET_TIMELINE_GRID_CELL } from '../../../screens/timeline';
describe(
'Timeline table Row Actions',
{
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
env: {
ftrConfig: {
kbnServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'unifiedComponentsInTimelineDisabled',
])}`,
],
},
},
},
() => {
beforeEach(() => {
deleteAlertsAndRules();
createRule(getNewRule());
login();
visitWithTimeRange(ALERTS_URL);
openTimelineUsingToggle();
createNewTimeline();
executeTimelineSearch('*');
});
it('should refresh the table when alert status is changed', () => {
executeTimelineKQL('kibana.alert.workflow_status:open');
cy.get(GET_TIMELINE_GRID_CELL('@timestamp')).should('have.length', 1);
openTimelineEventContextMenu();
cy.get(MARK_ALERT_ACKNOWLEDGED_BTN).click();
cy.get(GET_TIMELINE_GRID_CELL('@timestamp')).should('have.length', 0);
});
}
);