diff --git a/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts index 4889d40ae7d39..aca988e195161 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts @@ -49,7 +49,7 @@ describe('timeline data providers', () => { .first() .invoke('text') .should(hostname => { - expect(dataProviderText).to.eq(`host.name: "${hostname}"`); + expect(dataProviderText).to.eq(hostname); }); }); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts index 1a94a4abbe5bf..736eee421a305 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TIMELINE_FLYOUT_BODY, TIMELINE_NOT_READY_TO_DROP_BUTTON } from '../screens/timeline'; +import { TIMELINE_FLYOUT_HEADER, TIMELINE_NOT_READY_TO_DROP_BUTTON } from '../screens/timeline'; import { dragFirstHostToTimeline, waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; import { loginAndWaitForPage } from '../tasks/login'; @@ -26,7 +26,7 @@ describe('timeline flyout button', () => { it('toggles open the timeline', () => { openTimeline(); - cy.get(TIMELINE_FLYOUT_BODY).should('have.css', 'visibility', 'visible'); + cy.get(TIMELINE_FLYOUT_HEADER).should('have.css', 'visibility', 'visible'); }); it('sets the flyout button background to euiColorSuccess with a 10% alpha channel when the user starts dragging a host, but is not hovering over the flyout button', () => { diff --git a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts index 5638b8d23e83a..fbce585a70f86 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts @@ -31,6 +31,8 @@ export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContain export const TIMELINE_FIELDS_BUTTON = '[data-test-subj="timeline"] [data-test-subj="show-field-browser"]'; +export const TIMELINE_FLYOUT_HEADER = '[data-test-subj="eui-flyout-header"]'; + export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]'; export const TIMELINE_INSPECT_BUTTON = '[data-test-subj="inspect-empty-button"]'; diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index ad4a6e86ffc88..472a473842f02 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@types/lodash": "^4.14.110", "@types/js-yaml": "^3.12.1", - "@types/react-beautiful-dnd": "^11.0.4" + "@types/react-beautiful-dnd": "^12.1.1" }, "dependencies": { "lodash": "^4.17.15", diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index f3b2b736ed87d..5c15f2d3c8d4f 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -16,8 +16,8 @@ import { RecursivePartial, } from '@elastic/charts'; import { getOr, get, isNull, isNumber } from 'lodash/fp'; -import useResizeObserver from 'use-resize-observer/polyfilled'; +import { useThrottledResizeObserver } from '../utils'; import { ChartPlaceHolder } from './chart_place_holder'; import { useTimeZone } from '../../lib/kibana'; import { @@ -131,7 +131,7 @@ interface AreaChartComponentProps { } export const AreaChartComponent: React.FC = ({ areaChart, configs }) => { - const { ref: measureRef, width, height } = useResizeObserver({}); + const { ref: measureRef, width, height } = useThrottledResizeObserver(); const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); const chartHeight = getChartHeight(customHeight, height); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index da0f3d1d0047f..f53a1555fa1f4 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { Chart, BarSeries, Axis, Position, ScaleType, Settings } from '@elastic/charts'; import { getOr, get, isNumber } from 'lodash/fp'; import deepmerge from 'deepmerge'; -import useResizeObserver from 'use-resize-observer/polyfilled'; +import { useThrottledResizeObserver } from '../utils'; import { useTimeZone } from '../../lib/kibana'; import { ChartPlaceHolder } from './chart_place_holder'; import { @@ -105,7 +105,7 @@ interface BarChartComponentProps { } export const BarChartComponent: React.FC = ({ barChart, configs }) => { - const { ref: measureRef, width, height } = useResizeObserver({}); + const { ref: measureRef, width, height } = useThrottledResizeObserver(); const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); const chartHeight = getChartHeight(customHeight, height); diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx index 72f5a62d0af97..11db33fff6d72 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { defaultTo, noop } from 'lodash/fp'; +import { noop } from 'lodash/fp'; import React, { useCallback } from 'react'; import { DropResult, DragDropContext } from 'react-beautiful-dnd'; import { connect, ConnectedProps } from 'react-redux'; @@ -103,10 +103,7 @@ DragDropContextWrapperComponent.displayName = 'DragDropContextWrapperComponent'; const emptyDataProviders: dragAndDropModel.IdToDataProvider = {}; // stable reference const mapStateToProps = (state: State) => { - const dataProviders = defaultTo( - emptyDataProviders, - dragAndDropSelectors.dataProvidersSelector(state) - ); + const dataProviders = dragAndDropSelectors.dataProvidersSelector(state) ?? emptyDataProviders; return { dataProviders }; }; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index 9dcc335d4ff16..11891afabbf3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -88,21 +88,9 @@ describe('DraggableWrapper', () => { describe('ConditionalPortal', () => { const mount = useMountAppended(); const props = { - usePortal: false, registerProvider: jest.fn(), - isDragging: true, }; - it(`doesn't call registerProvider is NOT isDragging`, () => { - mount( - -
- - ); - - expect(props.registerProvider.mock.calls.length).toEqual(0); - }); - it('calls registerProvider when isDragging', () => { mount( diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index b7d368639ed92..3a6a4de7984db 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Draggable, DraggableProvided, @@ -15,7 +15,6 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { EuiPortal } from '@elastic/eui'; import { dragAndDropActions } from '../../store/drag_and_drop'; import { DataProvider } from '../timeline/data_providers/data_provider'; import { TruncatableText } from '../truncatable_text'; @@ -27,9 +26,6 @@ export const DragEffects = styled.div``; DragEffects.displayName = 'DragEffects'; -export const DraggablePortalContext = createContext(false); -export const useDraggablePortalContext = () => useContext(DraggablePortalContext); - /** * Wraps the `react-beautiful-dnd` error boundary. See also: * https://github.com/atlassian/react-beautiful-dnd/blob/v12.0.0/docs/guides/setup-problem-detection-and-error-recovery.md @@ -89,7 +85,6 @@ export const DraggableWrapper = React.memo( ({ dataProvider, render, truncate }) => { const [providerRegistered, setProviderRegistered] = useState(false); const dispatch = useDispatch(); - const usePortal = useDraggablePortalContext(); const registerProvider = useCallback(() => { if (!providerRegistered) { @@ -113,7 +108,26 @@ export const DraggableWrapper = React.memo( return ( - + ( + +
+ + {render(dataProvider, provided, snapshot)} + +
+
+ )} + > {droppableProvided => (
( key={getDraggableId(dataProvider.id)} > {(provided, snapshot) => ( - - - {truncate && !snapshot.isDragging ? ( - - {render(dataProvider, provided, snapshot)} - - ) : ( - - {render(dataProvider, provided, snapshot)} - - )} - - + {truncate && !snapshot.isDragging ? ( + + {render(dataProvider, provided, snapshot)} + + ) : ( + + {render(dataProvider, provided, snapshot)} + + )} + )} {droppableProvided.placeholder} @@ -178,20 +183,16 @@ DraggableWrapper.displayName = 'DraggableWrapper'; interface ConditionalPortalProps { children: React.ReactNode; - usePortal: boolean; - isDragging: boolean; registerProvider: () => void; } export const ConditionalPortal = React.memo( - ({ children, usePortal, registerProvider, isDragging }) => { + ({ children, registerProvider }) => { useEffect(() => { - if (isDragging) { - registerProvider(); - } - }, [isDragging, registerProvider]); + registerProvider(); + }, [registerProvider]); - return usePortal ? {children} : <>{children}; + return <>{children}; } ); diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index 821ef9be10e8d..a81954f57564e 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx @@ -6,7 +6,7 @@ import { rgba } from 'polished'; import React from 'react'; -import { Droppable } from 'react-beautiful-dnd'; +import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd'; import styled from 'styled-components'; interface Props { @@ -16,6 +16,7 @@ interface Props { isDropDisabled?: boolean; type?: string; render?: ({ isDraggingOver }: { isDraggingOver: boolean }) => React.ReactNode; + renderClone?: DraggableChildrenFn; } const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string }>` @@ -94,12 +95,14 @@ export const DroppableWrapper = React.memo( isDropDisabled = false, type, render = null, + renderClone, }) => ( {(provided, snapshot) => ( (({ width }) => { + if (width) { + return { + style: { + width: `${width}px`, + }, + }; + } +})` background-color: ${({ theme }) => theme.eui.euiColorEmptyShade}; border: ${({ theme }) => theme.eui.euiBorderThin}; box-shadow: 0 2px 2px -1px ${({ theme }) => rgba(theme.eui.euiColorMediumShade, 0.3)}, @@ -24,12 +36,9 @@ Field.displayName = 'Field'; * Renders a field (e.g. `event.action`) as a draggable badge */ -// 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 -export const DraggableFieldBadge = React.memo<{ fieldId: string; fieldWidth?: string }>( +export const DraggableFieldBadge = React.memo<{ fieldId: string; fieldWidth?: number }>( ({ fieldId, fieldWidth }) => ( - + {fieldId} ) diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx index 57f047416ec1c..1fe6c936d2823 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiBadgeProps, EuiToolTip, IconType } from '@elastic/eui'; +import { EuiBadge, EuiToolTip, IconType } from '@elastic/eui'; import React from 'react'; +import styled from 'styled-components'; import { Omit } from '../../../common/utility_types'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; @@ -116,13 +117,9 @@ export const DefaultDraggable = React.memo( DefaultDraggable.displayName = 'DefaultDraggable'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const Badge = styled(EuiBadge)` -// vertical-align: top; -// `; -export const Badge = (props: EuiBadgeProps) => ( - -); +export const Badge = styled(EuiBadge)` + vertical-align: top; +`; Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx index 249aae1eda0eb..15c423a3b3dc1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx @@ -12,7 +12,6 @@ import { EuiOutsideClickDetector, } from '@elastic/eui'; import { FeatureGeometry, FeatureProperty, MapToolTipProps } from '../types'; -import { DraggablePortalContext } from '../../drag_and_drop/draggable_wrapper'; import { ToolTipFooter } from './tooltip_footer'; import { LineToolTipContent } from './line_tool_tip_content'; import { PointToolTipContent } from './point_tool_tip_content'; @@ -101,46 +100,44 @@ export const MapToolTipComponent = ({ ) : ( - - { - if (closeTooltip != null) { - closeTooltip(); - setFeatureIndex(0); - } - }} - > -
- {featureGeometry != null && featureGeometry.type === 'LineString' ? ( - - ) : ( - - )} - {features.length > 1 && ( - { - setFeatureIndex(featureIndex - 1); - setIsLoadingNextFeature(true); - }} - nextFeature={() => { - setFeatureIndex(featureIndex + 1); - setIsLoadingNextFeature(true); - }} - /> - )} - {isLoadingNextFeature && } -
-
-
+ { + if (closeTooltip != null) { + closeTooltip(); + setFeatureIndex(0); + } + }} + > +
+ {featureGeometry != null && featureGeometry.type === 'LineString' ? ( + + ) : ( + + )} + {features.length > 1 && ( + { + setFeatureIndex(featureIndex - 1); + setIsLoadingNextFeature(true); + }} + nextFeature={() => { + setFeatureIndex(featureIndex + 1); + setIsLoadingNextFeature(true); + }} + /> + )} + {isLoadingNextFeature && } +
+
); }; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx index e9903ce66d799..cd94a9fdcb5ac 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx @@ -115,6 +115,17 @@ export const getColumns = ({ )} isDropDisabled={true} type={DRAG_TYPE_FIELD} + renderClone={provided => ( +
+ + + +
+ )} > - {(provided, snapshot) => ( + {provided => (
- {!snapshot.isDragging ? ( - - ) : ( - - - - )} +
)}
diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/event_details_width_context.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/event_details_width_context.tsx new file mode 100644 index 0000000000000..86a776a0313cc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/event_details_width_context.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useContext } from 'react'; +import { useThrottledResizeObserver } from '../utils'; + +const EventDetailsWidthContext = createContext(0); + +export const useEventDetailsWidthContext = () => useContext(EventDetailsWidthContext); + +export const EventDetailsWidthProvider = React.memo(({ children }) => { + const { ref, width } = useThrottledResizeObserver(); + + return ( + <> + + {children} + +
+ + ); +}); + +EventDetailsWidthProvider.displayName = 'EventDetailsWidthProvider'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index a913186d9ad3b..ea2cb661763fa 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -9,7 +9,6 @@ import { getOr, isEmpty, union } from 'lodash/fp'; import React, { useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import useResizeObserver from 'use-resize-observer/polyfilled'; import { BrowserFields } from '../../containers/source'; import { TimelineQuery } from '../../containers/timeline'; @@ -25,8 +24,8 @@ import { OnChangeItemsPerPage } from '../timeline/events'; import { Footer, footerHeight } from '../timeline/footer'; import { combineQueries } from '../timeline/helpers'; import { TimelineRefetch } from '../timeline/refetch_timeline'; -import { isCompactFooter } from '../timeline/timeline'; import { ManageTimelineContext, TimelineTypeContextProps } from '../timeline/timeline_context'; +import { EventDetailsWidthProvider } from './event_details_width_context'; import * as i18n from './translations'; import { Filter, @@ -38,15 +37,15 @@ import { inputsModel } from '../../store'; const DEFAULT_EVENTS_VIEWER_HEIGHT = 500; -const WrappedByAutoSizer = styled.div` - width: 100%; -`; // required by AutoSizer -WrappedByAutoSizer.displayName = 'WrappedByAutoSizer'; - const StyledEuiPanel = styled(EuiPanel)` max-width: 100%; `; +const EventsContainerLoading = styled.div` + width: 100%; + overflow: auto; +`; + interface Props { browserFields: BrowserFields; columns: ColumnHeaderOptions[]; @@ -94,7 +93,6 @@ const EventsViewerComponent: React.FC = ({ toggleColumn, utilityBar, }) => { - const { ref: measureRef, width = 0 } = useResizeObserver({}); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const kibana = useKibana(); const combinedQueries = combineQueries({ @@ -117,25 +115,25 @@ const EventsViewerComponent: React.FC = ({ ), [columnsHeader, timelineTypeContext.queryFields] ); + const sortField = useMemo( + () => ({ + sortFieldId: sort.columnId, + direction: sort.sortDirection as Direction, + }), + [sort.columnId, sort.sortDirection] + ); return ( - <> - -
- - - {combinedQueries != null ? ( + {combinedQueries != null ? ( + {({ @@ -169,15 +167,8 @@ const EventsViewerComponent: React.FC = ({ {utilityBar?.(refetch, totalCountMinusDeleted)} -
- + + = ({ />
= ({ tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)} /> -
+ ); }}
- ) : null} - +
+ ) : null} ); }; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx index 990c2678b1006..62f9297c38ef5 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx @@ -90,6 +90,13 @@ export const getFieldItems = ({ key={`field-browser-field-items-field-droppable-wrapper-${timelineId}-${categoryId}-${field.name}`} isDropDisabled={true} type={DRAG_TYPE_FIELD} + renderClone={provided => ( +
+ + + +
+ )} > - {(provided, snapshot) => ( -
- {!snapshot.isDragging ? ( - - - - c.id === field.name) !== -1} - data-test-subj={`field-${field.name}-checkbox`} - id={field.name || ''} - onChange={() => - toggleColumn({ - columnHeaderType: defaultColumnHeaderType, - id: field.name || '', - width: DEFAULT_COLUMN_MIN_WIDTH, - }) - } - /> - - - - - - - - + {provided => ( +
+ + + + c.id === field.name) !== -1} + data-test-subj={`field-${field.name}-checkbox`} + id={field.name || ''} + onChange={() => + toggleColumn({ + columnHeaderType: defaultColumnHeaderType, + id: field.name || '', + width: DEFAULT_COLUMN_MIN_WIDTH, + }) + } + /> + + - - + + - - - ) : ( - - - - )} + + + + + + +
)} diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap index abdc4f4681294..4bf0033bcb430 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap @@ -3,7 +3,6 @@ exports[`Flyout rendering it renders correctly against snapshot 1`] = ` ( isDataInTimeline, isDatepickerLocked, title, - width = DEFAULT_TIMELINE_WIDTH, noteIds, notesById, timelineId, @@ -77,7 +75,6 @@ const StatefulFlyoutHeader = React.memo( updateTitle={updateTitle} updateNote={updateNote} usersViewing={usersViewing} - width={width} /> ); } @@ -103,7 +100,6 @@ const makeMapStateToProps = () => { kqlQuery, title = '', noteIds = emptyNotesId, - width = DEFAULT_TIMELINE_WIDTH, } = timeline; const history = emptyHistory; // TODO: get history from store via selector @@ -118,7 +114,6 @@ const makeMapStateToProps = () => { isDatepickerLocked: globalInput.linkTo.includes('timeline'), noteIds, title, - width, }; }; return mapStateToProps; @@ -126,28 +121,6 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ associateNote: (noteId: string) => dispatch(timelineActions.addNote({ id: timelineId, noteId })), - applyDeltaToWidth: ({ - id, - delta, - bodyClientWidthPixels, - maxWidthPercent, - minWidthPixels, - }: { - id: string; - delta: number; - bodyClientWidthPixels: number; - maxWidthPercent: number; - minWidthPixels: number; - }) => - dispatch( - timelineActions.applyDeltaToWidth({ - id, - delta, - bodyClientWidthPixels, - maxWidthPercent, - minWidthPixels, - }) - ), createTimeline: ({ id, show }: { id: string; show?: boolean }) => dispatch( timelineActions.createTimeline({ diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..df96f2a1f7eba --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/__snapshots__/index.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlyoutHeaderWithCloseButton renders correctly against snapshot 1`] = ` + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.test.tsx new file mode 100644 index 0000000000000..e0eace2ad5b10 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../../mock'; +import { FlyoutHeaderWithCloseButton } from '.'; + +describe('FlyoutHeaderWithCloseButton', () => { + test('renders correctly against snapshot', () => { + const EmptyComponent = shallow( + + + + ); + expect(EmptyComponent.find('FlyoutHeaderWithCloseButton')).toMatchSnapshot(); + }); + + test('it should invoke onClose when the close button is clicked', () => { + const closeMock = jest.fn(); + const wrapper = mount( + + + + ); + wrapper + .find('[data-test-subj="close-timeline"] button') + .first() + .simulate('click'); + + expect(closeMock).toBeCalled(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.tsx new file mode 100644 index 0000000000000..a4d9f0e8293df --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import styled from 'styled-components'; + +import { FlyoutHeader } from '../header'; +import * as i18n from './translations'; + +const FlyoutHeaderContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +`; + +// manually wrap the close button because EuiButtonIcon can't be a wrapped `styled` +const WrappedCloseButton = styled.div` + margin-right: 5px; +`; + +const FlyoutHeaderWithCloseButtonComponent: React.FC<{ + onClose: () => void; + timelineId: string; + usersViewing: string[]; +}> = ({ onClose, timelineId, usersViewing }) => ( + + + + + + + + +); + +export const FlyoutHeaderWithCloseButton = React.memo(FlyoutHeaderWithCloseButtonComponent); + +FlyoutHeaderWithCloseButton.displayName = 'FlyoutHeaderWithCloseButton'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/translations.ts b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/translations.ts new file mode 100644 index 0000000000000..7fcffc9c1f0b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/flyout/header_with_close_button/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const CLOSE_TIMELINE = i18n.translate( + 'xpack.siem.timeline.flyout.header.closeTimelineButtonLabel', + { + defaultMessage: 'Close timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx index 83b842956e10e..ab41b4617894e 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx @@ -13,9 +13,14 @@ import { apolloClientObservable, mockGlobalState, TestProviders } from '../../mo import { createStore, State } from '../../store'; import { mockDataProviders } from '../timeline/data_providers/mock/mock_data_providers'; -import { Flyout, FlyoutComponent, flyoutHeaderHeight } from '.'; +import { Flyout, FlyoutComponent } from '.'; import { FlyoutButton } from './button'; +jest.mock('../timeline', () => ({ + // eslint-disable-next-line react/display-name + StatefulTimeline: () =>
, +})); + const testFlyoutHeight = 980; const usersViewing = ['elastic']; @@ -26,12 +31,7 @@ describe('Flyout', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(wrapper.find('Flyout')).toMatchSnapshot(); @@ -40,12 +40,7 @@ describe('Flyout', () => { test('it renders the default flyout state as a button', () => { const wrapper = mount( - + ); @@ -57,41 +52,13 @@ describe('Flyout', () => { ).toContain('Timeline'); }); - test('it renders the title field when its state is set to flyout is true', () => { - const stateShowIsTrue = set('timeline.timelineById.test.show', true, state); - const storeShowIsTrue = createStore(stateShowIsTrue, apolloClientObservable); - - const wrapper = mount( - - - - ); - - expect( - wrapper - .find('[data-test-subj="timeline-title"]') - .first() - .props().placeholder - ).toContain('Untitled Timeline'); - }); - test('it does NOT render the fly out button when its state is set to flyout is true', () => { const stateShowIsTrue = set('timeline.timelineById.test.show', true, state); const storeShowIsTrue = createStore(stateShowIsTrue, apolloClientObservable); const wrapper = mount( - + ); @@ -100,31 +67,6 @@ describe('Flyout', () => { ); }); - test('it renders the flyout body', () => { - const stateShowIsTrue = set('timeline.timelineById.test.show', true, state); - const storeShowIsTrue = createStore(stateShowIsTrue, apolloClientObservable); - - const wrapper = mount( - - -

{'Fake flyout body'}

-
-
- ); - - expect( - wrapper - .find('[data-test-subj="eui-flyout-body"]') - .first() - .text() - ).toContain('Fake flyout body'); - }); - test('it does render the data providers badge when the number is greater than 0', () => { const stateWithDataProviders = set( 'timeline.timelineById.test.dataProviders', @@ -135,12 +77,7 @@ describe('Flyout', () => { const wrapper = mount( - + ); @@ -157,12 +94,7 @@ describe('Flyout', () => { const wrapper = mount( - + ); @@ -177,12 +109,7 @@ describe('Flyout', () => { test('it hides the data providers badge when the timeline does NOT have data providers', () => { const wrapper = mount( - + ); @@ -204,12 +131,7 @@ describe('Flyout', () => { const wrapper = mount( - + ); @@ -228,7 +150,6 @@ describe('Flyout', () => { { expect(showTimeline).toBeCalled(); }); - - test('should call the onClose when the close button is clicked', () => { - const stateShowIsTrue = set('timeline.timelineById.test.show', true, state); - const storeShowIsTrue = createStore(stateShowIsTrue, apolloClientObservable); - - const showTimeline = (jest.fn() as unknown) as ActionCreator<{ id: string; show: boolean }>; - const wrapper = mount( - - - - ); - - wrapper - .find('[data-test-subj="close-timeline"] button') - .first() - .simulate('click'); - - expect(showTimeline).toBeCalled(); - }); }); describe('showFlyoutButton', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index 22fc9f27ce26c..44abe5b679c8e 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -5,7 +5,6 @@ */ import { EuiBadge } from '@elastic/eui'; -import { defaultTo, getOr } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import styled from 'styled-components'; @@ -16,9 +15,8 @@ import { FlyoutButton } from './button'; import { Pane } from './pane'; import { timelineActions } from '../../store/actions'; import { DEFAULT_TIMELINE_WIDTH } from '../timeline/body/constants'; - -/** The height in pixels of the flyout header, exported for use in height calculations */ -export const flyoutHeaderHeight: number = 60; +import { StatefulTimeline } from '../timeline'; +import { TimelineById } from '../../store/timeline/types'; export const Badge = styled(EuiBadge)` position: absolute; @@ -38,9 +36,7 @@ const Visible = styled.div<{ show?: boolean }>` Visible.displayName = 'Visible'; interface OwnProps { - children?: React.ReactNode; flyoutHeight: number; - headerHeight: number; timelineId: string; usersViewing: string[]; } @@ -48,17 +44,7 @@ interface OwnProps { type Props = OwnProps & ProsFromRedux; export const FlyoutComponent = React.memo( - ({ - children, - dataProviders, - flyoutHeight, - headerHeight, - show, - showTimeline, - timelineId, - usersViewing, - width, - }) => { + ({ dataProviders, flyoutHeight, show, showTimeline, timelineId, usersViewing, width }) => { const handleClose = useCallback(() => showTimeline({ id: timelineId, show: false }), [ showTimeline, timelineId, @@ -73,17 +59,15 @@ export const FlyoutComponent = React.memo( - {children} + ( FlyoutComponent.displayName = 'FlyoutComponent'; +const DEFAULT_DATA_PROVIDERS: DataProvider[] = []; +const DEFAULT_TIMELINE_BY_ID = {}; + const mapStateToProps = (state: State, { timelineId }: OwnProps) => { - const timelineById = defaultTo({}, timelineSelectors.timelineByIdSelector(state)); - const dataProviders = getOr([], `${timelineId}.dataProviders`, timelineById) as DataProvider[]; - const show = getOr(false, `${timelineId}.show`, timelineById) as boolean; - const width = getOr(DEFAULT_TIMELINE_WIDTH, `${timelineId}.width`, timelineById) as number; + const timelineById: TimelineById = + timelineSelectors.timelineByIdSelector(state) ?? DEFAULT_TIMELINE_BY_ID; + /* + In case timelineById[timelineId]?.dataProviders is an empty array it will cause unnecessary rerender + of StatefulTimeline which can be expensive, so to avoid that return DEFAULT_DATA_PROVIDERS + */ + const dataProviders = timelineById[timelineId]?.dataProviders.length + ? timelineById[timelineId]?.dataProviders + : DEFAULT_DATA_PROVIDERS; + const show = timelineById[timelineId]?.show ?? false; + const width = timelineById[timelineId]?.width ?? DEFAULT_TIMELINE_WIDTH; return { dataProviders, show, width }; }; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap index efa682cd4d18e..d30fd6f31012c 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap @@ -3,14 +3,8 @@ exports[`Pane renders correctly against snapshot 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index 365f99c6667b8..53cf8f95de0ce 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -8,12 +8,10 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../../mock'; -import { flyoutHeaderHeight } from '..'; import { Pane } from '.'; const testFlyoutHeight = 980; const testWidth = 640; -const usersViewing = ['elastic']; describe('Pane', () => { test('renders correctly against snapshot', () => { @@ -21,10 +19,8 @@ describe('Pane', () => { {'I am a child of flyout'} @@ -39,10 +35,8 @@ describe('Pane', () => { {'I am a child of flyout'} @@ -53,87 +47,13 @@ describe('Pane', () => { expect(wrapper.find('Resizable').get(0).props.maxWidth).toEqual('95vw'); }); - test('it applies timeline styles to the EuiFlyout', () => { - const wrapper = mount( - - - {'I am a child of flyout'} - - - ); - - expect( - wrapper - .find('[data-test-subj="eui-flyout"]') - .first() - .hasClass('timeline-flyout') - ).toEqual(true); - }); - - test('it applies timeline styles to the EuiFlyoutHeader', () => { - const wrapper = mount( - - - {'I am a child of flyout'} - - - ); - - expect( - wrapper - .find('[data-test-subj="eui-flyout-header"]') - .first() - .hasClass('timeline-flyout-header') - ).toEqual(true); - }); - - test('it applies timeline styles to the EuiFlyoutBody', () => { - const wrapper = mount( - - - {'I am a child of flyout'} - - - ); - - expect( - wrapper - .find('[data-test-subj="eui-flyout-body"]') - .first() - .hasClass('timeline-flyout-body') - ).toEqual(true); - }); - test('it should render a resize handle', () => { const wrapper = mount( {'I am a child of flyout'} @@ -149,74 +69,19 @@ describe('Pane', () => { ).toEqual(true); }); - test('it should render an empty title', () => { + test('it should render children', () => { const wrapper = mount( - {'I am a child of flyout'} - - - ); - - expect( - wrapper - .find('[data-test-subj="timeline-title"]') - .first() - .text() - ).toContain(''); - }); - - test('it should render the flyout body', () => { - const wrapper = mount( - - {'I am a mock body'} ); - expect( - wrapper - .find('[data-test-subj="eui-flyout-body"]') - .first() - .text() - ).toContain('I am a mock body'); - }); - - test('it should invoke onClose when the close button is clicked', () => { - const closeMock = jest.fn(); - const wrapper = mount( - - - {'I am a mock child'} - - - ); - wrapper - .find('[data-test-subj="close-timeline"] button') - .first() - .simulate('click'); - - expect(closeMock).toBeCalled(); + expect(wrapper.first().text()).toContain('I am a mock body'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx index 38ec4a4b6f1f3..3b5041c1ee346 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx @@ -4,130 +4,85 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { EuiFlyout } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { Resizable, ResizeCallback } from 're-resizable'; -import { throttle } from 'lodash/fp'; import { TimelineResizeHandle } from './timeline_resize_handle'; -import { FlyoutHeader } from '../header'; +import { EventDetailsWidthProvider } from '../../events_viewer/event_details_width_context'; import * as i18n from './translations'; import { timelineActions } from '../../../store/actions'; const minWidthPixels = 550; // do not allow the flyout to shrink below this width (pixels) const maxWidthPercent = 95; // do not allow the flyout to grow past this percentage of the view -interface OwnProps { +interface FlyoutPaneComponentProps { children: React.ReactNode; flyoutHeight: number; - headerHeight: number; onClose: () => void; timelineId: string; - usersViewing: string[]; width: number; } -type Props = OwnProps & PropsFromRedux; - -const EuiFlyoutContainer = styled.div<{ headerHeight: number }>` +const EuiFlyoutContainer = styled.div` .timeline-flyout { min-width: 150px; width: auto; } - .timeline-flyout-header { - align-items: center; - box-shadow: none; - display: flex; - flex-direction: row; - height: ${({ headerHeight }) => `${headerHeight}px`}; - max-height: ${({ headerHeight }) => `${headerHeight}px`}; - overflow: hidden; - padding: 5px 0 0 10px; - } - .timeline-flyout-body { - overflow-y: hidden; - padding: 0; - .euiFlyoutBody__overflowContent { - padding: 0; - } - } `; -const FlyoutHeaderContainer = styled.div` - align-items: center; +const StyledResizable = styled(Resizable)` display: flex; - flex-direction: row; - justify-content: space-between; - width: 100%; -`; - -// manually wrap the close button because EuiButtonIcon can't be a wrapped `styled` -const WrappedCloseButton = styled.div` - margin-right: 5px; + flex-direction: column; `; -const FlyoutHeaderWithCloseButtonComponent: React.FC<{ - onClose: () => void; - timelineId: string; - usersViewing: string[]; -}> = ({ onClose, timelineId, usersViewing }) => ( - - - - - - - - -); - -const FlyoutHeaderWithCloseButton = React.memo( - FlyoutHeaderWithCloseButtonComponent, - (prevProps, nextProps) => - prevProps.timelineId === nextProps.timelineId && - prevProps.usersViewing === nextProps.usersViewing -); +const RESIZABLE_ENABLE = { left: true }; -const FlyoutPaneComponent: React.FC = ({ - applyDeltaToWidth, +const FlyoutPaneComponent: React.FC = ({ children, flyoutHeight, - headerHeight, onClose, timelineId, - usersViewing, width, }) => { - const [lastDelta, setLastDelta] = useState(0); + const dispatch = useDispatch(); + const onResizeStop: ResizeCallback = useCallback( (e, direction, ref, delta) => { const bodyClientWidthPixels = document.body.clientWidth; if (delta.width) { - applyDeltaToWidth({ - bodyClientWidthPixels, - delta: -(delta.width - lastDelta), - id: timelineId, - maxWidthPercent, - minWidthPixels, - }); - setLastDelta(delta.width); + dispatch( + timelineActions.applyDeltaToWidth({ + bodyClientWidthPixels, + delta: -delta.width, + id: timelineId, + maxWidthPercent, + minWidthPixels, + }) + ); } }, - [applyDeltaToWidth, maxWidthPercent, minWidthPixels, lastDelta] + [dispatch] + ); + const resizableDefaultSize = useMemo( + () => ({ + width, + height: '100%', + }), + [] + ); + const resizableHandleComponent = useMemo( + () => ({ + left: , + }), + [flyoutHeight] ); - const resetLastDelta = useCallback(() => setLastDelta(0), [setLastDelta]); - const throttledResize = throttle(100, onResizeStop); return ( - + = ({ onClose={onClose} size="l" > - - ), - }} - onResizeStart={resetLastDelta} - onResize={throttledResize} + handleComponent={resizableHandleComponent} + onResizeStop={onResizeStop} > - - - - - {children} - - + {children} + ); }; -const mapDispatchToProps = { - applyDeltaToWidth: timelineActions.applyDeltaToWidth, -}; - -const connector = connect(null, mapDispatchToProps); - -type PropsFromRedux = ConnectedProps; - -export const Pane = connector(React.memo(FlyoutPaneComponent)); +export const Pane = React.memo(FlyoutPaneComponent); Pane.displayName = 'Pane'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/translations.ts b/x-pack/legacy/plugins/siem/public/components/flyout/pane/translations.ts index 4ba0307eb527b..0c31cdb81e8e1 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/translations.ts @@ -12,10 +12,3 @@ export const TIMELINE_DESCRIPTION = i18n.translate( defaultMessage: 'Timeline Properties', } ); - -export const CLOSE_TIMELINE = i18n.translate( - 'xpack.siem.timeline.flyout.pane.closeTimelineButtonLabel', - { - defaultMessage: 'Close timeline', - } -); diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx index d6f8143745356..f10a740db2b93 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx @@ -7,11 +7,10 @@ import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import { getOr, omit } from 'lodash/fp'; import React, { useCallback } from 'react'; -import { connect } from 'react-redux'; -import { ActionCreator } from 'typescript-fsa'; +import { connect, ConnectedProps } from 'react-redux'; import styled, { css } from 'styled-components'; -import { inputsModel, inputsSelectors, State } from '../../store'; +import { inputsSelectors, State } from '../../store'; import { InputsModelId } from '../../store/inputs/constants'; import { inputsActions } from '../../store/inputs'; @@ -60,24 +59,7 @@ interface OwnProps { title: string | React.ReactElement | React.ReactNode; } -interface InspectButtonReducer { - id: string; - isInspected: boolean; - loading: boolean; - inspect: inputsModel.InspectQuery | null; - selectedInspectIndex: number; -} - -interface InspectButtonDispatch { - setIsInspected: ActionCreator<{ - id: string; - inputId: InputsModelId; - isInspected: boolean; - selectedInspectIndex: number; - }>; -} - -type InspectButtonProps = OwnProps & InspectButtonReducer & InspectButtonDispatch; +type InspectButtonProps = OwnProps & PropsFromRedux; const InspectButtonComponent: React.FC = ({ compact = false, @@ -175,7 +157,8 @@ const mapDispatchToProps = { setIsInspected: inputsActions.setInspectionParameter, }; -export const InspectButton = connect( - makeMapStateToProps, - mapDispatchToProps -)(React.memo(InspectButtonComponent)); +const connector = connect(makeMapStateToProps, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const InspectButton = connector(React.memo(InspectButtonComponent)); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.tsx index caa9cd0689c76..982937659c0aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonIcon, EuiModal, EuiToolTip, EuiOverlayMask } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { DeleteTimelineModal, DELETE_TIMELINE_MODAL_WIDTH } from './delete_timeline_modal'; import * as i18n from '../translations'; @@ -23,15 +23,15 @@ export const DeleteTimelineModalButton = React.memo( ({ deleteTimelines, savedObjectId, title }) => { const [showModal, setShowModal] = useState(false); - const openModal = () => setShowModal(true); - const closeModal = () => setShowModal(false); + const openModal = useCallback(() => setShowModal(true), [setShowModal]); + const closeModal = useCallback(() => setShowModal(false), [setShowModal]); - const onDelete = () => { + const onDelete = useCallback(() => { if (deleteTimelines != null && savedObjectId != null) { deleteTimelines([savedObjectId]); } closeModal(); - }; + }, [deleteTimelines, savedObjectId, closeModal]); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/components/page/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/index.tsx index 781155c3ddc38..ef6a19f4b7448 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/index.tsx @@ -4,15 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { - EuiBadge, - EuiBadgeProps, - EuiDescriptionList, - EuiFlexGroup, - EuiIcon, - EuiPage, -} from '@elastic/eui'; +import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon, EuiPage } from '@elastic/eui'; import styled, { createGlobalStyle } from 'styled-components'; /* @@ -20,6 +12,12 @@ import styled, { createGlobalStyle } from 'styled-components'; and `EuiPopover`, `EuiToolTip` global styles */ export const AppGlobalStyle = createGlobalStyle` + /* dirty hack to fix draggables with tooltip on FF */ + body#siem-app { + position: static; + } + /* end of dirty hack to fix draggables with tooltip on FF */ + div.app-wrapper { background-color: rgba(0,0,0,0); } @@ -107,6 +105,7 @@ export const PageHeader = styled.div` PageHeader.displayName = 'PageHeader'; export const FooterContainer = styled.div` + flex: 0; bottom: 0; color: #666; left: 0; @@ -154,13 +153,9 @@ export const Pane1FlexContent = styled.div` Pane1FlexContent.displayName = 'Pane1FlexContent'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const Badge = styled(EuiBadge)` -// margin-left: 5px; -// `; -export const CountBadge = (props: EuiBadgeProps) => ( - -); +export const CountBadge = styled(EuiBadge)` + margin-left: 5px; +`; CountBadge.displayName = 'CountBadge'; @@ -170,13 +165,9 @@ export const Spacer = styled.span` Spacer.displayName = 'Spacer'; -// Ref: https://github.com/elastic/eui/issues/1655 -// export const Badge = styled(EuiBadge)` -// vertical-align: top; -// `; -export const Badge = (props: EuiBadgeProps) => ( - -); +export const Badge = styled(EuiBadge)` + vertical-align: top; +`; Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx index 6f22287774d7e..a557e4cea2df0 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx @@ -9,7 +9,6 @@ import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; import React, { useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Dispatch } from 'redux'; -import { ActionCreator } from 'typescript-fsa'; import { AllTimelinesQuery } from '../../containers/timeline/all'; import { SortFieldTimeline, Direction } from '../../graphql/types'; @@ -31,14 +30,13 @@ export type Props = OwnProps & PropsFromRedux; const StatefulRecentTimelinesComponent = React.memo( ({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => { - const actionDispatcher = updateIsLoading as ActionCreator<{ id: string; isLoading: boolean }>; const onOpenTimeline: OnOpenTimeline = useCallback( ({ duplicate, timelineId }: { duplicate: boolean; timelineId: string }) => { queryTimelineById({ apolloClient, duplicate, timelineId, - updateIsLoading: actionDispatcher, + updateIsLoading, updateTimeline, }); }, diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx index 1fdcd8eee941f..0ee54a1a20003 100644 --- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx @@ -26,16 +26,10 @@ describe('SkeletonRow', () => { expect(wrapper.find('.siemSkeletonRow__cell')).toHaveLength(10); }); - test('it applies row and cell styles when cellColor/cellMargin/rowHeight/rowPadding/style provided', () => { + test('it applies row and cell styles when cellColor/cellMargin/rowHeight/rowPadding provided', () => { const wrapper = mount( - + ); const siemSkeletonRow = wrapper.find('.siemSkeletonRow').first(); @@ -43,7 +37,6 @@ describe('SkeletonRow', () => { expect(siemSkeletonRow).toHaveStyleRule('height', '100px'); expect(siemSkeletonRow).toHaveStyleRule('padding', '10px'); - expect(siemSkeletonRow.props().style!.width).toBe('auto'); expect(siemSkeletonRowCell).toHaveStyleRule('background-color', 'red'); expect(siemSkeletonRowCell).toHaveStyleRule('margin-left', '10px', { modifier: '& + &', diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx index dce360877130e..ae30f11d8bb16 100644 --- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx @@ -54,11 +54,10 @@ Cell.displayName = 'Cell'; export interface SkeletonRowProps extends CellProps, RowProps { cellCount?: number; - style?: object; } export const SkeletonRow = React.memo( - ({ cellColor, cellCount = 4, cellMargin, rowHeight, rowPadding, style }) => { + ({ cellColor, cellCount = 4, cellMargin, rowHeight, rowPadding }) => { const cells = useMemo( () => [...Array(cellCount)].map( @@ -69,7 +68,7 @@ export const SkeletonRow = React.memo( ); return ( - + {cells} ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap index 372930ee3167d..02938cb2b86b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap @@ -1,668 +1,702 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Timeline rendering renders correctly against snapshot 1`] = ` - - - + + + - + onChangeDataProviderKqlQuery={[MockFunction]} + onChangeDroppableAndProvider={[MockFunction]} + onDataProviderEdited={[MockFunction]} + onDataProviderRemoved={[MockFunction]} + onToggleDataProviderEnabled={[MockFunction]} + onToggleDataProviderExcluded={[MockFunction]} + show={true} + showCallOutUnauthorizedMsg={false} + /> + + - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index b8b03be4e4720..03e4f4b5f0f2b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -490,7 +490,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` isCombineEnabled={false} isDropDisabled={false} mode="standard" - renderClone={null} + renderClone={[Function]} type="drag-type-field" > diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx index c3f28fd513d08..e070ed8fa1d2a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx @@ -4,27 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Draggable } from 'react-beautiful-dnd'; import { Resizable, ResizeCallback } from 're-resizable'; +import deepEqual from 'fast-deep-equal'; import { ColumnHeaderOptions } from '../../../../store/timeline/model'; -import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; -import { getDraggableFieldId, DRAG_TYPE_FIELD } from '../../../drag_and_drop/helpers'; -import { DraggableFieldBadge } from '../../../draggables/field_badge'; +import { getDraggableFieldId } from '../../../drag_and_drop/helpers'; import { OnColumnRemoved, OnColumnSorted, OnFilterChange, OnColumnResized } from '../../events'; import { EventsTh, EventsThContent, EventsHeadingHandle } from '../../styles'; import { Sort } from '../sort'; -import { DraggingContainer } from './common/dragging_container'; import { Header } from './header'; +const RESIZABLE_ENABLE = { right: true }; + interface ColumneHeaderProps { draggableIndex: number; header: ColumnHeaderOptions; onColumnRemoved: OnColumnRemoved; onColumnSorted: OnColumnSorted; onColumnResized: OnColumnResized; + isDragging: boolean; onFilterChange?: OnFilterChange; sort: Sort; timelineId: string; @@ -34,69 +35,82 @@ const ColumnHeaderComponent: React.FC = ({ draggableIndex, header, timelineId, + isDragging, onColumnRemoved, onColumnResized, onColumnSorted, onFilterChange, sort, }) => { - const [isDragging, setIsDragging] = React.useState(false); - const handleResizeStop: ResizeCallback = (e, direction, ref, delta) => { - onColumnResized({ columnId: header.id, delta: delta.width }); - }; + const resizableSize = useMemo( + () => ({ + width: header.width, + height: 'auto', + }), + [header.width] + ); + const resizableStyle: { + position: 'absolute' | 'relative'; + } = useMemo( + () => ({ + position: isDragging ? 'absolute' : 'relative', + }), + [isDragging] + ); + const resizableHandleComponent = useMemo( + () => ({ + right: , + }), + [] + ); + const handleResizeStop: ResizeCallback = useCallback( + (e, direction, ref, delta) => { + onColumnResized({ columnId: header.id, delta: delta.width }); + }, + [header.id, onColumnResized] + ); + const draggableId = useMemo( + () => + getDraggableFieldId({ + contextId: `timeline-column-headers-${timelineId}`, + fieldId: header.id, + }), + [timelineId, header.id] + ); return ( , - }} + enable={RESIZABLE_ENABLE} + size={resizableSize} + style={resizableStyle} + handleComponent={resizableHandleComponent} onResizeStop={handleResizeStop} > - {(dragProvided, dragSnapshot) => ( + {dragProvided => ( - {!dragSnapshot.isDragging ? ( - -
- - ) : ( - - - - - - )} + +
+ )} @@ -104,4 +118,16 @@ const ColumnHeaderComponent: React.FC = ({ ); }; -export const ColumnHeader = React.memo(ColumnHeaderComponent); +export const ColumnHeader = React.memo( + ColumnHeaderComponent, + (prevProps, nextProps) => + prevProps.draggableIndex === nextProps.draggableIndex && + prevProps.timelineId === nextProps.timelineId && + prevProps.isDragging === nextProps.isDragging && + prevProps.onColumnRemoved === nextProps.onColumnRemoved && + prevProps.onColumnResized === nextProps.onColumnResized && + prevProps.onColumnSorted === nextProps.onColumnSorted && + prevProps.onFilterChange === nextProps.onFilterChange && + prevProps.sort === nextProps.sort && + deepEqual(prevProps.header, nextProps.header) +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx index ab8dc629dd577..7a072f1dbf578 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx @@ -6,9 +6,12 @@ import { EuiCheckbox } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import React from 'react'; -import { Droppable } from 'react-beautiful-dnd'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd'; +import deepEqual from 'fast-deep-equal'; +import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; +import { DraggableFieldBadge } from '../../../draggables/field_badge'; import { BrowserFields } from '../../../../containers/source'; import { ColumnHeaderOptions } from '../../../../store/timeline/model'; import { DRAG_TYPE_FIELD, droppableTimelineColumnsPrefix } from '../../../drag_and_drop/helpers'; @@ -53,6 +56,26 @@ interface Props { toggleColumn: (column: ColumnHeaderOptions) => void; } +interface DraggableContainerProps { + children: React.ReactNode; + onMount: () => void; + onUnmount: () => void; +} + +export const DraggableContainer = React.memo( + ({ children, onMount, onUnmount }) => { + useEffect(() => { + onMount(); + + return () => onUnmount(); + }, [onMount, onUnmount]); + + return <>{children}; + } +); + +DraggableContainer.displayName = 'DraggableContainer'; + /** Renders the timeline header columns */ export const ColumnHeadersComponent = ({ actionsColumnWidth, @@ -71,86 +94,157 @@ export const ColumnHeadersComponent = ({ sort, timelineId, toggleColumn, -}: Props) => ( - - - - {showEventsSelect && ( - - - - - - )} - {showSelectAllCheckbox && ( +}: Props) => { + const [draggingIndex, setDraggingIndex] = useState(null); + + const handleSelectAllChange = useCallback( + (event: React.ChangeEvent) => { + onSelectAll({ isSelected: event.currentTarget.checked }); + }, + [onSelectAll] + ); + + const renderClone: DraggableChildrenFn = useCallback( + (dragProvided, dragSnapshot, rubric) => { + // TODO: Remove after github.com/DefinitelyTyped/DefinitelyTyped/pull/43057 is merged + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const index = (rubric as any).source.index; + const header = columnHeaders[index]; + + const onMount = () => setDraggingIndex(index); + const onUnmount = () => setDraggingIndex(null); + + return ( + + + + + + + + ); + }, + [columnHeaders, setDraggingIndex] + ); + + const ColumnHeaderList = useMemo( + () => + columnHeaders.map((header, draggableIndex) => ( + + )), + [ + columnHeaders, + timelineId, + draggingIndex, + onColumnRemoved, + onFilterChange, + onColumnResized, + sort, + ] + ); + + return ( + + + + {showEventsSelect && ( + + + + + + )} + {showSelectAllCheckbox && ( + + + + + + )} - - ) => { - onSelectAll({ isSelected: event.currentTarget.checked }); - }} + + - )} - - - - - - - - - {(dropProvided, snapshot) => ( - <> - - {columnHeaders.map((header, draggableIndex) => ( - - ))} - - {dropProvided.placeholder} - - )} - - - -); + -export const ColumnHeaders = React.memo(ColumnHeadersComponent); + + {(dropProvided, snapshot) => ( + <> + + {ColumnHeaderList} + + + )} + + + + ); +}; + +export const ColumnHeaders = React.memo( + ColumnHeadersComponent, + (prevProps, nextProps) => + prevProps.actionsColumnWidth === nextProps.actionsColumnWidth && + prevProps.isEventViewer === nextProps.isEventViewer && + prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && + prevProps.onColumnRemoved === nextProps.onColumnRemoved && + prevProps.onColumnResized === nextProps.onColumnResized && + prevProps.onColumnSorted === nextProps.onColumnSorted && + prevProps.onSelectAll === nextProps.onSelectAll && + prevProps.onUpdateColumns === nextProps.onUpdateColumns && + prevProps.onFilterChange === nextProps.onFilterChange && + prevProps.showEventsSelect === nextProps.showEventsSelect && + prevProps.showSelectAllCheckbox === nextProps.showSelectAllCheckbox && + prevProps.sort === nextProps.sort && + prevProps.timelineId === nextProps.timelineId && + prevProps.toggleColumn === nextProps.toggleColumn && + deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) && + deepEqual(prevProps.browserFields, nextProps.browserFields) +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 93e12a0ed4fcd..75623252181db 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -6,11 +6,7 @@ exports[`Columns it renders the expected columns 1`] = ` > ( - ({ _id, columnHeaders, columnRenderers, data, ecsData, timelineId }) => { - // 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 ( - - {columnHeaders.map((header, index) => ( - - - {getColumnRenderer(header.id, columnRenderers, data).renderColumn({ - columnName: header.id, - eventId: _id, - field: header, - linkValues: getOr([], header.linkField ?? '', ecsData), - timelineId, - truncate: true, - values: getMappedNonEcsValue({ - data, - fieldName: header.id, - }), - })} - - - ))} - - ); - } + ({ _id, columnHeaders, columnRenderers, data, ecsData, timelineId }) => ( + + {columnHeaders.map(header => ( + + + {getColumnRenderer(header.id, columnRenderers, data).renderColumn({ + columnName: header.id, + eventId: _id, + field: header, + linkValues: getOr([], header.linkField ?? '', ecsData), + timelineId, + truncate: true, + values: getMappedNonEcsValue({ + data, + fieldName: header.id, + }), + })} + + + ))} + + ) ); DataDrivenColumns.displayName = 'DataDrivenColumns'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx index 84c4253076dc9..4178bc656f32d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx @@ -51,9 +51,6 @@ interface Props { updateNote: UpdateNote; } -// 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 const EventsComponent: React.FC = ({ actionsColumnWidth, addNoteToEvent, @@ -93,7 +90,7 @@ const EventsComponent: React.FC = ({ getNotesByIds={getNotesByIds} isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })} isEventViewer={isEventViewer} - key={event._id} + key={`${event._id}_${event._index}`} loadingEventIds={loadingEventIds} maxDelay={maxDelay(i)} onColumnResized={onColumnResized} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx index 1f09ae4337c42..6e5c292064dc6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx @@ -25,13 +25,14 @@ import { } from '../../events'; import { ExpandableEvent } from '../../expandable_event'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; -import { EventsTrGroup, EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; -import { useTimelineWidthContext } from '../../timeline_context'; +import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles'; import { ColumnRenderer } from '../renderers/column_renderer'; import { getRowRenderer } from '../renderers/get_row_renderer'; import { RowRenderer } from '../renderers/row_renderer'; import { getEventType } from '../helpers'; -import { StatefulEventChild } from './stateful_event_child'; +import { NoteCards } from '../../../notes/note_cards'; +import { useEventDetailsWidthContext } from '../../../events_viewer/event_details_width_context'; +import { EventColumnView } from './event_column_view'; interface Props { actionsColumnWidth: number; @@ -89,28 +90,14 @@ const TOP_OFFSET = 50; */ const BOTTOM_OFFSET = -500; -interface AttributesProps { - children: React.ReactNode; -} - -const AttributesComponent: React.FC = ({ children }) => { - const width = useTimelineWidthContext(); +const emptyNotes: string[] = []; - // 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 ( - - {children} - - ); -}; +const EventsTrSupplementContainerWrapper = React.memo(({ children }) => { + const width = useEventDetailsWidthContext(); + return {children}; +}); -const Attributes = React.memo(AttributesComponent); +EventsTrSupplementContainerWrapper.displayName = 'EventsTrSupplementContainerWrapper'; const StatefulEventComponent: React.FC = ({ actionsColumnWidth, @@ -221,60 +208,75 @@ const StatefulEventComponent: React.FC = ({ data-test-subj="event" eventType={getEventType(event.ecs)} showLeftBorder={!isEventViewer} - ref={c => { - if (c != null) { - divElement.current = c; - } - }} + ref={divElement} > - {getRowRenderer(event.ecs, rowRenderers).renderRow({ - browserFields, - data: event.ecs, - children: ( - + + + + - ), - timelineId, - })} + - - - + {getRowRenderer(event.ecs, rowRenderers).renderRow({ + browserFields, + data: event.ecs, + timelineId, + })} + + + + + )} @@ -286,10 +288,7 @@ const StatefulEventComponent: React.FC = ({ ? `${divElement.current.clientHeight}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 ; + return ; } }} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx deleted file mode 100644 index 04f4ddf2a6eab..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx +++ /dev/null @@ -1,138 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import uuid from 'uuid'; - -import { TimelineNonEcsData, Ecs } from '../../../../graphql/types'; -import { Note } from '../../../../lib/note'; -import { ColumnHeaderOptions } from '../../../../store/timeline/model'; -import { AddNoteToEvent, UpdateNote } from '../../../notes/helpers'; -import { NoteCards } from '../../../notes/note_cards'; -import { OnPinEvent, OnColumnResized, OnUnPinEvent, OnRowSelected } from '../../events'; -import { EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; -import { useTimelineWidthContext } from '../../timeline_context'; -import { ColumnRenderer } from '../renderers/column_renderer'; -import { EventColumnView } from './event_column_view'; - -interface Props { - id: string; - actionsColumnWidth: number; - addNoteToEvent: AddNoteToEvent; - onPinEvent: OnPinEvent; - columnHeaders: ColumnHeaderOptions[]; - columnRenderers: ColumnRenderer[]; - data: TimelineNonEcsData[]; - ecsData: Ecs; - expanded: boolean; - eventIdToNoteIds: Readonly>; - isEventViewer?: boolean; - isEventPinned: boolean; - loading: boolean; - loadingEventIds: Readonly; - onColumnResized: OnColumnResized; - onRowSelected: OnRowSelected; - onUnPinEvent: OnUnPinEvent; - selectedEventIds: Readonly>; - showCheckboxes: boolean; - showNotes: boolean; - timelineId: string; - updateNote: UpdateNote; - onToggleExpanded: () => void; - onToggleShowNotes: () => void; - getNotesByIds: (noteIds: string[]) => Note[]; - associateNote: (noteId: string) => void; -} - -export const getNewNoteId = (): string => uuid.v4(); - -const emptyNotes: string[] = []; - -export const StatefulEventChild = React.memo( - ({ - id, - actionsColumnWidth, - associateNote, - addNoteToEvent, - onPinEvent, - columnHeaders, - columnRenderers, - expanded, - data, - ecsData, - eventIdToNoteIds, - getNotesByIds, - isEventViewer = false, - isEventPinned = false, - loading, - loadingEventIds, - onColumnResized, - onRowSelected, - onToggleExpanded, - onUnPinEvent, - selectedEventIds, - showCheckboxes, - showNotes, - timelineId, - onToggleShowNotes, - updateNote, - }) => { - const width = useTimelineWidthContext(); - - // 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 ( - <> - - - - - - - ); - } -); -StatefulEventChild.displayName = 'StatefulEventChild'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx index ea80d3351408a..fac8cc61cddd2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx @@ -38,7 +38,7 @@ export interface BodyProps { columnRenderers: ColumnRenderer[]; data: TimelineItem[]; getNotesByIds: (noteIds: string[]) => Note[]; - height: number; + height?: number; id: string; isEventViewer?: boolean; isSelectAllChecked: boolean; @@ -96,9 +96,10 @@ export const Body = React.memo( }) => { const containerElementRef = useRef(null); const timelineTypeContext = useTimelineTypeContext(); - const additionalActionWidth = - timelineTypeContext.timelineActions?.reduce((acc, v) => acc + v.width, 0) ?? 0; - + const additionalActionWidth = useMemo( + () => timelineTypeContext.timelineActions?.reduce((acc, v) => acc + v.width, 0) ?? 0, + [timelineTypeContext.timelineActions] + ); const actionsColumnWidth = useMemo( () => getActionsColumnWidth(isEventViewer, showCheckboxes, additionalActionWidth), [isEventViewer, showCheckboxes, additionalActionWidth] @@ -113,11 +114,7 @@ export const Body = React.memo( return ( <> - + - - some child - - -`; +exports[`get_column_renderer renders correctly against snapshot 1`] = ``; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_row_renderer.test.tsx.snap index 5731921907fc8..66a1b293cf8b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_row_renderer.test.tsx.snap @@ -1,9 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`plain_row_renderer renders correctly against snapshot 1`] = ` - - - some children - - -`; +exports[`plain_row_renderer renders correctly against snapshot 1`] = ``; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap index 0b2a1b2f2a0ae..b24a90589ce65 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap @@ -2,9 +2,6 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly against snapshot 1`] = ` - - some children - - - some children - { const children = connectedToRenderer.renderRow({ browserFields, data: auditd, - children: {'some children'}, timelineId: 'test', }); @@ -66,26 +65,10 @@ describe('GenericRowRenderer', () => { } }); - test('should render children normally if it does not have a auditd object', () => { - const children = connectedToRenderer.renderRow({ - browserFields: mockBrowserFields, - data: nonAuditd, - children: {'some children'}, - timelineId: 'test', - }); - const wrapper = mount( - - {children} - - ); - expect(wrapper.text()).toEqual('some children'); - }); - test('should render a auditd row', () => { const children = connectedToRenderer.renderRow({ browserFields: mockBrowserFields, data: auditd, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -94,7 +77,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Session246alice@zeek-londonsome textwget(1490)wget www.example.comwith resultsuccessDestination93.184.216.34:80' + 'Session246alice@zeek-londonsome textwget(1490)wget www.example.comwith resultsuccessDestination93.184.216.34:80' ); }); }); @@ -119,7 +102,6 @@ describe('GenericRowRenderer', () => { const children = fileToRenderer.renderRow({ browserFields, data: auditdFile, - children: {'some children'}, timelineId: 'test', }); @@ -145,26 +127,10 @@ describe('GenericRowRenderer', () => { } }); - test('should render children normally if it does not have a auditd object', () => { - const children = fileToRenderer.renderRow({ - browserFields: mockBrowserFields, - data: nonAuditd, - children: {'some children'}, - timelineId: 'test', - }); - const wrapper = mount( - - {children} - - ); - expect(wrapper.text()).toEqual('some children'); - }); - test('should render a auditd row', () => { const children = fileToRenderer.renderRow({ browserFields: mockBrowserFields, data: auditdFile, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -173,7 +139,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess' + 'Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx index bcf464ab6da15..4ed4ae10ed810 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx @@ -32,19 +32,16 @@ export const createGenericAuditRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -67,20 +64,17 @@ export const createGenericFileRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx index f367769b78f40..7ad8cfed5256b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -38,7 +38,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, - children: {'some child'}, timelineId: 'test', }); @@ -51,7 +50,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, - children: {'some child'}, timelineId: 'test', }); const wrapper = mount( @@ -59,7 +57,7 @@ describe('get_column_renderer', () => { {row} ); - expect(wrapper.text()).toContain('some child'); + expect(wrapper.text()).toEqual(''); }); test('should render a suricata row data when it is a suricata row', () => { @@ -67,7 +65,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: suricata, - children: {'some child '}, timelineId: 'test', }); const wrapper = mount( @@ -76,7 +73,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' + '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); @@ -86,7 +83,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: suricata, - children: {'some child '}, timelineId: 'test', }); const wrapper = mount( @@ -95,7 +91,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' + '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); @@ -105,7 +101,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: zeek, - children: {'some child '}, timelineId: 'test', }); const wrapper = mount( @@ -114,7 +109,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' + 'C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' ); }); @@ -124,7 +119,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: system, - children: {'some child '}, timelineId: 'test', }); const wrapper = mount( @@ -133,7 +127,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child Braden@zeek-londonattempted a login via(6278)with resultfailureSource128.199.212.120' + 'Braden@zeek-londonattempted a login via(6278)with resultfailureSource128.199.212.120' ); }); @@ -143,7 +137,6 @@ describe('get_column_renderer', () => { const row = rowRenderer.renderRow({ browserFields: mockBrowserFields, data: auditd, - children: {'some child '}, timelineId: 'test', }); const wrapper = mount( @@ -152,7 +145,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child Sessionalice@zeek-sanfranin/executedgpgconf(5402)gpgconf--list-dirsagent-socketgpgconf --list-dirs agent-socket' + 'Sessionalice@zeek-sanfranin/executedgpgconf(5402)gpgconf--list-dirsagent-socketgpgconf --list-dirs agent-socket' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap index 4326b7372604d..d7bdacbcc61ef 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap @@ -2,9 +2,6 @@ exports[`netflowRowRenderer renders correctly against snapshot 1`] = ` - - some children -
{ const children = netflowRowRenderer.renderRow({ browserFields, data: getMockNetflowData(), - children: {'some children'}, timelineId: 'test', }); @@ -98,26 +97,10 @@ describe('netflowRowRenderer', () => { }); }); - test('should render children normally when given non-netflow data', () => { - const children = netflowRowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: justIdAndTimestamp, - children: {'some children'}, - timelineId: 'test', - }); - const wrapper = mount( - - {children} - - ); - expect(wrapper.text()).toEqual('some children'); - }); - test('should render netflow data', () => { const children = netflowRowRenderer.renderRow({ browserFields: mockBrowserFields, data: getMockNetflowData(), - children: {'some children'}, timelineId: 'test', }); const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx index 754d6ad99b7fe..10d80e1952f40 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx @@ -78,73 +78,63 @@ export const eventActionMatches = (eventAction: string | object | undefined | nu }; export const netflowRowRenderer: RowRenderer = { - isInstance: ecs => { - return ( - eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) || - eventActionMatches(get(EVENT_ACTION_FIELD, ecs)) - ); - }, - renderRow: ({ data, children, timelineId }) => ( - <> - {children} - -
- -
-
- + isInstance: ecs => + eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) || + eventActionMatches(get(EVENT_ACTION_FIELD, ecs)), + renderRow: ({ data, timelineId }) => ( + +
+ +
+
), }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx index 50ea7ca05b921..467f507e8be7d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx @@ -25,7 +25,6 @@ describe('plain_row_renderer', () => { const children = plainRowRenderer.renderRow({ browserFields: mockBrowserFields, data: mockDatum, - children: {'some children'}, timelineId: 'test', }); const wrapper = shallow({children}); @@ -40,7 +39,6 @@ describe('plain_row_renderer', () => { const children = plainRowRenderer.renderRow({ browserFields: mockBrowserFields, data: mockDatum, - children: {'some children'}, timelineId: 'test', }); const wrapper = mount( @@ -48,6 +46,6 @@ describe('plain_row_renderer', () => { {children} ); - expect(wrapper.text()).toEqual('some children'); + expect(wrapper.text()).toEqual(''); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx index 6725830c97d0a..da78f41f09ed4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx @@ -12,5 +12,5 @@ import { RowRenderer } from './row_renderer'; export const plainRowRenderer: RowRenderer = { isInstance: _ => true, - renderRow: ({ children }) => <>{children}, + renderRow: () => <>, }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/row_renderer.tsx index df92fc1e9f634..2d9f877fe4af0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/row_renderer.tsx @@ -8,28 +8,17 @@ import React from 'react'; import { BrowserFields } from '../../../../containers/source'; import { Ecs } from '../../../../graphql/types'; -import { EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; -import { useTimelineWidthContext } from '../../timeline_context'; +import { EventsTrSupplement } from '../../styles'; interface RowRendererContainerProps { children: React.ReactNode; } -export const RowRendererContainer = React.memo(({ children }) => { - const width = useTimelineWidthContext(); - - // 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 ( - - {children} - - ); -}); +export const RowRendererContainer = React.memo(({ children }) => ( + + {children} + +)); RowRendererContainer.displayName = 'RowRendererContainer'; export interface RowRenderer { @@ -37,12 +26,10 @@ export interface RowRenderer { renderRow: ({ browserFields, data, - children, timelineId, }: { browserFields: BrowserFields; data: Ecs; - children: React.ReactNode; timelineId: string; }) => React.ReactNode; } diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap index 3608a81234677..93b3046b57ed6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap @@ -2,9 +2,6 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = ` - - some children - { const children = suricataRowRenderer.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, - children: {'some children'}, timelineId: 'test', }); @@ -45,26 +44,10 @@ describe('suricata_row_renderer', () => { expect(suricataRowRenderer.isInstance(suricata)).toBe(true); }); - test('should render children normally if it does not have a signature', () => { - const children = suricataRowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: nonSuricata, - children: {'some children'}, - timelineId: 'test', - }); - const wrapper = mount( - - {children} - - ); - expect(wrapper.text()).toEqual('some children'); - }); - test('should render a suricata row', () => { const children = suricataRowRenderer.renderRow({ browserFields: mockBrowserFields, data: suricata, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -73,7 +56,7 @@ describe('suricata_row_renderer', () => { ); expect(wrapper.text()).toContain( - 'some children 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' + '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); @@ -82,7 +65,6 @@ describe('suricata_row_renderer', () => { const children = suricataRowRenderer.renderRow({ browserFields: mockBrowserFields, data: suricata, - children: {'some children'}, timelineId: 'test', }); const wrapper = mount( @@ -90,6 +72,6 @@ describe('suricata_row_renderer', () => { {children} ); - expect(wrapper.text()).toEqual('some children'); + expect(wrapper.text()).toEqual(''); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx index b227326551e01..e49a5f65b47c1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx @@ -17,15 +17,9 @@ export const suricataRowRenderer: RowRenderer = { const module: string | null | undefined = get('event.module[0]', ecs); return module != null && module.toLowerCase() === 'suricata'; }, - renderRow: ({ browserFields, data, children, timelineId }) => { - return ( - <> - {children} - - - - - - ); - }, + renderRow: ({ browserFields, data, timelineId }) => ( + + + + ), }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx index 2b9adfe21b120..9ccd1fb7a0519 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; @@ -28,11 +28,9 @@ const SignatureFlexItem = styled(EuiFlexItem)` SignatureFlexItem.displayName = 'SignatureFlexItem'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const Badge = styled(EuiBadge)` -// vertical-align: top; -// `; -const Badge = (props: EuiBadgeProps) => ; +const Badge = styled(EuiBadge)` + vertical-align: top; +`; Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap index 9ed6587145584..6fff32925abf3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap @@ -2,9 +2,6 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly against snapshot 1`] = ` - - some children - - - some children - { const children = connectedToRenderer.renderRow({ browserFields, data: system, - children: {'some children'}, timelineId: 'test', }); @@ -99,7 +98,6 @@ describe('GenericRowRenderer', () => { const children = connectedToRenderer.renderRow({ browserFields: mockBrowserFields, data: system, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -108,7 +106,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Evan@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' + 'Evan@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' ); }); }); @@ -133,7 +131,6 @@ describe('GenericRowRenderer', () => { const children = fileToRenderer.renderRow({ browserFields, data: systemFile, - children: {'some children'}, timelineId: 'test', }); @@ -162,7 +159,6 @@ describe('GenericRowRenderer', () => { const children = fileToRenderer.renderRow({ browserFields: mockBrowserFields, data: systemFile, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -171,7 +167,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Braden@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' + 'Braden@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' ); }); }); @@ -195,14 +191,13 @@ describe('GenericRowRenderer', () => { endgameProcessCreationEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameCreationEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee12563599116157778a22600d2a163d8112aed84562d06d7235b37895b68de56687895743' + 'Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee12563599116157778a22600d2a163d8112aed84562d06d7235b37895b68de56687895743' ); }); @@ -224,14 +219,13 @@ describe('GenericRowRenderer', () => { endgameProcessTerminationEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameTerminationEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776797255e72d5ed5c058d4785950eba7abaa057653bd4401441a21bf1abce6404f4231db4d' + 'Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776797255e72d5ed5c058d4785950eba7abaa057653bd4401441a21bf1abce6404f4231db4d' ); }); @@ -253,7 +247,6 @@ describe('GenericRowRenderer', () => { endgameProcessCreationEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameCreationEvent, - children: {'some children '}, timelineId: 'test', })} @@ -284,7 +277,6 @@ describe('GenericRowRenderer', () => { endgameProcessCreationEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameCreationEvent, - children: {'some children '}, timelineId: 'test', })} @@ -315,7 +307,6 @@ describe('GenericRowRenderer', () => { endgameProcessCreationEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameCreationEvent, - children: {'some children '}, timelineId: 'test', })} @@ -344,14 +335,13 @@ describe('GenericRowRenderer', () => { endgameFileCreateEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameFileCreateEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children Arun\\Anvi-Acer@HD-obe-8bf77f54created a fileinC:\\Users\\Arun\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\63d78c21-e593-4484-b7a9-db33cd522ddc.tmpviachrome.exe(11620)' + 'Arun\\Anvi-Acer@HD-obe-8bf77f54created a fileinC:\\Users\\Arun\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\63d78c21-e593-4484-b7a9-db33cd522ddc.tmpviachrome.exe(11620)' ); }); @@ -373,14 +363,13 @@ describe('GenericRowRenderer', () => { endgameFileDeleteEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameFileDeleteEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-v1s-d2118419deleted a filetmp000002f6inC:\\Windows\\TEMP\\tmp00000404\\tmp000002f6viaAmSvc.exe(1084)' + 'SYSTEM\\NT AUTHORITY@HD-v1s-d2118419deleted a filetmp000002f6inC:\\Windows\\TEMP\\tmp00000404\\tmp000002f6viaAmSvc.exe(1084)' ); }); @@ -402,15 +391,12 @@ describe('GenericRowRenderer', () => { fileCreatedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: fimFileCreatedEvent, - children: {'some children '}, timelineId: 'test', })} ); - expect(wrapper.text()).toEqual( - 'some children foohostcreated a filein/etc/subgidviaan unknown process' - ); + expect(wrapper.text()).toEqual('foohostcreated a filein/etc/subgidviaan unknown process'); }); test('it renders a FIM (non-endgame) file deleted event', () => { @@ -431,14 +417,13 @@ describe('GenericRowRenderer', () => { fileDeletedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: fimFileDeletedEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children foohostdeleted a filein/etc/gshadow.lockviaan unknown process' + 'foohostdeleted a filein/etc/gshadow.lockviaan unknown process' ); }); @@ -460,7 +445,6 @@ describe('GenericRowRenderer', () => { endgameFileCreateEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameFileCreateEvent, - children: {'some children '}, timelineId: 'test', })} @@ -491,7 +475,6 @@ describe('GenericRowRenderer', () => { endgameFileCreateEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: endgameFileCreateEvent, - children: {'some children '}, timelineId: 'test', })} @@ -522,7 +505,6 @@ describe('GenericRowRenderer', () => { fileCreatedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: fimFileCreatedEvent, - children: {'some children '}, timelineId: 'test', })} @@ -551,14 +533,13 @@ describe('GenericRowRenderer', () => { endgameIpv4ConnectionAcceptEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: ipv4ConnectionAcceptEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-gqf-0af7b4feaccepted a connection viaAmSvc.exe(1084)tcp1:network-community_idSource127.0.0.1:49306Destination127.0.0.1:49305' + 'SYSTEM\\NT AUTHORITY@HD-gqf-0af7b4feaccepted a connection viaAmSvc.exe(1084)tcp1:network-community_idSource127.0.0.1:49306Destination127.0.0.1:49305' ); }); @@ -580,14 +561,13 @@ describe('GenericRowRenderer', () => { endgameIpv6ConnectionAcceptEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: ipv6ConnectionAcceptEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66accepted a connection via(4)tcp1:network-community_idSource::1:51324Destination::1:5357' + 'SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66accepted a connection via(4)tcp1:network-community_idSource::1:51324Destination::1:5357' ); }); @@ -609,14 +589,13 @@ describe('GenericRowRenderer', () => { endgameIpv4DisconnectReceivedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: ipv4DisconnectReceivedEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children Arun\\Anvi-Acer@HD-obe-8bf77f54disconnected viachrome.exe(11620)8.1KBtcp1:LxYHJJv98b2O0fNccXu6HheXmwk=Source192.168.0.6:59356(25.78%)2.1KB(74.22%)6KBDestination10.156.162.53:443' + 'Arun\\Anvi-Acer@HD-obe-8bf77f54disconnected viachrome.exe(11620)8.1KBtcp1:LxYHJJv98b2O0fNccXu6HheXmwk=Source192.168.0.6:59356(25.78%)2.1KB(74.22%)6KBDestination10.156.162.53:443' ); }); @@ -638,14 +617,13 @@ describe('GenericRowRenderer', () => { endgameIpv6DisconnectReceivedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: ipv6DisconnectReceivedEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66disconnected via(4)7.9KBtcp1:ZylzQhsB1dcptA2t4DY8S6l9o8E=Source::1:51338(96.92%)7.7KB(3.08%)249BDestination::1:2869' + 'SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66disconnected via(4)7.9KBtcp1:ZylzQhsB1dcptA2t4DY8S6l9o8E=Source::1:51338(96.92%)7.7KB(3.08%)249BDestination::1:2869' ); }); @@ -667,14 +645,13 @@ describe('GenericRowRenderer', () => { socketOpenedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: socketOpenedEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children root@foohostopened a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59554 -> 10.1.2.3:80) Ooutboundtcp1:network-community_idSource10.4.20.1:59554Destination10.1.2.3:80' + 'root@foohostopened a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59554 -> 10.1.2.3:80) Ooutboundtcp1:network-community_idSource10.4.20.1:59554Destination10.1.2.3:80' ); }); @@ -696,14 +673,13 @@ describe('GenericRowRenderer', () => { socketClosedEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: socketClosedEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children root@foohostclosed a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59508 -> 10.1.2.3:80) Coutboundtcp1:network-community_idSource10.4.20.1:59508Destination10.1.2.3:80' + 'root@foohostclosed a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59508 -> 10.1.2.3:80) Coutboundtcp1:network-community_idSource10.4.20.1:59508Destination10.1.2.3:80' ); }); @@ -725,7 +701,6 @@ describe('GenericRowRenderer', () => { endgameIpv4ConnectionAcceptEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: ipv4ConnectionAcceptEvent, - children: {'some children '}, timelineId: 'test', })} @@ -750,14 +725,13 @@ describe('GenericRowRenderer', () => { userLogonEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: userLogonEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-v1s-d2118419successfully logged inusing logon type5 - Service(target logon ID0x3e7)viaC:\\Windows\\System32\\services.exe(432)as requested by subjectWIN-Q3DOP1UKA81$(subject logon ID0x3e7)4624' + 'SYSTEM\\NT AUTHORITY@HD-v1s-d2118419successfully logged inusing logon type5 - Service(target logon ID0x3e7)viaC:\\Windows\\System32\\services.exe(432)as requested by subjectWIN-Q3DOP1UKA81$(subject logon ID0x3e7)4624' ); }); @@ -775,14 +749,13 @@ describe('GenericRowRenderer', () => { adminLogonEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: adminLogonEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children With special privileges,SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54successfully logged inviaC:\\Windows\\System32\\lsass.exe(964)as requested by subjectSYSTEM\\NT AUTHORITY4672' + 'With special privileges,SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54successfully logged inviaC:\\Windows\\System32\\lsass.exe(964)as requested by subjectSYSTEM\\NT AUTHORITY4672' ); }); @@ -800,14 +773,13 @@ describe('GenericRowRenderer', () => { explicitUserLogonEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: explicitUserLogonEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children A login was attempted using explicit credentialsArun\\Anvi-AcertoHD-55b-3ec87f66viaC:\\Windows\\System32\\svchost.exe(1736)as requested by subjectANVI-ACER$\\WORKGROUP(subject logon ID0x3e7)4648' + 'A login was attempted using explicit credentialsArun\\Anvi-AcertoHD-55b-3ec87f66viaC:\\Windows\\System32\\svchost.exe(1736)as requested by subjectANVI-ACER$\\WORKGROUP(subject logon ID0x3e7)4648' ); }); @@ -825,14 +797,13 @@ describe('GenericRowRenderer', () => { userLogoffEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: userLogoffEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children Arun\\Anvi-Acer@HD-55b-3ec87f66logged offusing logon type2 - Interactive(target logon ID0x16db41e)viaC:\\Windows\\System32\\lsass.exe(964)4634' + 'Arun\\Anvi-Acer@HD-55b-3ec87f66logged offusing logon type2 - Interactive(target logon ID0x16db41e)viaC:\\Windows\\System32\\lsass.exe(964)4634' ); }); @@ -850,7 +821,6 @@ describe('GenericRowRenderer', () => { userLogonEventRowRenderer.renderRow({ browserFields: mockBrowserFields, data: userLogonEvent, - children: {'some children '}, timelineId: 'test', })} @@ -874,14 +844,13 @@ describe('GenericRowRenderer', () => { dnsRowRenderer.renderRow({ browserFields: mockBrowserFields, data: requestEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54asked forupdate.googleapis.comwith question typeA, which resolved to10.100.197.67viaGoogleUpdate.exe(443192)3008dns' + 'SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54asked forupdate.googleapis.comwith question typeA, which resolved to10.100.197.67viaGoogleUpdate.exe(443192)3008dns' ); }); @@ -898,14 +867,13 @@ describe('GenericRowRenderer', () => { dnsRowRenderer.renderRow({ browserFields: mockBrowserFields, data: dnsEvent, - children: {'some children '}, timelineId: 'test', })} ); expect(wrapper.text()).toEqual( - 'some children iot.example.comasked forlookup.example.comwith question typeA, which resolved to10.1.2.3(response code:NOERROR)viaan unknown process6.937500msOct 8, 2019 @ 10:05:23.241Oct 8, 2019 @ 10:05:23.248outbounddns177Budp1:network-community_idSource10.9.9.9:58732(22.60%)40B(77.40%)137BDestination10.1.1.1:53OceaniaAustralia🇦🇺AU' + 'iot.example.comasked forlookup.example.comwith question typeA, which resolved to10.1.2.3(response code:NOERROR)viaan unknown process6.937500msOct 8, 2019 @ 10:05:23.241Oct 8, 2019 @ 10:05:23.248outbounddns177Budp1:network-community_idSource10.9.9.9:58732(22.60%)40B(77.40%)137BDestination10.1.1.1:53OceaniaAustralia🇦🇺AU' ); }); @@ -928,7 +896,6 @@ describe('GenericRowRenderer', () => { dnsRowRenderer.renderRow({ browserFields: mockBrowserFields, data: requestEvent, - children: {'some children '}, timelineId: 'test', })} @@ -956,7 +923,6 @@ describe('GenericRowRenderer', () => { dnsRowRenderer.renderRow({ browserFields: mockBrowserFields, data: requestEvent, - children: {'some children '}, timelineId: 'test', })} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx index 3e64248d39876..523d4f3a0cfb8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx @@ -35,19 +35,16 @@ export const createGenericSystemRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -68,20 +65,17 @@ export const createEndgameProcessRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -102,20 +96,17 @@ export const createFimRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -136,19 +127,16 @@ export const createGenericFileRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -163,19 +151,16 @@ export const createSocketRowRenderer = ({ const action: string | null | undefined = get('event.action[0]', ecs); return action != null && action.toLowerCase() === actionName; }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -194,18 +179,15 @@ export const createSecurityEventRowRenderer = ({ action.toLowerCase() === actionName ); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); @@ -215,18 +197,15 @@ export const createDnsRowRenderer = (): RowRenderer => ({ const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs); return !isNillEmptyOrNotFinite(dnsQuestionType) && !isNillEmptyOrNotFinite(dnsQuestionName); }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap index 9b59f69cad3a3..460ad35b47678 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap @@ -2,9 +2,6 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = ` - - some children - { const children = zeekRowRenderer.renderRow({ browserFields: mockBrowserFields, data: nonZeek, - children: {'some children'}, timelineId: 'test', }); @@ -44,26 +43,10 @@ describe('zeek_row_renderer', () => { expect(zeekRowRenderer.isInstance(zeek)).toBe(true); }); - test('should render children normally if it does not have a zeek object', () => { - const children = zeekRowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: nonZeek, - children: {'some children'}, - timelineId: 'test', - }); - const wrapper = mount( - - {children} - - ); - expect(wrapper.text()).toEqual('some children'); - }); - test('should render a zeek row', () => { const children = zeekRowRenderer.renderRow({ browserFields: mockBrowserFields, data: zeek, - children: {'some children '}, timelineId: 'test', }); const wrapper = mount( @@ -72,7 +55,7 @@ describe('zeek_row_renderer', () => { ); expect(wrapper.text()).toContain( - 'some children C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' + 'C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx index fc528e33b5ab6..0fca5cdd8b3d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx @@ -17,12 +17,9 @@ export const zeekRowRenderer: RowRenderer = { const module: string | null | undefined = get('event.module[0]', ecs); return module != null && module.toLowerCase() === 'zeek'; }, - renderRow: ({ browserFields, data, children, timelineId }) => ( - <> - {children} - - - - + renderRow: ({ browserFields, data, timelineId }) => ( + + + ), }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 57e5ff19eb815..f13a236e8ec36 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { get } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; @@ -19,11 +19,9 @@ import { IS_OPERATOR } from '../../../data_providers/data_provider'; import * as i18n from './translations'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const Badge = styled(EuiBadge)` -// vertical-align: top; -// `; -const Badge = (props: EuiBadgeProps) => ; +const Badge = styled(EuiBadge)` + vertical-align: top; +`; Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx index d06dcbb84ad78..76f26d3dda5af 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx @@ -8,6 +8,7 @@ import { noop } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import React, { useCallback, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { BrowserFields } from '../../../containers/source'; import { TimelineItem } from '../../../graphql/types'; @@ -38,9 +39,9 @@ import { plainRowRenderer } from './renderers/plain_row_renderer'; interface OwnProps { browserFields: BrowserFields; data: TimelineItem[]; + height?: number; id: string; isEventViewer?: boolean; - height: number; sort: Sort; toggleColumn: (column: ColumnHeaderOptions) => void; } @@ -101,7 +102,7 @@ const StatefulBodyComponent = React.memo( isSelected && Object.keys(selectedEventIds).length + 1 === data.length, }); }, - [id, data, selectedEventIds, timelineTypeContext.queryFields] + [setSelected, id, data, selectedEventIds, timelineTypeContext.queryFields] ); const onSelectAll: OnSelectAll = useCallback( @@ -118,7 +119,7 @@ const StatefulBodyComponent = React.memo( isSelectAllChecked: isSelected, }) : clearSelected!({ id }), - [id, data, timelineTypeContext.queryFields] + [setSelected, clearSelected, id, data, timelineTypeContext.queryFields] ); const onColumnSorted: OnColumnSorted = useCallback( @@ -189,25 +190,22 @@ const StatefulBodyComponent = React.memo( /> ); }, - (prevProps, nextProps) => { - return ( - prevProps.browserFields === nextProps.browserFields && - prevProps.columnHeaders === nextProps.columnHeaders && - prevProps.data === nextProps.data && - prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && - prevProps.notesById === nextProps.notesById && - prevProps.height === nextProps.height && - prevProps.id === nextProps.id && - prevProps.isEventViewer === nextProps.isEventViewer && - prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && - prevProps.loadingEventIds === nextProps.loadingEventIds && - prevProps.pinnedEventIds === nextProps.pinnedEventIds && - prevProps.selectedEventIds === nextProps.selectedEventIds && - prevProps.showCheckboxes === nextProps.showCheckboxes && - prevProps.showRowRenderers === nextProps.showRowRenderers && - prevProps.sort === nextProps.sort - ); - } + (prevProps, nextProps) => + deepEqual(prevProps.browserFields, nextProps.browserFields) && + deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) && + deepEqual(prevProps.data, nextProps.data) && + prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && + deepEqual(prevProps.notesById, nextProps.notesById) && + prevProps.height === nextProps.height && + prevProps.id === nextProps.id && + prevProps.isEventViewer === nextProps.isEventViewer && + prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && + prevProps.loadingEventIds === nextProps.loadingEventIds && + prevProps.pinnedEventIds === nextProps.pinnedEventIds && + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.showRowRenderers === nextProps.showRowRenderers && + prevProps.sort === nextProps.sort ); StatefulBodyComponent.displayName = 'StatefulBodyComponent'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index a47fb932ed26c..56639f90c1464 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiBadgeProps, EuiText } from '@elastic/eui'; +import { EuiBadge, EuiText } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; @@ -21,24 +21,12 @@ const Text = styled(EuiText)` Text.displayName = 'Text'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const BadgeHighlighted = styled(EuiBadge)` -// height: 20px; -// margin: 0 5px 0 5px; -// max-width: 70px; -// min-width: 70px; -// `; -const BadgeHighlighted = (props: EuiBadgeProps) => ( - -); +const BadgeHighlighted = styled(EuiBadge)` + height: 20px; + margin: 0 5px 0 5px; + maxwidth: 85px; + minwidth: 85px; +`; BadgeHighlighted.displayName = 'BadgeHighlighted'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx index 1a1e8292b7e02..663b3dd501341 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { rgba } from 'polished'; -import React from 'react'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; @@ -54,13 +54,9 @@ const DropAndTargetDataProviders = styled.div<{ hasAndItem: boolean }>` DropAndTargetDataProviders.displayName = 'DropAndTargetDataProviders'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const NumberProviderAndBadge = styled(EuiBadge)` -// margin: 0px 5px; -// `; -const NumberProviderAndBadge = (props: EuiBadgeProps) => ( - -); +const NumberProviderAndBadge = styled(EuiBadge)` + margin: 0px 5px; +`; NumberProviderAndBadge.displayName = 'NumberProviderAndBadge'; @@ -89,8 +85,13 @@ export const ProviderItemAndDragDrop = React.memo( onToggleDataProviderExcluded, timelineId, }) => { - const onMouseEnter = () => onChangeDroppableAndProvider(dataProvider.id); - const onMouseLeave = () => onChangeDroppableAndProvider(''); + const onMouseEnter = useCallback(() => onChangeDroppableAndProvider(dataProvider.id), [ + onChangeDroppableAndProvider, + dataProvider.id, + ]); + const onMouseLeave = useCallback(() => onChangeDroppableAndProvider(''), [ + onChangeDroppableAndProvider, + ]); const hasAndItem = dataProvider.and.length > 0; return ( ` ${({ hideExpandButton }) => @@ -50,33 +49,26 @@ export const ExpandableEvent = React.memo( timelineId, toggleColumn, onUpdateColumns, - }) => { - const width = useTimelineWidthContext(); - // 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 ( - - ( - - )} - forceExpand={forceExpand} - paddingSize="none" - /> - - ); - } + }) => ( + + ( + + )} + forceExpand={forceExpand} + paddingSize="none" + /> + + ) ); ExpandableEvent.displayName = 'ExpandableEvent'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx index 65c539d77a16b..16eaa80308205 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx @@ -6,6 +6,7 @@ import { memo, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { IIndexPattern } from 'src/plugins/data/public'; import { timelineSelectors, State } from '../../store'; @@ -39,7 +40,14 @@ const TimelineKqlFetchComponent = memo( }); }, [kueryFilterQueryDraft, kueryFilterQuery, id]); return null; - } + }, + (prevProps, nextProps) => + prevProps.id === nextProps.id && + prevProps.inputId === nextProps.inputId && + prevProps.setTimelineQuery === nextProps.setTimelineQuery && + deepEqual(prevProps.kueryFilterQuery, nextProps.kueryFilterQuery) && + deepEqual(prevProps.kueryFilterQueryDraft, nextProps.kueryFilterQueryDraft) && + deepEqual(prevProps.indexPattern, nextProps.indexPattern) ); const makeMapStateToProps = () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/footer/__snapshots__/index.test.tsx.snap index 9cdbda757d97e..eff487ceb7981 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/__snapshots__/index.test.tsx.snap @@ -1,94 +1,86 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Footer Timeline Component rendering it renders the default timeline footer 1`] = ` - - + - - - - - 1 rows - , - - 5 rows - , - - 10 rows - , - - 20 rows - , - ] - } - itemsCount={2} - onClick={[Function]} - serverSideEventCount={15546} - /> - - - - + 1 rows + , + + 5 rows + , + + 10 rows + , + + 20 rows + , + ] + } + itemsCount={2} + onClick={[Function]} + serverSideEventCount={15546} /> - - - - - - - - - + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index cbad2d42cf8af..d54a4cee83e52 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx @@ -17,7 +17,6 @@ describe('Footer Timeline Component', () => { const loadMore = jest.fn(); const onChangeItemsPerPage = jest.fn(); const getUpdatedAt = () => 1546878704036; - const compact = true; describe('rendering', () => { test('it renders the default timeline footer', () => { @@ -36,7 +35,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -59,7 +57,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -83,7 +80,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -140,7 +136,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -164,7 +159,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -195,7 +189,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -225,7 +218,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -259,7 +251,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); @@ -285,7 +276,6 @@ describe('Footer Timeline Component', () => { nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!} tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)} getUpdatedAt={getUpdatedAt} - compact={compact} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx index 1fcc4382c1798..7a025e96e57f2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx @@ -19,7 +19,7 @@ import { EuiPopoverProps, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; import styled from 'styled-components'; import { LoadingPanel } from '../../loading'; @@ -28,8 +28,30 @@ import { OnChangeItemsPerPage, OnLoadMore } from '../events'; import { LastUpdatedAt } from './last_updated'; import * as i18n from './translations'; import { useTimelineTypeContext } from '../timeline_context'; +import { useEventDetailsWidthContext } from '../../events_viewer/event_details_width_context'; -const FixedWidthLastUpdated = styled.div<{ compact: boolean }>` +export const isCompactFooter = (width: number): boolean => width < 600; + +interface FixedWidthLastUpdatedContainerProps { + updatedAt: number; +} + +const FixedWidthLastUpdatedContainer = React.memo( + ({ updatedAt }) => { + const width = useEventDetailsWidthContext(); + const compact = useMemo(() => isCompactFooter(width), [width]); + + return ( + + + + ); + } +); + +FixedWidthLastUpdatedContainer.displayName = 'FixedWidthLastUpdatedContainer'; + +const FixedWidthLastUpdated = styled.div<{ compact?: boolean }>` width: ${({ compact }) => (!compact ? 200 : 25)}px; overflow: hidden; text-align: end; @@ -37,8 +59,16 @@ const FixedWidthLastUpdated = styled.div<{ compact: boolean }>` FixedWidthLastUpdated.displayName = 'FixedWidthLastUpdated'; -const FooterContainer = styled(EuiFlexGroup)<{ height: number }>` - height: ${({ height }) => height}px; +interface HeightProp { + height: number; +} + +const FooterContainer = styled(EuiFlexGroup).attrs(({ height }) => ({ + style: { + height: `${height}px`, + }, +}))` + flex: 0; `; FooterContainer.displayName = 'FooterContainer'; @@ -56,7 +86,7 @@ const LoadingPanelContainer = styled.div` LoadingPanelContainer.displayName = 'LoadingPanelContainer'; -const PopoverRowItems = styled((EuiPopover as unknown) as FunctionComponent)< +const PopoverRowItems = styled((EuiPopover as unknown) as FC)< EuiPopoverProps & { className?: string; id?: string; @@ -173,11 +203,9 @@ export const PagingControl = React.memo(PagingControlComponent); PagingControl.displayName = 'PagingControl'; interface FooterProps { - compact: boolean; getUpdatedAt: () => number; hasNextPage: boolean; height: number; - isEventViewer?: boolean; isLive: boolean; isLoading: boolean; itemsCount: number; @@ -192,11 +220,9 @@ interface FooterProps { /** Renders a loading indicator and paging controls */ export const FooterComponent = ({ - compact, getUpdatedAt, hasNextPage, height, - isEventViewer, isLive, isLoading, itemsCount, @@ -216,11 +242,13 @@ export const FooterComponent = ({ const loadMore = useCallback(() => { setPaginationLoading(true); onLoadMore(nextCursor, tieBreaker); - }, [nextCursor, tieBreaker, onLoadMore]); + }, [nextCursor, tieBreaker, onLoadMore, setPaginationLoading]); - const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); - - const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [ + isPopoverOpen, + setIsPopoverOpen, + ]); + const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); useEffect(() => { if (paginationLoading && !isLoading) { @@ -263,95 +291,78 @@ export const FooterComponent = ({ )); return ( - <> - + - - - - - - - - - {isLive ? ( - - - {i18n.AUTO_REFRESH_ACTIVE}{' '} - - } - type="iInCircle" - /> - - - ) : ( - - )} - - - - - - - - - - + + + + + + + + {isLive ? ( + + + {i18n.AUTO_REFRESH_ACTIVE}{' '} + + } + type="iInCircle" + /> + + + ) : ( + + )} + + + + + + + ); }; FooterComponent.displayName = 'FooterComponent'; -export const Footer = React.memo( - FooterComponent, - (prevProps, nextProps) => - prevProps.compact === nextProps.compact && - prevProps.hasNextPage === nextProps.hasNextPage && - prevProps.height === nextProps.height && - prevProps.isEventViewer === nextProps.isEventViewer && - prevProps.isLive === nextProps.isLive && - prevProps.isLoading === nextProps.isLoading && - prevProps.itemsCount === nextProps.itemsCount && - prevProps.itemsPerPage === nextProps.itemsPerPage && - prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions && - prevProps.serverSideEventCount === nextProps.serverSideEventCount -); +export const Footer = React.memo(FooterComponent); Footer.displayName = 'Footer'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/header/__snapshots__/index.test.tsx.snap index 048ca080772f6..90d0dc1a8a66d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/__snapshots__/index.test.tsx.snap @@ -1,9 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header rendering renders correctly against snapshot 1`] = ` - + - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx index 5af7aff4f8795..317c68b63f691 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx @@ -7,13 +7,12 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Direction } from '../../../graphql/types'; import { mockIndexPattern } from '../../../mock'; import { TestProviders } from '../../../mock/test_providers'; import { mockDataProviders } from '../data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../../utils/use_mount_appended'; -import { TimelineHeaderComponent } from '.'; +import { TimelineHeader } from '.'; jest.mock('../../../lib/kibana'); @@ -24,7 +23,7 @@ describe('Header', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { onToggleDataProviderExcluded={jest.fn()} show={true} showCallOutUnauthorizedMsg={false} - sort={{ - columnId: '@timestamp', - sortDirection: Direction.desc, - }} /> ); expect(wrapper).toMatchSnapshot(); @@ -49,7 +44,7 @@ describe('Header', () => { test('it renders the data providers', () => { const wrapper = mount( - { onToggleDataProviderExcluded={jest.fn()} show={true} showCallOutUnauthorizedMsg={false} - sort={{ - columnId: '@timestamp', - sortDirection: Direction.desc, - }} /> ); @@ -76,7 +67,7 @@ describe('Header', () => { test('it renders the unauthorized call out providers', () => { const wrapper = mount( - { onToggleDataProviderExcluded={jest.fn()} show={true} showCallOutUnauthorizedMsg={true} - sort={{ - columnId: '@timestamp', - sortDirection: Direction.desc, - }} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx index 81eef0efbfa5b..7cac03cec42b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx @@ -6,10 +6,9 @@ import { EuiCallOut } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; import { IIndexPattern } from 'src/plugins/data/public'; +import deepEqual from 'fast-deep-equal'; -import { Sort } from '../body/sort'; import { DataProviders } from '../data_providers'; import { DataProvider } from '../data_providers/data_provider'; import { @@ -38,16 +37,9 @@ interface Props { onToggleDataProviderExcluded: OnToggleDataProviderExcluded; show: boolean; showCallOutUnauthorizedMsg: boolean; - sort: Sort; } -const TimelineHeaderContainer = styled.div` - width: 100%; -`; - -TimelineHeaderContainer.displayName = 'TimelineHeaderContainer'; - -export const TimelineHeaderComponent: React.FC = ({ +const TimelineHeaderComponent: React.FC = ({ browserFields, id, indexPattern, @@ -61,7 +53,7 @@ export const TimelineHeaderComponent: React.FC = ({ show, showCallOutUnauthorizedMsg, }) => ( - + <> {showCallOutUnauthorizedMsg && ( = ({ indexPattern={indexPattern} timelineId={id} /> - + ); -export const TimelineHeader = React.memo(TimelineHeaderComponent); +export const TimelineHeader = React.memo( + TimelineHeaderComponent, + (prevProps, nextProps) => + deepEqual(prevProps.browserFields, nextProps.browserFields) && + prevProps.id === nextProps.id && + deepEqual(prevProps.indexPattern, nextProps.indexPattern) && + deepEqual(prevProps.dataProviders, nextProps.dataProviders) && + prevProps.onChangeDataProviderKqlQuery === nextProps.onChangeDataProviderKqlQuery && + prevProps.onChangeDroppableAndProvider === nextProps.onChangeDroppableAndProvider && + prevProps.onDataProviderEdited === nextProps.onDataProviderEdited && + prevProps.onDataProviderRemoved === nextProps.onDataProviderRemoved && + prevProps.onToggleDataProviderEnabled === nextProps.onToggleDataProviderEnabled && + prevProps.onToggleDataProviderExcluded === nextProps.onToggleDataProviderExcluded && + prevProps.show === nextProps.show && + prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/helpers.tsx index 611d08e61be22..f051bbe5b1af6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/helpers.tsx @@ -153,25 +153,6 @@ export const combineQueries = ({ }; }; -interface CalculateBodyHeightParams { - /** The the height of the flyout container, which is typically the entire "page", not including the standard Kibana navigation */ - flyoutHeight?: number; - /** The flyout header typically contains a title and a close button */ - flyoutHeaderHeight?: number; - /** All non-body timeline content (i.e. the providers drag and drop area, and the column headers) */ - timelineHeaderHeight?: number; - /** Footer content that appears below the body (i.e. paging controls) */ - timelineFooterHeight?: number; -} - -export const calculateBodyHeight = ({ - flyoutHeight = 0, - flyoutHeaderHeight = 0, - timelineHeaderHeight = 0, - timelineFooterHeight = 0, -}: CalculateBodyHeightParams): number => - flyoutHeight - (flyoutHeaderHeight + timelineHeaderHeight + timelineFooterHeight); - /** * The CSS class name of a "stateful event", which appears in both * the `Timeline` and the `Events Viewer` widget diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx index 0ce6bc16f1325..35099e3836fb4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx @@ -28,8 +28,8 @@ import { Timeline } from './timeline'; export interface OwnProps { id: string; - flyoutHeaderHeight: number; - flyoutHeight: number; + onClose: () => void; + usersViewing: string[]; } type Props = OwnProps & PropsFromRedux; @@ -42,14 +42,13 @@ const StatefulTimelineComponent = React.memo( eventType, end, filters, - flyoutHeaderHeight, - flyoutHeight, id, isLive, itemsPerPage, itemsPerPageOptions, kqlMode, kqlQueryExpression, + onClose, onDataProviderEdited, removeColumn, removeProvider, @@ -63,6 +62,7 @@ const StatefulTimelineComponent = React.memo( updateHighlightedDropAndProviderId, updateItemsPerPage, upsertColumn, + usersViewing, }) => { const { loading, signalIndexExists, signalIndexName } = useSignalIndex(); @@ -173,8 +173,6 @@ const StatefulTimelineComponent = React.memo( end={end} eventType={eventType} filters={filters} - flyoutHeaderHeight={flyoutHeaderHeight} - flyoutHeight={flyoutHeight} id={id} indexPattern={indexPattern} indexToAdd={indexToAdd} @@ -187,6 +185,7 @@ const StatefulTimelineComponent = React.memo( onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery} onChangeDroppableAndProvider={onChangeDroppableAndProvider} onChangeItemsPerPage={onChangeItemsPerPage} + onClose={onClose} onDataProviderEdited={onDataProviderEditedLocal} onDataProviderRemoved={onDataProviderRemoved} onToggleDataProviderEnabled={onToggleDataProviderEnabled} @@ -196,6 +195,7 @@ const StatefulTimelineComponent = React.memo( sort={sort!} start={start} toggleColumn={toggleColumn} + usersViewing={usersViewing} /> )} @@ -205,8 +205,6 @@ const StatefulTimelineComponent = React.memo( return ( prevProps.eventType === nextProps.eventType && prevProps.end === nextProps.end && - prevProps.flyoutHeaderHeight === nextProps.flyoutHeaderHeight && - prevProps.flyoutHeight === nextProps.flyoutHeight && prevProps.id === nextProps.id && prevProps.isLive === nextProps.isLive && prevProps.itemsPerPage === nextProps.itemsPerPage && @@ -219,7 +217,8 @@ const StatefulTimelineComponent = React.memo( deepEqual(prevProps.dataProviders, nextProps.dataProviders) && deepEqual(prevProps.filters, nextProps.filters) && deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && - deepEqual(prevProps.sort, nextProps.sort) + deepEqual(prevProps.sort, nextProps.sort) && + deepEqual(prevProps.usersViewing, nextProps.usersViewing) ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index ae139c24d0176..4b1fd4b5851c0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -6,7 +6,6 @@ import { EuiBadge, - EuiBadgeProps, EuiButton, EuiButtonEmpty, EuiButtonIcon, @@ -18,8 +17,9 @@ import { EuiOverlayMask, EuiToolTip, } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import uuid from 'uuid'; +import styled from 'styled-components'; import { Note } from '../../../lib/note'; import { Notes } from '../../notes'; @@ -32,13 +32,10 @@ export const historyToolTip = 'The chronological history of actions related to t export const streamLiveToolTip = 'Update the Timeline as new data arrives'; export const newTimelineToolTip = 'Create a new timeline'; -// Ref: https://github.com/elastic/eui/issues/1655 -// const NotesCountBadge = styled(EuiBadge)` -// margin-left: 5px; -// `; -const NotesCountBadge = (props: EuiBadgeProps) => ( - -); +const NotesCountBadge = styled(EuiBadge)` + margin-left: 5px; +`; + NotesCountBadge.displayName = 'NotesCountBadge'; type CreateTimeline = ({ id, show }: { id: string; show?: boolean }) => void; @@ -121,20 +118,24 @@ interface NewTimelineProps { } export const NewTimeline = React.memo( - ({ createTimeline, onClosePopover, timelineId }) => ( - { - createTimeline({ id: timelineId, show: true }); - onClosePopover(); - }} - > - {i18n.NEW_TIMELINE} - - ) + ({ createTimeline, onClosePopover, timelineId }) => { + const handleClick = useCallback(() => { + createTimeline({ id: timelineId, show: true }); + onClosePopover(); + }, [createTimeline, timelineId, onClosePopover]); + + return ( + + {i18n.NEW_TIMELINE} + + ); + } ); NewTimeline.displayName = 'NewTimeline'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx index 495b94f8c02e7..e942c8f36dc83 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx @@ -10,11 +10,18 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import { mockGlobalState, apolloClientObservable } from '../../../mock'; import { createStore, State } from '../../../store'; +import { useThrottledResizeObserver } from '../../utils'; import { Properties, showDescriptionThreshold, showNotesThreshold } from '.'; jest.mock('../../../lib/kibana'); +let mockedWidth = 1000; +jest.mock('../../utils'); +(useThrottledResizeObserver as jest.Mock).mockImplementation(() => ({ + width: mockedWidth, +})); + describe('Properties', () => { const usersViewing = ['elastic']; @@ -24,6 +31,7 @@ describe('Properties', () => { beforeEach(() => { jest.clearAllMocks(); store = createStore(state, apolloClientObservable); + mockedWidth = 1000; }); test('renders correctly', () => { @@ -46,7 +54,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -73,7 +80,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -101,7 +107,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -131,7 +136,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -164,7 +168,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -197,7 +200,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -229,7 +231,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -243,7 +244,7 @@ describe('Properties', () => { test('it renders a description on the left when the width is at least as wide as the threshold', () => { const description = 'strange'; - const width = showDescriptionThreshold; + mockedWidth = showDescriptionThreshold; const wrapper = mount( @@ -264,7 +265,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={width} /> ); @@ -280,7 +280,7 @@ describe('Properties', () => { test('it does NOT render a description on the left when the width is less than the threshold', () => { const description = 'strange'; - const width = showDescriptionThreshold - 1; + mockedWidth = showDescriptionThreshold - 1; const wrapper = mount( @@ -301,7 +301,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={width} /> ); @@ -315,7 +314,7 @@ describe('Properties', () => { }); test('it renders a notes button on the left when the width is at least as wide as the threshold', () => { - const width = showNotesThreshold; + mockedWidth = showNotesThreshold; const wrapper = mount( @@ -336,7 +335,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={width} /> ); @@ -350,7 +348,7 @@ describe('Properties', () => { }); test('it does NOT render a a notes button on the left when the width is less than the threshold', () => { - const width = showNotesThreshold - 1; + mockedWidth = showNotesThreshold - 1; const wrapper = mount( @@ -371,7 +369,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={width} /> ); @@ -404,7 +401,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -434,7 +430,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); @@ -462,7 +457,6 @@ describe('Properties', () => { updateTitle={jest.fn()} updateNote={jest.fn()} usersViewing={usersViewing} - width={1000} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx index 7b69e006f48ad..8549784b8ecd6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; +import { useThrottledResizeObserver } from '../../utils'; import { Note } from '../../../lib/note'; import { InputsModelId } from '../../../store/inputs/constants'; import { AssociateNote, UpdateNote } from '../../notes/helpers'; @@ -37,7 +38,6 @@ interface Props { updateNote: UpdateNote; updateTitle: UpdateTitle; usersViewing: string[]; - width: number; } const rightGutter = 60; // px @@ -49,7 +49,7 @@ const starIconWidth = 30; const nameWidth = 155; const descriptionWidth = 165; const noteWidth = 130; -const settingsWidth = 50; +const settingsWidth = 55; /** Displays the properties of a timeline, i.e. name, description, notes, etc */ export const Properties = React.memo( @@ -70,47 +70,36 @@ export const Properties = React.memo( updateNote, updateTitle, usersViewing, - width, }) => { + const { ref, width = 0 } = useThrottledResizeObserver(300); const [showActions, setShowActions] = useState(false); const [showNotes, setShowNotes] = useState(false); const [showTimelineModal, setShowTimelineModal] = useState(false); - const onButtonClick = useCallback(() => { - setShowActions(!showActions); - }, [showActions]); - - const onToggleShowNotes = useCallback(() => { - setShowNotes(!showNotes); - }, [showNotes]); - - const onClosePopover = useCallback(() => { - setShowActions(false); - }, []); - + const onButtonClick = useCallback(() => setShowActions(!showActions), [showActions]); + const onToggleShowNotes = useCallback(() => setShowNotes(!showNotes), [showNotes]); + const onClosePopover = useCallback(() => setShowActions(false), []); + const onCloseTimelineModal = useCallback(() => setShowTimelineModal(false), []); + const onToggleLock = useCallback(() => toggleLock({ linkToId: 'timeline' }), [toggleLock]); const onOpenTimelineModal = useCallback(() => { onClosePopover(); setShowTimelineModal(true); }, []); - const onCloseTimelineModal = useCallback(() => { - setShowTimelineModal(false); - }, []); - - const datePickerWidth = - width - - rightGutter - - starIconWidth - - nameWidth - - (width >= showDescriptionThreshold ? descriptionWidth : 0) - - noteWidth - - settingsWidth; + const datePickerWidth = useMemo( + () => + width - + rightGutter - + starIconWidth - + nameWidth - + (width >= showDescriptionThreshold ? descriptionWidth : 0) - + noteWidth - + settingsWidth, + [width] + ); - // 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 ( - + ( showNotesFromWidth={width >= showNotesThreshold} timelineId={timelineId} title={title} - toggleLock={() => { - toggleLock({ linkToId: 'timeline' }); - }} + toggleLock={onToggleLock} updateDescription={updateDescription} updateIsFavorite={updateIsFavorite} updateNote={updateNote} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_left.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_left.tsx index 21800fefb21fb..3016def8a80b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_left.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_left.tsx @@ -52,7 +52,15 @@ export const LockIconContainer = styled(EuiFlexItem)` LockIconContainer.displayName = 'LockIconContainer'; -export const DatePicker = styled(EuiFlexItem)` +interface WidthProp { + width: number; +} + +export const DatePicker = styled(EuiFlexItem).attrs(({ width }) => ({ + style: { + width: `${width}px`, + }, +}))` .euiSuperDatePicker__flexWrapper { max-width: none; width: auto; @@ -151,7 +159,7 @@ export const PropertiesLeft = React.memo( /> - + diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/styles.tsx index 3444875282ae7..74653fb6cb1ef 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/styles.tsx @@ -12,17 +12,26 @@ const fadeInEffect = keyframes` to { opacity: 1; } `; +interface WidthProp { + width: number; +} + export const TimelineProperties = styled.div` + flex: 1; align-items: center; display: flex; flex-direction: row; justify-content: space-between; user-select: none; `; + TimelineProperties.displayName = 'TimelineProperties'; -export const DatePicker = styled(EuiFlexItem)<{ width: number }>` - width: ${({ width }) => `${width}px`}; +export const DatePicker = styled(EuiFlexItem).attrs(({ width }) => ({ + style: { + width: `${width}px`, + }, +}))` .euiSuperDatePicker__flexWrapper { max-width: none; width: auto; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx index 3d2ec0683f091..73c20d9b9b6b4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx @@ -5,7 +5,7 @@ */ import React, { useEffect } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { inputsModel } from '../../store'; import { inputsActions } from '../../store/actions'; @@ -19,29 +19,20 @@ export interface TimelineRefetchProps { refetch: inputsModel.Refetch; } -type OwnProps = TimelineRefetchProps & PropsFromRedux; - -const TimelineRefetchComponent: React.FC = ({ +const TimelineRefetchComponent: React.FC = ({ id, inputId, inspect, loading, refetch, - setTimelineQuery, }) => { + const dispatch = useDispatch(); + useEffect(() => { - setTimelineQuery({ id, inputId, inspect, loading, refetch }); - }, [id, inputId, loading, refetch, inspect]); + dispatch(inputsActions.setQuery({ id, inputId, inspect, loading, refetch })); + }, [dispatch, id, inputId, loading, refetch, inspect]); return null; }; -const mapDispatchToProps = { - setTimelineQuery: inputsActions.setQuery, -}; - -const connector = connect(null, mapDispatchToProps); - -type PropsFromRedux = ConnectedProps; - -export const TimelineRefetch = connector(React.memo(TimelineRefetchComponent)); +export const TimelineRefetch = React.memo(TimelineRefetchComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx index d5e5d15eb8ad2..16fb57714829c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx @@ -11,12 +11,6 @@ import styled, { createGlobalStyle } from 'styled-components'; import { EventType } from '../../store/timeline/model'; import { IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME } from '../drag_and_drop/helpers'; -/** - * OFFSET PIXEL VALUES - */ - -export const OFFSET_SCROLLBAR = 17; - /** * TIMELINE BODY */ @@ -30,10 +24,11 @@ export const TimelineBodyGlobalStyle = createGlobalStyle` export const TimelineBody = styled.div.attrs(({ className = '' }) => ({ className: `siemTimeline__body ${className}`, -}))<{ bodyHeight: number }>` - height: ${({ bodyHeight }) => `${bodyHeight}px`}; +}))<{ bodyHeight?: number }>` + height: ${({ bodyHeight }) => (bodyHeight ? `${bodyHeight}px` : 'auto')}; overflow: auto; scrollbar-width: thin; + flex: 1; &::-webkit-scrollbar { height: ${({ theme }) => theme.eui.euiScrollBar}; @@ -57,10 +52,19 @@ TimelineBody.displayName = 'TimelineBody'; * EVENTS TABLE */ -export const EventsTable = styled.div.attrs(({ className = '' }) => ({ - className: `siemEventsTable ${className}`, - role: 'table', -}))``; +interface EventsTableProps { + columnWidths: number; +} + +export const EventsTable = styled.div.attrs( + ({ className = '', columnWidths }) => ({ + className: `siemEventsTable ${className}`, + role: 'table', + style: { + minWidth: `${columnWidths}px`, + }, + }) +)``; /* EVENTS HEAD */ @@ -177,6 +181,14 @@ export const EventsTrData = styled.div.attrs(({ className = '' }) => ({ display: flex; `; +const TIMELINE_EVENT_DETAILS_OFFSET = 40; + +export const EventsTrSupplementContainer = styled.div.attrs(({ width }) => ({ + style: { + width: `${width! - TIMELINE_EVENT_DETAILS_OFFSET}px`, + }, +}))``; + export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trSupplement ${className}`, }))<{ className: string }>` @@ -200,11 +212,17 @@ export const EventsTdGroupData = styled.div.attrs(({ className = '' }) => ({ }))` display: flex; `; +interface WidthProp { + width?: number; +} -export const EventsTd = styled.div.attrs(({ className = '' }) => ({ +export const EventsTd = styled.div.attrs(({ className = '', width }) => ({ className: `siemEventsTable__td ${className}`, role: 'cell', -}))` + style: { + flexBasis: width ? `${width}px` : 'auto', + }, +}))` align-items: center; display: flex; flex-shrink: 0; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx index d66bc702bae43..ea4406311d7cc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx @@ -14,20 +14,17 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { Direction } from '../../graphql/types'; import { defaultHeaders, mockTimelineData, mockIndexPattern } from '../../mock'; import { TestProviders } from '../../mock/test_providers'; -import { flyoutHeaderHeight } from '../flyout'; import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME, } from './data_providers/provider_item_actions'; -import { TimelineComponent } from './timeline'; +import { TimelineComponent, Props as TimelineComponentProps } from './timeline'; import { Sort } from './body/sort'; import { mockDataProviders } from './data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; -const testFlyoutHeight = 980; - jest.mock('../../lib/kibana'); const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; @@ -35,6 +32,7 @@ jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); describe('Timeline', () => { + let props = {} as TimelineComponentProps; const sort: Sort = { columnId: '@timestamp', sortDirection: Direction.desc, @@ -50,41 +48,44 @@ describe('Timeline', () => { const mount = useMountAppended(); + beforeEach(() => { + props = { + browserFields: mockBrowserFields, + columns: defaultHeaders, + id: 'foo', + dataProviders: mockDataProviders, + end: endDate, + eventType: 'raw' as TimelineComponentProps['eventType'], + filters: [], + indexPattern, + indexToAdd: [], + isLive: false, + itemsPerPage: 5, + itemsPerPageOptions: [5, 10, 20], + kqlMode: 'search' as TimelineComponentProps['kqlMode'], + kqlQueryExpression: '', + loadingIndexName: false, + onChangeDataProviderKqlQuery: jest.fn(), + onChangeDroppableAndProvider: jest.fn(), + onChangeItemsPerPage: jest.fn(), + onClose: jest.fn(), + onDataProviderEdited: jest.fn(), + onDataProviderRemoved: jest.fn(), + onToggleDataProviderEnabled: jest.fn(), + onToggleDataProviderExcluded: jest.fn(), + show: true, + showCallOutUnauthorizedMsg: false, + start: startDate, + sort, + toggleColumn: jest.fn(), + usersViewing: ['elastic'], + }; + }); + describe('rendering', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); }); @@ -92,37 +93,7 @@ describe('Timeline', () => { const wrapper = mount( - + ); @@ -130,41 +101,28 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true); }); + test('it renders the title field', () => { + const wrapper = mount( + + + + + + ); + + expect( + wrapper + .find('[data-test-subj="timeline-title"]') + .first() + .props().placeholder + ).toContain('Untitled Timeline'); + }); + test('it renders the timeline table', () => { const wrapper = mount( - + ); @@ -176,37 +134,7 @@ describe('Timeline', () => { const wrapper = mount( - + ); @@ -218,36 +146,7 @@ describe('Timeline', () => { const wrapper = mount( - + ); @@ -261,42 +160,10 @@ describe('Timeline', () => { describe('event wire up', () => { describe('onDataProviderRemoved', () => { test('it invokes the onDataProviderRemoved callback when the delete button on a provider is clicked', () => { - const mockOnDataProviderRemoved = jest.fn(); - const wrapper = mount( - + ); @@ -306,46 +173,16 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1'); + expect((props.onDataProviderRemoved as jest.Mock).mock.calls[0][0]).toEqual( + 'id-Provider 1' + ); }); test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => { - const mockOnDataProviderRemoved = jest.fn(); - const wrapper = mount( - + ); @@ -361,48 +198,18 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1'); + expect((props.onDataProviderRemoved as jest.Mock).mock.calls[0][0]).toEqual( + 'id-Provider 1' + ); }); }); describe('onToggleDataProviderEnabled', () => { test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => { - const mockOnToggleDataProviderEnabled = jest.fn(); - const wrapper = mount( - + ); @@ -419,7 +226,7 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({ + expect((props.onToggleDataProviderEnabled as jest.Mock).mock.calls[0][0]).toEqual({ providerId: 'id-Provider 1', enabled: false, }); @@ -428,42 +235,10 @@ describe('Timeline', () => { describe('onToggleDataProviderExcluded', () => { test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => { - const mockOnToggleDataProviderExcluded = jest.fn(); - const wrapper = mount( - + ); @@ -482,7 +257,7 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnToggleDataProviderExcluded.mock.calls[0][0]).toEqual({ + expect((props.onToggleDataProviderExcluded as jest.Mock).mock.calls[0][0]).toEqual({ providerId: 'id-Provider 1', excluded: true, }); @@ -490,44 +265,14 @@ describe('Timeline', () => { }); describe('#ProviderWithAndProvider', () => { - test('Rendering And Provider', () => { - const dataProviders = mockDataProviders.slice(0, 1); - dataProviders[0].and = mockDataProviders.slice(1, 3); + const dataProviders = mockDataProviders.slice(0, 1); + dataProviders[0].and = mockDataProviders.slice(1, 3); + test('Rendering And Provider', () => { const wrapper = mount( - + ); @@ -544,44 +289,10 @@ describe('Timeline', () => { }); test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the accordion menu', () => { - const dataProviders = mockDataProviders.slice(0, 1); - dataProviders[0].and = mockDataProviders.slice(1, 3); - const mockOnDataProviderRemoved = jest.fn(); - const wrapper = mount( - + ); @@ -600,48 +311,17 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnDataProviderRemoved.mock.calls[0]).toEqual(['id-Provider 1', 'id-Provider 2']); + expect((props.onDataProviderRemoved as jest.Mock).mock.calls[0]).toEqual([ + 'id-Provider 1', + 'id-Provider 2', + ]); }); test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the accordion menu', () => { - const dataProviders = mockDataProviders.slice(0, 1); - dataProviders[0].and = mockDataProviders.slice(1, 3); - const mockOnToggleDataProviderEnabled = jest.fn(); - const wrapper = mount( - + ); @@ -660,7 +340,7 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({ + expect((props.onToggleDataProviderEnabled as jest.Mock).mock.calls[0][0]).toEqual({ andProviderId: 'id-Provider 2', enabled: false, providerId: 'id-Provider 1', @@ -668,44 +348,10 @@ describe('Timeline', () => { }); test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the accordion menu', () => { - const dataProviders = mockDataProviders.slice(0, 1); - dataProviders[0].and = mockDataProviders.slice(1, 3); - const mockOnToggleDataProviderExcluded = jest.fn(); - const wrapper = mount( - + ); @@ -724,7 +370,7 @@ describe('Timeline', () => { .first() .simulate('click'); - expect(mockOnToggleDataProviderExcluded.mock.calls[0][0]).toEqual({ + expect((props.onToggleDataProviderExcluded as jest.Mock).mock.calls[0][0]).toEqual({ andProviderId: 'id-Provider 2', excluded: true, providerId: 'id-Provider 1', diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index 58bbbef328ddf..098dd82791610 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter } from '@elastic/eui'; import { getOr, isEmpty } from 'lodash/fp'; -import React from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; -import useResizeObserver from 'use-resize-observer/polyfilled'; +import { FlyoutHeaderWithCloseButton } from '../flyout/header_with_close_button'; import { BrowserFields } from '../../containers/source'; import { TimelineQuery } from '../../containers/timeline'; import { Direction } from '../../graphql/types'; @@ -31,38 +31,60 @@ import { import { TimelineKqlFetch } from './fetch_kql_timeline'; import { Footer, footerHeight } from './footer'; import { TimelineHeader } from './header'; -import { calculateBodyHeight, combineQueries } from './helpers'; +import { combineQueries } from './helpers'; import { TimelineRefetch } from './refetch_timeline'; import { ManageTimelineContext } from './timeline_context'; import { esQuery, Filter, IIndexPattern } from '../../../../../../../src/plugins/data/public'; -const WrappedByAutoSizer = styled.div` +const TimelineContainer = styled.div` + height: 100%; + display: flex; + flex-direction: column; +`; + +const TimelineHeaderContainer = styled.div` + margin-top: 6px; width: 100%; -`; // required by AutoSizer +`; -WrappedByAutoSizer.displayName = 'WrappedByAutoSizer'; +TimelineHeaderContainer.displayName = 'TimelineHeaderContainer'; -const TimelineContainer = styled(EuiFlexGroup)` - min-height: 500px; - overflow: hidden; - padding: 0 10px 0 12px; - user-select: none; - width: 100%; +const StyledEuiFlyoutHeader = styled(EuiFlyoutHeader)` + align-items: center; + box-shadow: none; + display: flex; + flex-direction: column; + padding: 14px 10px 0 12px; `; -TimelineContainer.displayName = 'TimelineContainer'; +const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` + overflow-y: hidden; + flex: 1; -export const isCompactFooter = (width: number): boolean => width < 600; + .euiFlyoutBody__overflow { + overflow: hidden; + mask-image: none; + } -interface Props { + .euiFlyoutBody__overflowContent { + padding: 0 10px 0 12px; + height: 100%; + display: flex; + } +`; + +const StyledEuiFlyoutFooter = styled(EuiFlyoutFooter)` + background: none; + padding: 0 10px 5px 12px; +`; + +export interface Props { browserFields: BrowserFields; columns: ColumnHeaderOptions[]; dataProviders: DataProvider[]; end: number; eventType?: EventType; filters: Filter[]; - flyoutHeaderHeight: number; - flyoutHeight: number; id: string; indexPattern: IIndexPattern; indexToAdd: string[]; @@ -75,6 +97,7 @@ interface Props { onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery; onChangeDroppableAndProvider: OnChangeDroppableAndProvider; onChangeItemsPerPage: OnChangeItemsPerPage; + onClose: () => void; onDataProviderEdited: OnDataProviderEdited; onDataProviderRemoved: OnDataProviderRemoved; onToggleDataProviderEnabled: OnToggleDataProviderEnabled; @@ -84,6 +107,7 @@ interface Props { start: number; sort: Sort; toggleColumn: (column: ColumnHeaderOptions) => void; + usersViewing: string[]; } /** The parent Timeline component */ @@ -94,8 +118,6 @@ export const TimelineComponent: React.FC = ({ end, eventType, filters, - flyoutHeaderHeight, - flyoutHeight, id, indexPattern, indexToAdd, @@ -108,6 +130,7 @@ export const TimelineComponent: React.FC = ({ onChangeDataProviderKqlQuery, onChangeDroppableAndProvider, onChangeItemsPerPage, + onClose, onDataProviderEdited, onDataProviderRemoved, onToggleDataProviderEnabled, @@ -117,10 +140,8 @@ export const TimelineComponent: React.FC = ({ start, sort, toggleColumn, + usersViewing, }) => { - const { ref: measureRef, width = 0, height: timelineHeaderHeight = 0 } = useResizeObserver< - HTMLDivElement - >({}); const kibana = useKibana(); const combinedQueries = combineQueries({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), @@ -134,45 +155,51 @@ export const TimelineComponent: React.FC = ({ end, }); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; + const timelineQueryFields = useMemo(() => columnsHeader.map(c => c.id), [columnsHeader]); + const timelineQuerySortField = useMemo( + () => ({ + sortFieldId: sort.columnId, + direction: sort.sortDirection as Direction, + }), + [sort.columnId, sort.sortDirection] + ); return ( - - }> - + + - + + + + {combinedQueries != null ? ( c.id)} + fields={timelineQueryFields} sourceId="default" limit={itemsPerPage} filterQuery={combinedQueries.filterQuery} - sortField={{ - sortFieldId: sort.columnId, - direction: sort.sortDirection as Direction, - }} + sortField={timelineQuerySortField} > {({ events, @@ -184,7 +211,7 @@ export const TimelineComponent: React.FC = ({ getUpdatedAt, refetch, }) => ( - + = ({ loading={loading} refetch={refetch} /> - -