Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
SecurityCellActionType,
} from '../cell_actions';
import { getSourcererScopeId } from '../../../helpers';
import { TimelineContext } from '../../../timelines/components/timeline';
import { TimelineContext } from '../../../timelines/components/timeline/context';

import { TableContext } from '../events_viewer/shared';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jest.mock('react-redux', () => {
};
});

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => jest.fn(),
}));

const mockRef = {
current: null,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { AddTimelineButton } from './add_timeline_button';
import { timelineActions } from '../../store';
import { TimelineSaveStatus } from '../save_status';
import { AddToFavoritesButton } from '../add_to_favorites';
import { TimelineEventsCountBadge } from '../../../common/hooks/use_timeline_events_count';
import TimelineQueryTabEventsCount from '../timeline/tabs/query/events_count';

interface TimelineBottomBarProps {
/**
Expand Down Expand Up @@ -63,9 +63,9 @@ export const TimelineBottomBar = React.memo<TimelineBottomBarProps>(
{title}
</EuiLink>
</EuiFlexItem>
{!show && ( // this is a hack because TimelineEventsCountBadge is using react-reverse-portal so the component which is used in multiple places cannot be visible in multiple places at the same time
{!show && ( // We only want to show this when the timeline modal is closed
<EuiFlexItem grow={false} data-test-subj="timeline-event-count-badge">
<TimelineEventsCountBadge />
<TimelineQueryTabEventsCount timelineId={timelineId} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, { useContext, useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/css';
import classNames from 'classnames';
import { TimelineContext } from '../../timeline';
import { TimelineContext } from '../../timeline/context';
import { getSourcererScopeId } from '../../../../helpers';
import { escapeDataProviderId } from '../../../../common/components/drag_and_drop/helpers';
import { defaultToEmptyTag } from '../../../../common/components/empty_value';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ import type { UnifiedTimelineBodyProps } from './unified_timeline_body';
import { UnifiedTimelineBody } from './unified_timeline_body';
import { render } from '@testing-library/react';
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { mockSourcererScope } from '../../../../sourcerer/containers/mocks';
import { DataView } from '@kbn/data-views-plugin/common';

jest.mock('../unified_components', () => {
return {
UnifiedTimeline: jest.fn(),
};
});

const mockDataView = new DataView({
spec: mockSourcererScope.sourcererDataView,
fieldFormats: fieldFormatsMock,
});

// Not returning an actual dataView here, just an object as a non-null value;
const mockUseGetScopedSourcererDataView = jest.fn().mockImplementation(() => mockDataView);

jest.mock('../../../../sourcerer/components/use_get_sourcerer_data_view', () => ({
useGetScopedSourcererDataView: () => mockUseGetScopedSourcererDataView(),
}));

const mockEventsData = structuredClone(mockTimelineData);

const defaultProps: UnifiedTimelineBodyProps = {
Expand Down Expand Up @@ -77,4 +92,11 @@ describe('UnifiedTimelineBody', () => {
{}
);
});

it('should render the dataview error component when no dataView is provided', () => {
mockUseGetScopedSourcererDataView.mockImplementationOnce(() => undefined);
const { queryByTestId } = renderTestComponents();

expect(queryByTestId('dataViewErrorComponent')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import type { ComponentProps, ReactElement } from 'react';
import React, { useMemo } from 'react';
import { RootDragDropProvider } from '@kbn/dom-drag-drop';
import { useGetScopedSourcererDataView } from '../../../../sourcerer/components/use_get_sourcerer_data_view';
import { DataViewErrorComponent } from '../../../../common/components/with_data_view/data_view_error';
import { StyledTableFlexGroup, StyledUnifiedTableFlexItem } from '../unified_components/styles';
import { UnifiedTimeline } from '../unified_components';
import { defaultUdtHeaders } from './column_headers/default_headers';
import { SourcererScopeName } from '../../../../sourcerer/store/model';

export interface UnifiedTimelineBodyProps extends ComponentProps<typeof UnifiedTimeline> {
export interface UnifiedTimelineBodyProps
extends Omit<ComponentProps<typeof UnifiedTimeline>, 'dataView'> {
header: ReactElement;
}

Expand All @@ -37,7 +41,9 @@ export const UnifiedTimelineBody = (props: UnifiedTimelineBodyProps) => {
leadingControlColumns,
onUpdatePageIndex,
} = props;

const dataView = useGetScopedSourcererDataView({
sourcererScope: SourcererScopeName.timeline,
});
const columnsHeader = useMemo(() => columns ?? defaultUdtHeaders, [columns]);

return (
Expand All @@ -48,26 +54,31 @@ export const UnifiedTimelineBody = (props: UnifiedTimelineBodyProps) => {
data-test-subj="unifiedTimelineBody"
>
<RootDragDropProvider>
<UnifiedTimeline
columns={columnsHeader}
rowRenderers={rowRenderers}
isSortEnabled={isSortEnabled}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={totalCount}
onFetchMoreRecords={onFetchMoreRecords}
activeTab={activeTab}
updatedAt={updatedAt}
isTextBasedQuery={false}
trailingControlColumns={trailingControlColumns}
leadingControlColumns={leadingControlColumns}
onUpdatePageIndex={onUpdatePageIndex}
/>
{dataView ? (
<UnifiedTimeline
columns={columnsHeader}
dataView={dataView}
rowRenderers={rowRenderers}
isSortEnabled={isSortEnabled}
timelineId={timelineId}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
sort={sort}
events={events}
refetch={refetch}
dataLoadingState={dataLoadingState}
totalCount={totalCount}
onFetchMoreRecords={onFetchMoreRecords}
activeTab={activeTab}
updatedAt={updatedAt}
isTextBasedQuery={false}
trailingControlColumns={trailingControlColumns}
leadingControlColumns={leadingControlColumns}
onUpdatePageIndex={onUpdatePageIndex}
/>
) : (
<DataViewErrorComponent />
)}
</RootDragDropProvider>
</StyledUnifiedTableFlexItem>
</StyledTableFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { createContext } from 'react';

export const TimelineContext = createContext<{
timelineId: string | null;
}>({ timelineId: null });
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { pick } from 'lodash/fp';
import { EuiProgress } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useRef, createContext } from 'react';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';

Expand All @@ -30,6 +30,7 @@ import { EXIT_FULL_SCREEN_CLASS_NAME } from '../../../common/components/exit_ful
import { useResolveConflict } from '../../../common/hooks/use_resolve_conflict';
import { sourcererSelectors } from '../../../common/store';
import { defaultUdtHeaders } from './body/column_headers/default_headers';
import { TimelineContext } from './context';

const TimelineTemplateBadge = styled.div`
background: ${({ theme }) => theme.eui.euiColorVis3_behindText};
Expand All @@ -44,7 +45,6 @@ const TimelineBody = styled.div`
flex-direction: column;
`;

export const TimelineContext = createContext<{ timelineId: string | null }>({ timelineId: null });
export interface Props {
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
rowRenderers: RowRenderer[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,7 @@ import { initializeTimelineSettings } from '../../../store/actions';
import { selectTimelineById, selectTimelineESQLSavedSearchId } from '../../../store/selectors';
import { fetchNotesBySavedObjectIds, selectSortedNotesBySavedObjectId } from '../../../../notes';
import { ENABLE_VISUALIZATIONS_IN_FLYOUT_SETTING } from '../../../../../common/constants';

const HideShowContainer = styled.div.attrs<{ $isVisible: boolean; isOverflowYScroll: boolean }>(
({ $isVisible = false, isOverflowYScroll = false }) => ({
style: {
display: $isVisible ? 'flex' : 'none',
overflow: isOverflowYScroll ? 'hidden scroll' : 'hidden',
},
})
)<{ $isVisible: boolean; isOverflowYScroll?: boolean }>`
flex: 1;
`;
import { LazyTimelineTabRenderer, TimelineTabFallback } from './lazy_timeline_tab_renderer';

/**
* A HOC which supplies React.Suspense with a fallback component
Expand All @@ -75,13 +65,35 @@ const tabWithSuspense = <P extends {}, R = {}>(
return Comp;
};

const QueryTab = tabWithSuspense(lazy(() => import('./query')));
const EqlTab = tabWithSuspense(lazy(() => import('./eql')));
const GraphTab = tabWithSuspense(lazy(() => import('./graph')));
const NotesTab = tabWithSuspense(lazy(() => import('./notes')));
const PinnedTab = tabWithSuspense(lazy(() => import('./pinned')));
const SessionTab = tabWithSuspense(lazy(() => import('./session')));
const EsqlTab = tabWithSuspense(lazy(() => import('./esql')));
const QueryTab = tabWithSuspense(
lazy(() => import('./query')),
<TimelineTabFallback />
);
const EqlTab = tabWithSuspense(
lazy(() => import('./eql')),
<TimelineTabFallback />
);
const GraphTab = tabWithSuspense(
lazy(() => import('./graph')),
<TimelineTabFallback />
);
const NotesTab = tabWithSuspense(
lazy(() => import('./notes')),
<TimelineTabFallback />
);
const PinnedTab = tabWithSuspense(
lazy(() => import('./pinned')),
<TimelineTabFallback />
);
const SessionTab = tabWithSuspense(
lazy(() => import('./session')),
<TimelineTabFallback />
);
const EsqlTab = tabWithSuspense(
lazy(() => import('./esql')),
<TimelineTabFallback />
);

interface BasicTimelineTab {
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
rowRenderers: RowRenderer[];
Expand Down Expand Up @@ -143,60 +155,60 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
[activeTimelineTab]
);

/* Future developer -> why are we doing that
* It is really expansive to re-render the QueryTab because the drag/drop
* Therefore, we are only hiding its dom when switching to another tab
* to avoid mounting/un-mounting === re-render
*/
return (
<>
<HideShowContainer
$isVisible={TimelineTabs.query === activeTimelineTab}
data-test-subj={`timeline-tab-content-${TimelineTabs.query}`}
<LazyTimelineTabRenderer
timelineId={timelineId}
shouldShowTab={TimelineTabs.query === activeTimelineTab}
dataTestSubj={`timeline-tab-content-${TimelineTabs.query}`}
>
<QueryTab
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
timelineId={timelineId}
/>
</HideShowContainer>
</LazyTimelineTabRenderer>
{showTimeline && shouldShowESQLTab && activeTimelineTab === TimelineTabs.esql && (
<HideShowContainer
$isVisible={true}
data-test-subj={`timeline-tab-content-${TimelineTabs.esql}`}
<LazyTimelineTabRenderer
timelineId={timelineId}
shouldShowTab={true}
dataTestSubj={`timeline-tab-content-${TimelineTabs.esql}`}
>
<EsqlTab timelineId={timelineId} />
</HideShowContainer>
</LazyTimelineTabRenderer>
)}
<HideShowContainer
$isVisible={TimelineTabs.pinned === activeTimelineTab}
data-test-subj={`timeline-tab-content-${TimelineTabs.pinned}`}
<LazyTimelineTabRenderer
timelineId={timelineId}
shouldShowTab={TimelineTabs.pinned === activeTimelineTab}
dataTestSubj={`timeline-tab-content-${TimelineTabs.pinned}`}
>
<PinnedTab
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
timelineId={timelineId}
/>
</HideShowContainer>
</LazyTimelineTabRenderer>
{timelineType === TimelineTypeEnum.default && (
<HideShowContainer
$isVisible={TimelineTabs.eql === activeTimelineTab}
data-test-subj={`timeline-tab-content-${TimelineTabs.eql}`}
<LazyTimelineTabRenderer
timelineId={timelineId}
shouldShowTab={TimelineTabs.eql === activeTimelineTab}
dataTestSubj={`timeline-tab-content-${TimelineTabs.eql}`}
>
<EqlTab
renderCellValue={renderCellValue}
rowRenderers={rowRenderers}
timelineId={timelineId}
/>
</HideShowContainer>
</LazyTimelineTabRenderer>
)}
<HideShowContainer
$isVisible={isGraphOrNotesTabs}
<LazyTimelineTabRenderer
timelineId={timelineId}
shouldShowTab={isGraphOrNotesTabs}
isOverflowYScroll={activeTimelineTab === TimelineTabs.session}
data-test-subj={`timeline-tab-content-${TimelineTabs.graph}-${TimelineTabs.notes}`}
dataTestSubj={`timeline-tab-content-${TimelineTabs.graph}-${TimelineTabs.notes}`}
>
{isGraphOrNotesTabs && getTab(activeTimelineTab)}
</HideShowContainer>
{isGraphOrNotesTabs ? getTab(activeTimelineTab) : null}
</LazyTimelineTabRenderer>
</>
);
}
Expand Down
Loading