[SIEM] Improves the timeline performance by optimizing the width, removing wasted renderers, and adding a visibility sensor (#43560)
## Summary This improves the timeline performance by: * Optimizing the widths using ContextProvider when the AutoSizer is moved. * Optimizing the renderers found within the Stats component by using React.Memo * Fixes a small mistake with a ContextProvider which would make the `Header.Div's` re-render on redraws of the width * Changed the width to use inline in areas as that is recommended by `StyledComponents` as a performance improvement when you have a fast amount of CSS changes dynamically. * Adds a visibility sensor to so we can perform "windowing" and remove heavy DOM elements from the timeline Windowing over 300 items will show "grey" placeholders now until they load ![windowing-up-and-down](https://user-images.githubusercontent.com/1151048/63405479-31db0180-c3a4-11e9-8444-7472e6265b8e.gif) Another shot of the windowing: ![place-holders-300](https://user-images.githubusercontent.com/1151048/63405598-9b5b1000-c3a4-11e9-927a-4d3e761df0d7.gif) ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. - [x] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [x] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
This commit is contained in:
parent
daa86da4cf
commit
a69a3f7dd2
|
@ -65,7 +65,7 @@ interface ToolTipProps {
|
|||
}
|
||||
|
||||
const ToolTip = React.memo<ToolTipProps>(({ categoryId, browserFields, onUpdateColumns }) => {
|
||||
const { isLoading } = useContext(TimelineContext);
|
||||
const isLoading = useContext(TimelineContext);
|
||||
return (
|
||||
<EuiToolTip content={i18n.VIEW_CATEGORY(categoryId)}>
|
||||
{!isLoading ? (
|
||||
|
|
|
@ -71,7 +71,7 @@ interface ToolTipProps {
|
|||
}
|
||||
|
||||
const ToolTip = React.memo<ToolTipProps>(({ categoryId, onUpdateColumns, categoryColumns }) => {
|
||||
const { isLoading } = useContext(TimelineContext);
|
||||
const isLoading = useContext(TimelineContext);
|
||||
return (
|
||||
<EuiToolTip content={i18n.VIEW_CATEGORY(categoryId)}>
|
||||
{!isLoading ? (
|
||||
|
|
|
@ -70,7 +70,7 @@ interface DispatchProps {
|
|||
|
||||
type Props = OwnProps & StateReduxProps & DispatchProps;
|
||||
|
||||
const statefulFlyoutHeader = React.memo<Props>(
|
||||
const StatefulFlyoutHeader = React.memo<Props>(
|
||||
({
|
||||
associateNote,
|
||||
createTimeline,
|
||||
|
@ -112,7 +112,7 @@ const statefulFlyoutHeader = React.memo<Props>(
|
|||
)
|
||||
);
|
||||
|
||||
statefulFlyoutHeader.displayName = 'statefulFlyoutHeader';
|
||||
StatefulFlyoutHeader.displayName = 'StatefulFlyoutHeader';
|
||||
|
||||
const emptyHistory: History[] = []; // stable reference
|
||||
|
||||
|
@ -212,4 +212,4 @@ const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({
|
|||
export const FlyoutHeader = connect(
|
||||
makeMapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(statefulFlyoutHeader);
|
||||
)(StatefulFlyoutHeader);
|
||||
|
|
|
@ -45,7 +45,6 @@ describe('NoteCards', () => {
|
|||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -62,7 +61,6 @@ describe('NoteCards', () => {
|
|||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -79,7 +77,6 @@ describe('NoteCards', () => {
|
|||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -103,7 +100,6 @@ describe('NoteCards', () => {
|
|||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -120,7 +116,6 @@ describe('NoteCards', () => {
|
|||
showAddNote={false}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@ import { EuiFlexGroup, EuiPanel } from '@elastic/eui';
|
|||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { Note } from '../../../lib/note';
|
||||
import { AddNote } from '../add_note';
|
||||
import { AssociateNote, GetNewNoteId, UpdateNote } from '../helpers';
|
||||
import { NoteCard } from '../note_card';
|
||||
import { TimelineWidthContext } from '../../timeline/timeline_context';
|
||||
|
||||
const AddNoteContainer = styled.div``;
|
||||
|
||||
|
@ -23,12 +25,29 @@ const NoteContainer = styled.div`
|
|||
|
||||
NoteContainer.displayName = 'NoteContainer';
|
||||
|
||||
const NoteCardsContainer = styled(EuiPanel)<{ width?: string }>`
|
||||
border: none;
|
||||
width: ${({ width = '100%' }) => width};
|
||||
`;
|
||||
interface NoteCardsCompProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
NoteCardsContainer.displayName = 'NoteCardsContainer';
|
||||
const NoteCardsComp = React.memo<NoteCardsCompProps>(({ children }) => {
|
||||
const width = useContext(TimelineWidthContext);
|
||||
|
||||
// Passing the styles directly to the component because the width is
|
||||
// being calculated and is recommended by Styled Components for performance
|
||||
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
return (
|
||||
<EuiPanel
|
||||
data-test-subj="note-cards"
|
||||
hasShadow={false}
|
||||
paddingSize="none"
|
||||
style={{ width: `${width - 10}px`, border: 'none' }}
|
||||
>
|
||||
{children}
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
||||
NoteCardsComp.displayName = 'NoteCardsComp';
|
||||
|
||||
const NotesContainer = styled(EuiFlexGroup)`
|
||||
padding: 0 5px;
|
||||
|
@ -45,7 +64,6 @@ interface Props {
|
|||
showAddNote: boolean;
|
||||
toggleShowAddNote: () => void;
|
||||
updateNote: UpdateNote;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -68,16 +86,10 @@ export class NoteCards extends React.PureComponent<Props, State> {
|
|||
showAddNote,
|
||||
toggleShowAddNote,
|
||||
updateNote,
|
||||
width,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<NoteCardsContainer
|
||||
data-test-subj="note-cards"
|
||||
hasShadow={false}
|
||||
paddingSize="none"
|
||||
width={width}
|
||||
>
|
||||
<NoteCardsComp>
|
||||
{noteIds.length ? (
|
||||
<NotesContainer data-test-subj="notes" direction="column" gutterSize="none">
|
||||
{getNotesByIds(noteIds).map(note => (
|
||||
|
@ -100,7 +112,7 @@ export class NoteCards extends React.PureComponent<Props, State> {
|
|||
/>
|
||||
</AddNoteContainer>
|
||||
) : null}
|
||||
</NoteCardsContainer>
|
||||
</NoteCardsComp>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,35 +41,30 @@ const FlexGroupSpinner = styled(EuiFlexGroup)`
|
|||
|
||||
FlexGroupSpinner.displayName = 'FlexGroupSpinner';
|
||||
|
||||
export const KpiHostsComponent = ({
|
||||
data,
|
||||
from,
|
||||
loading,
|
||||
id,
|
||||
to,
|
||||
narrowDateRange,
|
||||
}: KpiHostsProps | KpiHostDetailsProps) => {
|
||||
const mappings =
|
||||
(data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping;
|
||||
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
|
||||
mappings,
|
||||
data,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
narrowDateRange
|
||||
);
|
||||
return loading ? (
|
||||
<FlexGroupSpinner justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
</FlexGroupSpinner>
|
||||
) : (
|
||||
<EuiFlexGroup>
|
||||
{statItemsProps.map((mappedStatItemProps, idx) => {
|
||||
return <StatItemsComponent {...mappedStatItemProps} />;
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
export const KpiHostsComponent = React.memo<KpiHostsProps | KpiHostDetailsProps>(
|
||||
({ data, from, loading, id, to, narrowDateRange }) => {
|
||||
const mappings =
|
||||
(data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping;
|
||||
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
|
||||
mappings,
|
||||
data,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
narrowDateRange
|
||||
);
|
||||
return loading ? (
|
||||
<FlexGroupSpinner justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
</FlexGroupSpinner>
|
||||
) : (
|
||||
<EuiFlexGroup>
|
||||
{statItemsProps.map((mappedStatItemProps, idx) => {
|
||||
return <StatItemsComponent {...mappedStatItemProps} />;
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -122,21 +122,14 @@ const FlexGroup = styled(EuiFlexGroup)`
|
|||
|
||||
FlexGroup.displayName = 'FlexGroup';
|
||||
|
||||
export const KpiNetworkBaseComponent = ({
|
||||
fieldsMapping,
|
||||
data,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
narrowDateRange,
|
||||
}: {
|
||||
export const KpiNetworkBaseComponent = React.memo<{
|
||||
fieldsMapping: Readonly<StatItems[]>;
|
||||
data: KpiNetworkData;
|
||||
id: string;
|
||||
from: number;
|
||||
to: number;
|
||||
narrowDateRange: UpdateDateRange;
|
||||
}) => {
|
||||
}>(({ fieldsMapping, data, id, from, to, narrowDateRange }) => {
|
||||
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
|
||||
fieldsMapping,
|
||||
data,
|
||||
|
@ -153,7 +146,7 @@ export const KpiNetworkBaseComponent = ({
|
|||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
|
||||
({ data, from, id, loading, to, narrowDateRange }) => {
|
||||
|
|
|
@ -67,7 +67,7 @@ export const CloseButton = pure<{
|
|||
CloseButton.displayName = 'CloseButton';
|
||||
|
||||
export const Actions = React.memo<Props>(({ header, onColumnRemoved, show, sort }) => {
|
||||
const { isLoading } = useContext(TimelineContext);
|
||||
const isLoading = useContext(TimelineContext);
|
||||
return (
|
||||
<ActionsContainer
|
||||
alignItems="center"
|
||||
|
|
|
@ -64,7 +64,7 @@ interface HeaderCompProps {
|
|||
}
|
||||
|
||||
const HeaderComp = React.memo<HeaderCompProps>(({ children, onClick, isResizing }) => {
|
||||
const { isLoading } = useContext(TimelineContext);
|
||||
const isLoading = useContext(TimelineContext);
|
||||
return (
|
||||
<HeaderDiv
|
||||
data-test-subj="header"
|
||||
|
|
|
@ -49,7 +49,6 @@ interface Props {
|
|||
rowRenderers: RowRenderer[];
|
||||
toggleColumn: (column: ColumnHeader) => void;
|
||||
updateNote: UpdateNote;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export const Events = React.memo<Props>(
|
||||
|
@ -72,7 +71,6 @@ export const Events = React.memo<Props>(
|
|||
rowRenderers,
|
||||
toggleColumn,
|
||||
updateNote,
|
||||
width,
|
||||
}) => (
|
||||
<EventsContainer data-test-subj="events" minWidth={minWidth}>
|
||||
<EuiFlexGroup data-test-subj="events-flex-group" direction="column" gutterSize="none">
|
||||
|
@ -96,7 +94,6 @@ export const Events = React.memo<Props>(
|
|||
timelineId={id}
|
||||
toggleColumn={toggleColumn}
|
||||
updateNote={updateNote}
|
||||
width={width}
|
||||
maxDelay={maxDelay(i)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -8,6 +8,8 @@ import { EuiFlexItem } from '@elastic/eui';
|
|||
import * as React from 'react';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
import styled from 'styled-components';
|
||||
import { BrowserFields } from '../../../../containers/source';
|
||||
import { TimelineDetailsComponentQuery } from '../../../../containers/timeline/details';
|
||||
import { TimelineItem, DetailItem } from '../../../../graphql/types';
|
||||
|
@ -41,7 +43,6 @@ interface Props {
|
|||
timelineId: string;
|
||||
toggleColumn: (column: ColumnHeader) => void;
|
||||
updateNote: UpdateNote;
|
||||
width: number;
|
||||
maxDelay?: number;
|
||||
}
|
||||
|
||||
|
@ -55,6 +56,54 @@ export const getNewNoteId = (): string => uuid.v4();
|
|||
|
||||
const emptyDetails: DetailItem[] = [];
|
||||
|
||||
/**
|
||||
* This is the default row height whenever it is a plain row renderer and not a custom row height.
|
||||
* We use this value when we do not know the height of a particular row.
|
||||
*/
|
||||
const DEFAULT_ROW_HEIGHT = '27px';
|
||||
|
||||
/**
|
||||
* This is the default margin size from the EmptyRow below and is the top and bottom
|
||||
* margin added together.
|
||||
* If you change margin: 5px 0 5px 0; within EmptyRow styled component, then please
|
||||
* update this value
|
||||
*/
|
||||
const EMPTY_ROW_MARGIN_TOP_BOTTOM = 10;
|
||||
|
||||
/**
|
||||
* This is the top offset in pixels of the top part of the timeline. The UI area where you do your
|
||||
* drag and drop and filtering. It is a positive number in pixels of _PART_ of the header but not
|
||||
* the entire header. We leave room for some rows to render behind the drag and drop so they might be
|
||||
* visible by the time the user scrolls upwards. All other DOM elements are replaced with their "blank"
|
||||
* rows.
|
||||
*/
|
||||
const TOP_OFFSET = 50;
|
||||
|
||||
/**
|
||||
* This is the bottom offset in pixels of the bottom part of the timeline. The UI area right below the
|
||||
* timeline which is the footer. Since the footer is so incredibly small we don't have enough room to
|
||||
* render around 5 rows below the timeline to get the user the best chance of always scrolling without seeing
|
||||
* "blank rows". The negative number is to give the bottom of the browser window a bit of invisible space to
|
||||
* keep around 5 rows rendering below it. All other DOM elements are replaced with their "blank"
|
||||
* rows.
|
||||
*/
|
||||
const BOTTOM_OFFSET = -500;
|
||||
|
||||
/**
|
||||
* This is missing the height props intentionally for performance reasons that
|
||||
* you can see here:
|
||||
* https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
*
|
||||
* We inline the height style for performance reasons directly on the component.
|
||||
*/
|
||||
export const EmptyRow = styled.div`
|
||||
background-color: ${props => props.theme.eui.euiColorLightestShade};
|
||||
width: 100%;
|
||||
border: ${props => `1px solid ${props.theme.eui.euiColorLightShade}`};
|
||||
border-radius: 5px;
|
||||
margin: 5px 0 5px 0;
|
||||
`;
|
||||
|
||||
export class StatefulEvent extends React.Component<Props, State> {
|
||||
public readonly state: State = {
|
||||
expanded: {},
|
||||
|
@ -62,6 +111,8 @@ export class StatefulEvent extends React.Component<Props, State> {
|
|||
initialRender: false,
|
||||
};
|
||||
|
||||
public divElement: HTMLDivElement | null = null;
|
||||
|
||||
/**
|
||||
* Incrementally loads the events when it mounts by trying to
|
||||
* see if it resides within a window frame and if it is it will
|
||||
|
@ -98,69 +149,99 @@ export class StatefulEvent extends React.Component<Props, State> {
|
|||
timelineId,
|
||||
toggleColumn,
|
||||
updateNote,
|
||||
width,
|
||||
} = this.props;
|
||||
|
||||
// If we are not ready to render yet, just return null
|
||||
// see componentDidMount() for when it schedules the first
|
||||
// time this stateful component should be rendered.
|
||||
if (!this.state.initialRender) {
|
||||
return null;
|
||||
// height is being inlined directly in here because of performance with StyledComponents
|
||||
// involving quick and constant changes to the DOM.
|
||||
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
return <EmptyRow style={{ height: DEFAULT_ROW_HEIGHT }}></EmptyRow>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TimelineDetailsComponentQuery
|
||||
sourceId="default"
|
||||
indexName={event._index!}
|
||||
eventId={event._id}
|
||||
executeQuery={!!this.state.expanded[event._id]}
|
||||
<VisibilitySensor
|
||||
partialVisibility={true}
|
||||
scrollCheck={true}
|
||||
offset={{ top: TOP_OFFSET, bottom: BOTTOM_OFFSET }}
|
||||
>
|
||||
{({ detailsData, loading }) => (
|
||||
<div data-test-subj="event">
|
||||
{getRowRenderer(event.ecs, rowRenderers).renderRow({
|
||||
browserFields,
|
||||
data: event.ecs,
|
||||
width,
|
||||
children: (
|
||||
<StatefulEventChild
|
||||
id={event._id}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
associateNote={this.associateNote}
|
||||
addNoteToEvent={addNoteToEvent}
|
||||
onPinEvent={onPinEvent}
|
||||
columnHeaders={columnHeaders}
|
||||
columnRenderers={columnRenderers}
|
||||
expanded={!!this.state.expanded[event._id]}
|
||||
data={event.data}
|
||||
eventIdToNoteIds={eventIdToNoteIds}
|
||||
getNotesByIds={getNotesByIds}
|
||||
loading={loading}
|
||||
onColumnResized={onColumnResized}
|
||||
onToggleExpanded={this.onToggleExpanded}
|
||||
onUnPinEvent={onUnPinEvent}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
showNotes={!!this.state.showNotes[event._id]}
|
||||
onToggleShowNotes={this.onToggleShowNotes}
|
||||
updateNote={updateNote}
|
||||
width={width}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
<EuiFlexItem data-test-subj="event-details" grow={true}>
|
||||
<ExpandableEvent
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
id={event._id}
|
||||
event={detailsData || emptyDetails}
|
||||
forceExpand={!!this.state.expanded[event._id] && !loading}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
width={width}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
)}
|
||||
</TimelineDetailsComponentQuery>
|
||||
{({ isVisible }) => {
|
||||
if (isVisible) {
|
||||
return (
|
||||
<TimelineDetailsComponentQuery
|
||||
sourceId="default"
|
||||
indexName={event._index!}
|
||||
eventId={event._id}
|
||||
executeQuery={!!this.state.expanded[event._id]}
|
||||
>
|
||||
{({ detailsData, loading }) => (
|
||||
<div
|
||||
data-test-subj="event"
|
||||
ref={divElement => {
|
||||
if (divElement != null) {
|
||||
this.divElement = divElement;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{getRowRenderer(event.ecs, rowRenderers).renderRow({
|
||||
browserFields,
|
||||
data: event.ecs,
|
||||
children: (
|
||||
<StatefulEventChild
|
||||
id={event._id}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
associateNote={this.associateNote}
|
||||
addNoteToEvent={addNoteToEvent}
|
||||
onPinEvent={onPinEvent}
|
||||
columnHeaders={columnHeaders}
|
||||
columnRenderers={columnRenderers}
|
||||
expanded={!!this.state.expanded[event._id]}
|
||||
data={event.data}
|
||||
eventIdToNoteIds={eventIdToNoteIds}
|
||||
getNotesByIds={getNotesByIds}
|
||||
loading={loading}
|
||||
onColumnResized={onColumnResized}
|
||||
onToggleExpanded={this.onToggleExpanded}
|
||||
onUnPinEvent={onUnPinEvent}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
showNotes={!!this.state.showNotes[event._id]}
|
||||
onToggleShowNotes={this.onToggleShowNotes}
|
||||
updateNote={updateNote}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
<EuiFlexItem data-test-subj="event-details" grow={true}>
|
||||
<ExpandableEvent
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
id={event._id}
|
||||
event={detailsData || emptyDetails}
|
||||
forceExpand={!!this.state.expanded[event._id] && !loading}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
)}
|
||||
</TimelineDetailsComponentQuery>
|
||||
);
|
||||
} else {
|
||||
// Height place holder for visibility detection as well as re-rendering sections.
|
||||
const height =
|
||||
this.divElement != null
|
||||
? `${this.divElement.clientHeight - EMPTY_ROW_MARGIN_TOP_BOTTOM}px`
|
||||
: DEFAULT_ROW_HEIGHT;
|
||||
|
||||
// height is being inlined directly in here because of performance with StyledComponents
|
||||
// involving quick and constant changes to the DOM.
|
||||
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
return <EmptyRow style={{ height }}></EmptyRow>;
|
||||
}
|
||||
}}
|
||||
</VisibilitySensor>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ interface Props {
|
|||
onToggleExpanded: (eventId: string) => () => void;
|
||||
onToggleShowNotes: (eventId: string) => () => void;
|
||||
getNotesByIds: (noteIds: string[]) => Note[];
|
||||
width: number;
|
||||
associateNote: (
|
||||
eventId: string,
|
||||
addNoteToEvent: AddNoteToEvent,
|
||||
|
@ -68,7 +67,6 @@ export const StatefulEventChild = React.memo<Props>(
|
|||
showNotes,
|
||||
onToggleShowNotes,
|
||||
updateNote,
|
||||
width,
|
||||
}) => (
|
||||
<EuiFlexGroup data-test-subj="event-rows" direction="column" gutterSize="none">
|
||||
<EuiFlexItem data-test-subj="event-column-data" grow={false}>
|
||||
|
@ -104,7 +102,6 @@ export const StatefulEventChild = React.memo<Props>(
|
|||
showAddNote={showNotes}
|
||||
toggleShowAddNote={onToggleShowNotes(id)}
|
||||
updateNote={updateNote}
|
||||
width={`${width - 10}px`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -27,6 +27,12 @@ const mockSort: Sort = {
|
|||
sortDirection: Direction.desc,
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'react-visibility-sensor',
|
||||
() => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) =>
|
||||
children({ isVisible: true })
|
||||
);
|
||||
|
||||
describe('Body', () => {
|
||||
describe('rendering', () => {
|
||||
test('it renders the column headers', () => {
|
||||
|
@ -55,7 +61,6 @@ describe('Body', () => {
|
|||
sort={mockSort}
|
||||
toggleColumn={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width={100}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -94,7 +99,6 @@ describe('Body', () => {
|
|||
sort={mockSort}
|
||||
toggleColumn={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width={100}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -133,7 +137,6 @@ describe('Body', () => {
|
|||
sort={mockSort}
|
||||
toggleColumn={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width={100}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -174,13 +177,13 @@ describe('Body', () => {
|
|||
sort={mockSort}
|
||||
toggleColumn={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
width={100}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper.update();
|
||||
await wait();
|
||||
wrapper.update();
|
||||
headersJustTimestamp.forEach(h => {
|
||||
headersJustTimestamp.forEach(() => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="data-driven-columns"]')
|
||||
|
|
|
@ -54,7 +54,6 @@ interface Props {
|
|||
sort: Sort;
|
||||
toggleColumn: (column: ColumnHeader) => void;
|
||||
updateNote: UpdateNote;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const HorizontalScroll = styled.div<{
|
||||
|
@ -106,7 +105,6 @@ export const Body = React.memo<Props>(
|
|||
sort,
|
||||
toggleColumn,
|
||||
updateNote,
|
||||
width,
|
||||
}) => {
|
||||
const columnWidths = columnHeaders.reduce(
|
||||
(totalWidth, header) => totalWidth + header.width,
|
||||
|
@ -156,7 +154,6 @@ export const Body = React.memo<Props>(
|
|||
toggleColumn={toggleColumn}
|
||||
updateNote={updateNote}
|
||||
minWidth={columnWidths}
|
||||
width={width}
|
||||
/>
|
||||
</VerticalScrollContainer>
|
||||
</EuiText>
|
||||
|
|
|
@ -6,9 +6,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericDetails
|
||||
browserFields={Object {}}
|
||||
contextId="connected-to"
|
||||
|
@ -132,9 +130,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericFileDetails
|
||||
browserFields={Object {}}
|
||||
contextId="opened-file"
|
||||
|
|
|
@ -38,7 +38,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: auditd,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -68,7 +67,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonAuditd,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -83,7 +81,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: auditd,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -117,7 +114,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: auditdFile,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -147,7 +143,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonAuditd,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -162,7 +157,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: auditdFile,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -31,10 +31,10 @@ export const createGenericAuditRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => (
|
||||
renderRow: ({ browserFields, data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
|
@ -65,10 +65,10 @@ export const createGenericFileRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => (
|
||||
renderRow: ({ browserFields, data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericFileDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
|
|
|
@ -31,7 +31,6 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
width: 100,
|
||||
children: <span>{'some child'}</span>,
|
||||
});
|
||||
|
||||
|
@ -44,7 +43,6 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
width: 100,
|
||||
children: <span>{'some child'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -60,7 +58,6 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: suricata,
|
||||
width: 100,
|
||||
children: <span>{'some child '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -6,9 +6,7 @@ exports[`netflowRowRenderer renders correctly against snapshot 1`] = `
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={500}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<Details>
|
||||
<Netflow
|
||||
contextId="network_flow"
|
||||
|
|
|
@ -32,7 +32,6 @@ describe('netflowRowRenderer', () => {
|
|||
const children = netflowRowRenderer.renderRow({
|
||||
browserFields,
|
||||
data: getMockNetflowData(),
|
||||
width: 500,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -102,7 +101,6 @@ describe('netflowRowRenderer', () => {
|
|||
const children = netflowRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: justIdAndTimestamp,
|
||||
width: 500,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -117,7 +115,6 @@ describe('netflowRowRenderer', () => {
|
|||
const children = netflowRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: getMockNetflowData(),
|
||||
width: 500,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -84,10 +84,10 @@ export const netflowRowRenderer: RowRenderer = {
|
|||
eventActionMatches(get(EVENT_ACTION_FIELD, ecs))
|
||||
);
|
||||
},
|
||||
renderRow: ({ data, width, children }) => (
|
||||
renderRow: ({ data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<Details>
|
||||
<Netflow
|
||||
contextId={NETWORK_FLOW}
|
||||
|
|
|
@ -26,7 +26,6 @@ describe('plain_row_renderer', () => {
|
|||
const children = plainRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: mockDatum,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = shallow(<span>{children}</span>);
|
||||
|
@ -41,7 +40,6 @@ describe('plain_row_renderer', () => {
|
|||
const children = plainRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: mockDatum,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -4,14 +4,23 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { BrowserFields } from '../../../../containers/source';
|
||||
import { Ecs } from '../../../../graphql/types';
|
||||
import { TimelineWidthContext } from '../../timeline_context';
|
||||
|
||||
export const RowRendererContainer = styled.div<{ width: number }>`
|
||||
width: ${({ width }) => `${width}px`};
|
||||
`;
|
||||
interface RowRendererContainerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const RowRendererContainer = React.memo<RowRendererContainerProps>(({ children }) => {
|
||||
const width = useContext(TimelineWidthContext);
|
||||
|
||||
// Passing the styles directly to the component because the width is
|
||||
// being calculated and is recommended by Styled Components for performance
|
||||
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
return <div style={{ width: `${width}px` }}>{children}</div>;
|
||||
});
|
||||
|
||||
RowRendererContainer.displayName = 'RowRendererContainer';
|
||||
|
||||
|
@ -20,12 +29,10 @@ export interface RowRenderer {
|
|||
renderRow: ({
|
||||
browserFields,
|
||||
data,
|
||||
width,
|
||||
children,
|
||||
}: {
|
||||
browserFields: BrowserFields;
|
||||
data: Ecs;
|
||||
width: number;
|
||||
children: React.ReactNode;
|
||||
}) => React.ReactNode;
|
||||
}
|
|
@ -6,9 +6,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = `
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<SuricataDetails
|
||||
browserFields={
|
||||
Object {
|
||||
|
|
|
@ -28,7 +28,6 @@ describe('suricata_row_renderer', () => {
|
|||
const children = suricataRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -48,7 +47,6 @@ describe('suricata_row_renderer', () => {
|
|||
const children = suricataRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -63,7 +61,6 @@ describe('suricata_row_renderer', () => {
|
|||
const children = suricataRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: suricata,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -81,7 +78,6 @@ describe('suricata_row_renderer', () => {
|
|||
const children = suricataRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: suricata,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -16,11 +16,11 @@ export const suricataRowRenderer: RowRenderer = {
|
|||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
return module != null && module.toLowerCase() === 'suricata';
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => {
|
||||
renderRow: ({ browserFields, data, children }) => {
|
||||
return (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<SuricataDetails data={data} browserFields={browserFields} />
|
||||
</RowRendererContainer>
|
||||
</Row>
|
||||
|
|
|
@ -6,9 +6,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<SystemGenericFileDetails
|
||||
browserFields={Object {}}
|
||||
contextId="user_login"
|
||||
|
@ -109,9 +107,7 @@ exports[`GenericRowRenderer #createGenericSystemRowRenderer renders correctly ag
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<SystemGenericDetails
|
||||
browserFields={Object {}}
|
||||
contextId="process_started"
|
||||
|
|
|
@ -38,7 +38,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: system,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -68,7 +67,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: system,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -102,7 +100,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: systemFile,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -131,7 +128,6 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: systemFile,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -30,10 +30,10 @@ export const createGenericSystemRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => (
|
||||
renderRow: ({ browserFields, data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<SystemGenericDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
|
@ -62,10 +62,10 @@ export const createGenericFileRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => (
|
||||
renderRow: ({ browserFields, data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<SystemGenericFileDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
|
|
|
@ -6,9 +6,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = `
|
|||
<span>
|
||||
some children
|
||||
</span>
|
||||
<RowRendererContainer
|
||||
width={100}
|
||||
>
|
||||
<RowRendererContainer>
|
||||
<ZeekDetails
|
||||
browserFields={
|
||||
Object {
|
||||
|
|
|
@ -27,7 +27,6 @@ describe('zeek_row_renderer', () => {
|
|||
const children = zeekRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonZeek,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
|
||||
|
@ -47,7 +46,6 @@ describe('zeek_row_renderer', () => {
|
|||
const children = zeekRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonZeek,
|
||||
width: 100,
|
||||
children: <span>{'some children'}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -62,7 +60,6 @@ describe('zeek_row_renderer', () => {
|
|||
const children = zeekRowRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: zeek,
|
||||
width: 100,
|
||||
children: <span>{'some children '}</span>,
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -16,10 +16,10 @@ export const zeekRowRenderer: RowRenderer = {
|
|||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
return module != null && module.toLowerCase() === 'zeek';
|
||||
},
|
||||
renderRow: ({ browserFields, data, width, children }) => (
|
||||
renderRow: ({ browserFields, data, children }) => (
|
||||
<Row>
|
||||
{children}
|
||||
<RowRendererContainer width={width}>
|
||||
<RowRendererContainer>
|
||||
<ZeekDetails data={data} browserFields={browserFields} />
|
||||
</RowRendererContainer>
|
||||
</Row>
|
||||
|
|
|
@ -39,14 +39,13 @@ interface OwnProps {
|
|||
height: number;
|
||||
sort: Sort;
|
||||
toggleColumn: (column: ColumnHeader) => void;
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ReduxProps {
|
||||
columnHeaders: ColumnHeader[];
|
||||
eventIdToNoteIds?: Readonly<Record<string, string[]>>;
|
||||
eventIdToNoteIds: Readonly<Record<string, string[]>>;
|
||||
getNotesByIds: (noteIds: string[]) => Note[];
|
||||
pinnedEventIds?: Readonly<Record<string, boolean>>;
|
||||
pinnedEventIds: Readonly<Record<string, boolean>>;
|
||||
range?: string;
|
||||
}
|
||||
|
||||
|
@ -82,6 +81,8 @@ interface DispatchProps {
|
|||
|
||||
type StatefulBodyComponentProps = OwnProps & ReduxProps & DispatchProps;
|
||||
|
||||
export const emptyColumnHeaders: ColumnHeader[] = [];
|
||||
|
||||
class StatefulBodyComponent extends React.PureComponent<StatefulBodyComponentProps> {
|
||||
public render() {
|
||||
const {
|
||||
|
@ -96,7 +97,6 @@ class StatefulBodyComponent extends React.PureComponent<StatefulBodyComponentPro
|
|||
range,
|
||||
sort,
|
||||
toggleColumn,
|
||||
width,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -104,10 +104,10 @@ class StatefulBodyComponent extends React.PureComponent<StatefulBodyComponentPro
|
|||
addNoteToEvent={this.onAddNoteToEvent}
|
||||
browserFields={browserFields}
|
||||
id={id}
|
||||
columnHeaders={columnHeaders || []}
|
||||
columnHeaders={columnHeaders || emptyColumnHeaders}
|
||||
columnRenderers={columnRenderers}
|
||||
data={data}
|
||||
eventIdToNoteIds={eventIdToNoteIds!}
|
||||
eventIdToNoteIds={eventIdToNoteIds}
|
||||
getNotesByIds={getNotesByIds}
|
||||
height={height}
|
||||
onColumnResized={this.onColumnResized}
|
||||
|
@ -117,13 +117,12 @@ class StatefulBodyComponent extends React.PureComponent<StatefulBodyComponentPro
|
|||
onPinEvent={this.onPinEvent}
|
||||
onUpdateColumns={this.onUpdateColumns}
|
||||
onUnPinEvent={this.onUnPinEvent}
|
||||
pinnedEventIds={pinnedEventIds!}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
range={range!}
|
||||
rowRenderers={rowRenderers}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
updateNote={this.onUpdateNote}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ export const DataProviders = pure<Props>(
|
|||
}) => (
|
||||
<DropTargetDataProviders data-test-subj="dataProviders">
|
||||
<TimelineContext.Consumer>
|
||||
{({ isLoading }) => (
|
||||
{isLoading => (
|
||||
<DroppableWrapper isDropDisabled={!show || isLoading} droppableId={getDroppableId(id)}>
|
||||
{dataProviders != null && dataProviders.length ? (
|
||||
<Providers
|
||||
|
|
|
@ -59,7 +59,7 @@ export class ProviderItemBadge extends PureComponent<ProviderItemBadgeProps, Own
|
|||
|
||||
return (
|
||||
<TimelineContext.Consumer>
|
||||
{({ isLoading }) => (
|
||||
{isLoading => (
|
||||
<ProviderItemActions
|
||||
andProviderId={andProviderId}
|
||||
browserFields={browserFields}
|
||||
|
|
|
@ -95,7 +95,7 @@ describe('Providers', () => {
|
|||
const mockOnDataProviderRemoved = jest.fn();
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -158,7 +158,7 @@ describe('Providers', () => {
|
|||
const mockOnDataProviderRemoved = jest.fn();
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -240,7 +240,7 @@ describe('Providers', () => {
|
|||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -318,7 +318,7 @@ describe('Providers', () => {
|
|||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -427,7 +427,7 @@ describe('Providers', () => {
|
|||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -508,7 +508,7 @@ describe('Providers', () => {
|
|||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
@ -594,7 +594,7 @@ describe('Providers', () => {
|
|||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<TimelineContext.Provider value={{ isLoading: true }}>
|
||||
<TimelineContext.Provider value={true}>
|
||||
<DroppableWrapper droppableId="unitTest">
|
||||
<Providers
|
||||
browserFields={{}}
|
||||
|
|
|
@ -7,23 +7,24 @@
|
|||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { BrowserFields } from '../../../containers/source';
|
||||
import { ColumnHeader } from '../body/column_headers/column_header';
|
||||
import { DetailItem } from '../../../graphql/types';
|
||||
import { StatefulEventDetails } from '../../event_details/stateful_event_details';
|
||||
import { LazyAccordion } from '../../lazy_accordion';
|
||||
import { OnUpdateColumns } from '../events';
|
||||
import { TimelineWidthContext } from '../timeline_context';
|
||||
|
||||
const ExpandableDetails = styled.div<{ hideExpandButton: boolean; width?: number }>`
|
||||
width: ${({ width }) => (width != null ? `${width}px;` : '100%')}
|
||||
${({ hideExpandButton }) =>
|
||||
hideExpandButton
|
||||
? `
|
||||
const ExpandableDetails = styled.div<{ hideExpandButton: boolean }>`
|
||||
${({ hideExpandButton }) =>
|
||||
hideExpandButton
|
||||
? `
|
||||
.euiAccordion__button {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
: ''};
|
||||
: ''};
|
||||
`;
|
||||
|
||||
ExpandableDetails.displayName = 'ExpandableDetails';
|
||||
|
@ -38,7 +39,6 @@ interface Props {
|
|||
onUpdateColumns: OnUpdateColumns;
|
||||
timelineId: string;
|
||||
toggleColumn: (column: ColumnHeader) => void;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export const ExpandableEvent = React.memo<Props>(
|
||||
|
@ -51,31 +51,33 @@ export const ExpandableEvent = React.memo<Props>(
|
|||
timelineId,
|
||||
toggleColumn,
|
||||
onUpdateColumns,
|
||||
width,
|
||||
}) => (
|
||||
<ExpandableDetails
|
||||
data-test-subj="timeline-expandable-details"
|
||||
hideExpandButton={true}
|
||||
width={width}
|
||||
>
|
||||
<LazyAccordion
|
||||
id={`timeline-${timelineId}-row-${id}`}
|
||||
renderExpandedContent={() => (
|
||||
<StatefulEventDetails
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
data={event}
|
||||
id={id}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
)}
|
||||
forceExpand={forceExpand}
|
||||
paddingSize="none"
|
||||
/>
|
||||
</ExpandableDetails>
|
||||
)
|
||||
}) => {
|
||||
const width = useContext(TimelineWidthContext);
|
||||
// Passing the styles directly to the component of LazyAccordion because the width is
|
||||
// being calculated and is recommended by Styled Components for performance
|
||||
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
|
||||
return (
|
||||
<ExpandableDetails hideExpandButton={true}>
|
||||
<LazyAccordion
|
||||
style={{ width: `${width}px` }}
|
||||
id={`timeline-${timelineId}-row-${id}`}
|
||||
renderExpandedContent={() => (
|
||||
<StatefulEventDetails
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
data={event}
|
||||
id={id}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
)}
|
||||
forceExpand={forceExpand}
|
||||
paddingSize="none"
|
||||
/>
|
||||
</ExpandableDetails>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ExpandableEvent.displayName = 'ExpandableEvent';
|
||||
|
|
|
@ -34,7 +34,7 @@ import { Footer, footerHeight } from './footer';
|
|||
import { TimelineHeader } from './header';
|
||||
import { calculateBodyHeight, combineQueries } from './helpers';
|
||||
import { TimelineRefetch } from './refetch_timeline';
|
||||
import { TimelineContext } from './timeline_context';
|
||||
import { TimelineContext, TimelineWidthContext } from './timeline_context';
|
||||
|
||||
const WrappedByAutoSizer = styled.div`
|
||||
width: 100%;
|
||||
|
@ -171,37 +171,38 @@ export const Timeline = React.memo<Props>(
|
|||
refetch,
|
||||
}) => (
|
||||
<TimelineRefetch loading={loading} id={id} inspect={inspect} refetch={refetch}>
|
||||
<TimelineContext.Provider value={{ isLoading: loading }}>
|
||||
<StatefulBody
|
||||
browserFields={browserFields}
|
||||
data={events}
|
||||
id={id}
|
||||
height={calculateBodyHeight({
|
||||
flyoutHeight,
|
||||
flyoutHeaderHeight,
|
||||
timelineHeaderHeight,
|
||||
timelineFooterHeight: footerHeight,
|
||||
})}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
width={width}
|
||||
/>
|
||||
<Footer
|
||||
serverSideEventCount={totalCount}
|
||||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
|
||||
height={footerHeight}
|
||||
isLive={isLive}
|
||||
isLoading={loading}
|
||||
itemsCount={events.length}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onLoadMore={loadMore}
|
||||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
|
||||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
|
||||
getUpdatedAt={getUpdatedAt}
|
||||
compact={isCompactFooter(width)}
|
||||
/>
|
||||
<TimelineContext.Provider value={loading}>
|
||||
<TimelineWidthContext.Provider value={width}>
|
||||
<StatefulBody
|
||||
browserFields={browserFields}
|
||||
data={events}
|
||||
id={id}
|
||||
height={calculateBodyHeight({
|
||||
flyoutHeight,
|
||||
flyoutHeaderHeight,
|
||||
timelineHeaderHeight,
|
||||
timelineFooterHeight: footerHeight,
|
||||
})}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
<Footer
|
||||
serverSideEventCount={totalCount}
|
||||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
|
||||
height={footerHeight}
|
||||
isLive={isLive}
|
||||
isLoading={loading}
|
||||
itemsCount={events.length}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onLoadMore={loadMore}
|
||||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
|
||||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
|
||||
getUpdatedAt={getUpdatedAt}
|
||||
compact={isCompactFooter(width)}
|
||||
/>
|
||||
</TimelineWidthContext.Provider>
|
||||
</TimelineContext.Provider>
|
||||
</TimelineRefetch>
|
||||
)}
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import * as React from 'react';
|
||||
|
||||
export interface TimelineContextData {
|
||||
isLoading: boolean;
|
||||
}
|
||||
export const TimelineContext = React.createContext<boolean>(false);
|
||||
|
||||
export const TimelineContext = React.createContext<TimelineContextData>({ isLoading: false });
|
||||
export const TimelineWidthContext = React.createContext<number>(0);
|
||||
|
|
|
@ -329,6 +329,7 @@
|
|||
"react-sticky": "^6.0.3",
|
||||
"react-syntax-highlighter": "^5.7.0",
|
||||
"react-vis": "^1.8.1",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"recompose": "^0.26.0",
|
||||
"reduce-reducers": "^0.4.3",
|
||||
"redux": "4.0.0",
|
||||
|
|
|
@ -23640,6 +23640,13 @@ react-vis@^1.8.1:
|
|||
prop-types "^15.5.8"
|
||||
react-motion "^0.4.8"
|
||||
|
||||
react-visibility-sensor@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz#5238380960d3a0b2be0b7faddff38541e337f5a9"
|
||||
integrity sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react@^16.2.0, react@^16.6.0, react@^16.8.0:
|
||||
version "16.8.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c"
|
||||
|
|
Loading…
Reference in a new issue