Skip to content

Commit

Permalink
[Security Solution] Hide timeline footer when Resolver is open (#71516)
Browse files Browse the repository at this point in the history
* Hide the Timeline footer, in the event viewer, if Resolver is showing
  • Loading branch information
Robert Austin committed Jul 14, 2020
1 parent f74340f commit 63c3609
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ interface Props {
sort: Sort;
toggleColumn: (column: ColumnHeaderOptions) => void;
utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode;
// If truthy, the graph viewer (Resolver) is showing
graphEventId: string | undefined;
}

const EventsViewerComponent: React.FC<Props> = ({
Expand All @@ -90,6 +92,7 @@ const EventsViewerComponent: React.FC<Props> = ({
sort,
toggleColumn,
utilityBar,
graphEventId,
}) => {
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
Expand Down Expand Up @@ -191,22 +194,28 @@ const EventsViewerComponent: React.FC<Props> = ({
toggleColumn={toggleColumn}
/>

<Footer
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
id={id}
isLive={isLive}
isLoading={loading}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
serverSideEventCount={totalCountMinusDeleted}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
{
/** Hide the footer if Resolver is showing. */
!graphEventId && (
<Footer
data-test-subj="events-viewer-footer"
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
id={id}
isLive={isLive}
isLoading={loading}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
serverSideEventCount={totalCountMinusDeleted}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
)
}
</EventsContainerLoading>
</>
);
Expand Down Expand Up @@ -237,5 +246,6 @@ export const EventsViewer = React.memo(
deepEqual(prevProps.query, nextProps.query) &&
prevProps.start === nextProps.start &&
prevProps.sort === nextProps.sort &&
prevProps.utilityBar === nextProps.utilityBar
prevProps.utilityBar === nextProps.utilityBar &&
prevProps.graphEventId === nextProps.graphEventId
);
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
updateItemsPerPage,
upsertColumn,
utilityBar,
// If truthy, the graph viewer (Resolver) is showing
graphEventId,
}) => {
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns(
defaultIndices ?? useUiSetting<string[]>(DEFAULT_INDEX_KEY)
Expand Down Expand Up @@ -135,6 +137,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
sort={sort}
toggleColumn={toggleColumn}
utilityBar={utilityBar}
graphEventId={graphEventId}
/>
</InspectButtonContainer>
);
Expand All @@ -145,6 +148,7 @@ const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
const getEvents = timelineSelectors.getEventsByIdSelector();
const getTimeline = timelineSelectors.getTimelineByIdSelector();
const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => {
const input: inputsModel.InputsRange = getInputsTimeline(state);
const events: TimelineModel = getEvents(state, id) ?? defaultModel;
Expand Down Expand Up @@ -174,6 +178,9 @@ const makeMapStateToProps = () => {
query: getGlobalQuerySelector(state),
sort,
showCheckboxes,
// Used to determine whether the footer should show (since it is hidden if the graph is showing.)
// `getTimeline` actually returns `TimelineModel | undefined`
graphEventId: (getTimeline(state, id) as TimelineModel | undefined)?.graphEventId,
};
};
return mapStateToProps;
Expand Down Expand Up @@ -213,6 +220,7 @@ export const StatefulEventsViewer = connector(
deepEqual(prevProps.pageFilters, nextProps.pageFilters) &&
prevProps.showCheckboxes === nextProps.showCheckboxes &&
prevProps.start === nextProps.start &&
prevProps.utilityBar === nextProps.utilityBar
prevProps.utilityBar === nextProps.utilityBar &&
prevProps.graphEventId === nextProps.graphEventId
)
);
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ export const getEventType = (event: Ecs): Omit<EventType, 'all'> => {
return 'raw';
};

export const showGraphView = (graphEventId?: string) =>
graphEventId != null && graphEventId.length > 0;

export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => {
return (
get(['agent', 'type', 0], ecsData) === 'endpoint' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { columnRenderers, rowRenderers } from './renderers';
import { Sort } from './sort';
import { wait } from '../../../../common/lib/helpers';
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
import { SELECTOR_TIMELINE_BODY_CLASS_NAME } from '../styles';
import { SELECTOR_TIMELINE_BODY_CLASS_NAME, TimelineBody } from '../styles';
import { ReactWrapper } from '@elastic/eui/node_modules/@types/enzyme';

const testBodyHeight = 700;
const mockGetNotesByIds = (eventId: string[]) => [];
Expand All @@ -33,8 +34,12 @@ jest.mock('react-redux', () => {
useSelector: jest.fn(),
};
});

jest.mock('../../../../common/components/link_to');

// Prevent Resolver from rendering
jest.mock('../../graph_overlay');

jest.mock(
'react-visibility-sensor',
() => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) =>
Expand Down Expand Up @@ -148,6 +153,29 @@ describe('Body', () => {
.exists()
).toEqual(true);
});
describe('when there is a graphEventId', () => {
beforeEach(() => {
props.graphEventId = 'graphEventId'; // any string w/ length > 0 works
});
it('should not render the timeline body', () => {
const wrapper = mount(
<TestProviders>
<Body {...props} />
</TestProviders>
);

// The value returned if `wrapper.find` returns a `TimelineBody` instance.
type TimelineBodyEnzymeWrapper = ReactWrapper<React.ComponentProps<typeof TimelineBody>>;

// The first TimelineBody component
const timelineBody: TimelineBodyEnzymeWrapper = wrapper
.find('[data-test-subj="timeline-body"]')
.first() as TimelineBodyEnzymeWrapper;

// the timeline body still renders, but it gets a `display: none` style via `styled-components`.
expect(timelineBody.props().visible).toBe(false);
});
});
});

describe('action on event', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { EventsTable, TimelineBody, TimelineBodyGlobalStyle } from '../styles';
import { ColumnHeaders } from './column_headers';
import { getActionsColumnWidth } from './column_headers/helpers';
import { Events } from './events';
import { showGraphView } from './helpers';
import { ColumnRenderer } from './renderers/column_renderer';
import { RowRenderer } from './renderers/row_renderer';
import { Sort } from './sort';
Expand Down Expand Up @@ -146,15 +145,15 @@ export const Body = React.memo<BodyProps>(

return (
<>
{showGraphView(graphEventId) && (
{graphEventId && (
<GraphOverlay bodyHeight={height} graphEventId={graphEventId} timelineId={id} />
)}
<TimelineBody
data-test-subj="timeline-body"
data-timeline-id={id}
bodyHeight={height}
ref={containerElementRef}
visible={show && !showGraphView(graphEventId)}
visible={show && !graphEventId}
>
<EventsTable data-test-subj="events-table" columnWidths={columnWidths}>
<ColumnHeaders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import React from 'react';
import { FilterManager, IIndexPattern } from 'src/plugins/data/public';
import deepEqual from 'fast-deep-equal';

import { showGraphView } from '../body/helpers';
import { DataProviders } from '../data_providers';
import { DataProvider } from '../data_providers/data_provider';
import {
Expand Down Expand Up @@ -80,7 +79,7 @@ const TimelineHeaderComponent: React.FC<Props> = ({
size="s"
/>
)}
{show && !showGraphView(graphEventId) && (
{show && !graphEventId && (
<>
<DataProviders
browserFields={browserFields}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,34 @@ describe('Timeline', () => {
'All events'
);
});

it('it shows the timeline footer', () => {
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mocks}>
<TimelineComponent {...props} />
</MockedProvider>
</TestProviders>
);

expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(true);
});
describe('when there is a graphEventId', () => {
beforeEach(() => {
props.graphEventId = 'graphEventId'; // any string w/ length > 0 works
});
it('should not show the timeline footer', () => {
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mocks}>
<TimelineComponent {...props} />
</MockedProvider>
</TestProviders>
);

expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(false);
});
});
});

describe('event wire up', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,27 +282,33 @@ export const TimelineComponent: React.FC<Props> = ({
toggleColumn={toggleColumn}
/>
</StyledEuiFlyoutBody>
<StyledEuiFlyoutFooter
data-test-subj="eui-flyout-footer"
className="timeline-flyout-footer"
>
<Footer
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
id={id}
isLive={isLive}
isLoading={loading || loadingIndexName}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
serverSideEventCount={totalCount}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
</StyledEuiFlyoutFooter>
{
/** Hide the footer if Resolver is showing. */
!graphEventId && (
<StyledEuiFlyoutFooter
data-test-subj="eui-flyout-footer"
className="timeline-flyout-footer"
>
<Footer
data-test-subj="timeline-footer"
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
id={id}
isLive={isLive}
isLoading={loading || loadingIndexName}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
serverSideEventCount={totalCount}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
</StyledEuiFlyoutFooter>
)
}
</>
);
}}
Expand Down

0 comments on commit 63c3609

Please sign in to comment.