mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
d51d60e8a9
commit
87a2285fbd
40 changed files with 1481 additions and 6559 deletions
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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 }) =>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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">
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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`] = `
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -67,9 +67,6 @@ jest.mock('react-router-dom', () => ({
|
|||
}));
|
||||
|
||||
const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof ExperimentalFeatures) => {
|
||||
if (feature === 'unifiedComponentsInTimelineDisabled') {
|
||||
return false;
|
||||
}
|
||||
return allowedExperimentalValues[feature];
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue