[SIEM] Replace AutoSizer with use-resize-observer (#56588)

This commit is contained in:
patrykkopycinski 2020-02-18 14:53:31 +01:00 committed by GitHub
parent 23306d8097
commit ad5daba2ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1139 additions and 557 deletions

View file

@ -913,6 +913,14 @@
'@types/tslib',
],
},
{
groupSlug: 'use-resize-observer',
groupName: 'use-resize-observer related packages',
packageNames: [
'use-resize-observer',
'@types/use-resize-observer',
],
},
{
groupSlug: 'uuid',
groupName: 'uuid related packages',

View file

@ -1,27 +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 { storiesOf } from '@storybook/react';
import React from 'react';
import { AutoSizer } from '..';
storiesOf('components/AutoSizer', module).add('example', () => (
<div>
<AutoSizer>
{({ measureRef, content }) => (
<div ref={measureRef} style={{ border: '1px solid tomato' }}>
<div>
{'width: '}
{content.width}
</div>
<div>
{'height: '}
{content.height}
</div>
</div>
)}
</AutoSizer>
</div>
));

View file

@ -1,182 +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 isEqual from 'lodash/fp/isEqual';
import React from 'react';
import ResizeObserver from 'resize-observer-polyfill';
interface Measurement {
width?: number;
height?: number;
}
interface Measurements {
bounds: Measurement;
content: Measurement;
windowMeasurement: Measurement;
}
interface AutoSizerProps {
detectAnyWindowResize?: boolean;
bounds?: boolean;
content?: boolean;
onResize?: (size: Measurements) => void;
children: (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: { measureRef: (instance: HTMLElement | null) => any } & Measurements
) => React.ReactNode;
}
interface AutoSizerState {
boundsMeasurement: Measurement;
contentMeasurement: Measurement;
windowMeasurement: Measurement;
}
/** A hard-fork of the `infra` `AutoSizer` ಠ_ಠ */
export class AutoSizer extends React.PureComponent<AutoSizerProps, AutoSizerState> {
public element: HTMLElement | null = null;
public resizeObserver: ResizeObserver | null = null;
public windowWidth: number = -1;
public readonly state = {
boundsMeasurement: {
height: void 0,
width: void 0,
},
contentMeasurement: {
height: void 0,
width: void 0,
},
windowMeasurement: {
height: void 0,
width: void 0,
},
};
constructor(props: AutoSizerProps) {
super(props);
if (this.props.detectAnyWindowResize) {
window.addEventListener('resize', this.updateMeasurement);
}
this.resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
if (entry.target === this.element) {
this.measure(entry);
}
});
});
}
public componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.props.detectAnyWindowResize) {
window.removeEventListener('resize', this.updateMeasurement);
}
}
public measure = (entry: ResizeObserverEntry | null) => {
if (!this.element) {
return;
}
const { content = true, bounds = false } = this.props;
const {
boundsMeasurement: previousBoundsMeasurement,
contentMeasurement: previousContentMeasurement,
windowMeasurement: previousWindowMeasurement,
} = this.state;
const boundsRect = bounds ? this.element.getBoundingClientRect() : null;
const boundsMeasurement = boundsRect
? {
height: this.element.getBoundingClientRect().height,
width: this.element.getBoundingClientRect().width,
}
: previousBoundsMeasurement;
const windowMeasurement: Measurement = {
width: window.innerWidth,
height: window.innerHeight,
};
if (
this.props.detectAnyWindowResize &&
boundsMeasurement &&
boundsMeasurement.width &&
this.windowWidth !== -1 &&
this.windowWidth > window.innerWidth
) {
const gap = this.windowWidth - window.innerWidth;
boundsMeasurement.width = boundsMeasurement.width - gap;
}
this.windowWidth = window.innerWidth;
const contentRect = content && entry ? entry.contentRect : null;
const contentMeasurement =
contentRect && entry
? {
height: entry.contentRect.height,
width: entry.contentRect.width,
}
: previousContentMeasurement;
if (
isEqual(boundsMeasurement, previousBoundsMeasurement) &&
isEqual(contentMeasurement, previousContentMeasurement) &&
isEqual(windowMeasurement, previousWindowMeasurement)
) {
return;
}
requestAnimationFrame(() => {
if (!this.resizeObserver) {
return;
}
this.setState({ boundsMeasurement, contentMeasurement, windowMeasurement });
if (this.props.onResize) {
this.props.onResize({
bounds: boundsMeasurement,
content: contentMeasurement,
windowMeasurement,
});
}
});
};
public render() {
const { children } = this.props;
const { boundsMeasurement, contentMeasurement, windowMeasurement } = this.state;
return children({
bounds: boundsMeasurement,
content: contentMeasurement,
windowMeasurement,
measureRef: this.storeRef,
});
}
private updateMeasurement = () => {
window.setTimeout(() => {
this.measure(null);
}, 0);
};
private storeRef = (element: HTMLElement | null) => {
if (this.element && this.resizeObserver) {
this.resizeObserver.unobserve(this.element);
}
if (element && this.resizeObserver) {
this.resizeObserver.observe(element);
}
this.element = element;
};
}

View file

@ -331,7 +331,7 @@ describe('AreaChart', () => {
});
it(`should render area chart`, () => {
expect(shallowWrapper.find('AutoSizer')).toHaveLength(1);
expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(1);
expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0);
});
});
@ -344,7 +344,7 @@ describe('AreaChart', () => {
});
it(`should render a chart place holder`, () => {
expect(shallowWrapper.find('AutoSizer')).toHaveLength(0);
expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(0);
expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1);
});
}

View file

@ -16,7 +16,8 @@ import {
RecursivePartial,
} from '@elastic/charts';
import { getOr, get, isNull, isNumber } from 'lodash/fp';
import { AutoSizer } from '../auto_sizer';
import useResizeObserver from 'use-resize-observer';
import { ChartPlaceHolder } from './chart_place_holder';
import { useTimeZone } from '../../lib/kibana';
import {
@ -124,35 +125,24 @@ export const AreaChartBase = React.memo(AreaChartBaseComponent);
AreaChartBase.displayName = 'AreaChartBase';
export const AreaChartComponent = ({
areaChart,
configs,
}: {
interface AreaChartComponentProps {
areaChart: ChartSeriesData[] | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}) => {
}
export const AreaChartComponent: React.FC<AreaChartComponentProps> = ({ areaChart, configs }) => {
const { ref: measureRef, width, height } = useResizeObserver<HTMLDivElement>({});
const customHeight = get('customHeight', configs);
const customWidth = get('customWidth', configs);
const chartHeight = getChartHeight(customHeight, height);
const chartWidth = getChartWidth(customWidth, width);
return checkIfAnyValidSeriesExist(areaChart) ? (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer ref={measureRef} height={getChartHeight(customHeight, height)}>
<AreaChartBase
data={areaChart}
height={getChartHeight(customHeight, height)}
width={getChartWidth(customWidth, width)}
configs={configs}
/>
</WrappedByAutoSizer>
)}
</AutoSizer>
<WrappedByAutoSizer ref={measureRef} height={chartHeight}>
<AreaChartBase data={areaChart} height={chartHeight} width={chartWidth} configs={configs} />
</WrappedByAutoSizer>
) : (
<ChartPlaceHolder
height={getChartHeight(customHeight)}
width={getChartWidth(customWidth)}
data={areaChart}
/>
<ChartPlaceHolder height={chartHeight} width={chartWidth} data={areaChart} />
);
};

View file

@ -278,7 +278,7 @@ describe.each(chartDataSets)('BarChart with valid data [%o]', data => {
});
it(`should render chart`, () => {
expect(shallowWrapper.find('AutoSizer')).toHaveLength(1);
expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(1);
expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0);
});
});
@ -290,8 +290,8 @@ describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => {
shallowWrapper = shallow(<BarChartComponent configs={mockConfig} barChart={data} />);
});
it(`should render chart holder`, () => {
expect(shallowWrapper.find('AutoSizer')).toHaveLength(0);
it(`should render a ChartPlaceHolder`, () => {
expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(0);
expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1);
});
});

View file

@ -8,9 +8,9 @@ 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';
import { useTimeZone } from '../../lib/kibana';
import { AutoSizer } from '../auto_sizer';
import { ChartPlaceHolder } from './chart_place_holder';
import {
chartDefaultSettings,
@ -99,40 +99,25 @@ export const BarChartBase = React.memo(BarChartBaseComponent);
BarChartBase.displayName = 'BarChartBase';
export const BarChartComponent = ({
barChart,
configs,
}: {
interface BarChartComponentProps {
barChart: ChartSeriesData[] | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}) => {
}
export const BarChartComponent: React.FC<BarChartComponentProps> = ({ barChart, configs }) => {
const { ref: measureRef, width, height } = useResizeObserver<HTMLDivElement>({});
const customHeight = get('customHeight', configs);
const customWidth = get('customWidth', configs);
const chartHeight = getChartHeight(customHeight, height);
const chartWidth = getChartWidth(customWidth, width);
return checkIfAnyValidSeriesExist(barChart) ? (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer ref={measureRef} height={getChartHeight(customHeight, height)}>
<BarChartBaseComponent
height={getChartHeight(customHeight, height)}
width={getChartWidth(customWidth, width)}
data={barChart}
configs={configs}
/>
</WrappedByAutoSizer>
)}
</AutoSizer>
<WrappedByAutoSizer ref={measureRef} height={chartHeight}>
<BarChartBase height={chartHeight} width={chartHeight} data={barChart} configs={configs} />
</WrappedByAutoSizer>
) : (
<ChartPlaceHolder
height={getChartHeight(customHeight)}
width={getChartWidth(customWidth)}
data={barChart}
/>
<ChartPlaceHolder height={chartHeight} width={chartWidth} data={barChart} />
);
};
BarChartComponent.displayName = 'BarChartComponent';
export const BarChart = React.memo(BarChartComponent);
BarChart.displayName = 'BarChart';

View file

@ -6,6 +6,7 @@
import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import useResizeObserver from 'use-resize-observer';
import { mockIndexPattern, TestProviders } from '../../mock';
import { wait } from '../../lib/helpers';
@ -27,6 +28,10 @@ mockUseFetchIndexPatterns.mockImplementation(() => [
},
]);
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer');
mockUseResizeObserver.mockImplementation(() => ({}));
const from = 1566943856794;
const to = 1566857456791;

View file

@ -9,13 +9,13 @@ import deepEqual from 'fast-deep-equal';
import { getOr, isEmpty, isEqual, union } from 'lodash/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import useResizeObserver from 'use-resize-observer';
import { BrowserFields } from '../../containers/source';
import { TimelineQuery } from '../../containers/timeline';
import { Direction } from '../../graphql/types';
import { useKibana } from '../../lib/kibana';
import { KqlMode } from '../../store/timeline/model';
import { AutoSizer } from '../auto_sizer';
import { HeaderSection } from '../header_section';
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
@ -95,6 +95,7 @@ const EventsViewerComponent: React.FC<Props> = ({
toggleColumn,
utilityBar,
}) => {
const { ref: measureRef, width = 0 } = useResizeObserver<HTMLDivElement>({});
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const combinedQueries = combineQueries({
@ -120,115 +121,108 @@ const EventsViewerComponent: React.FC<Props> = ({
return (
<StyledEuiPanel data-test-subj="events-viewer-panel">
<AutoSizer detectAnyWindowResize={true} content>
{({ measureRef, content: { width = 0 } }) => (
<>
<WrappedByAutoSizer ref={measureRef}>
<div
data-test-subj="events-viewer-measured"
style={{ height: '0px', width: '100%' }}
/>
</WrappedByAutoSizer>
<>
<WrappedByAutoSizer ref={measureRef}>
<div data-test-subj="events-viewer-measured" style={{ height: '0px', width: '100%' }} />
</WrappedByAutoSizer>
{combinedQueries != null ? (
<TimelineQuery
fields={queryFields}
filterQuery={combinedQueries.filterQuery}
id={id}
indexPattern={indexPattern}
limit={itemsPerPage}
sortField={{
sortFieldId: sort.columnId,
direction: sort.sortDirection as Direction,
}}
sourceId="default"
>
{({
events,
getUpdatedAt,
inspect,
loading,
loadMore,
pageInfo,
refetch,
totalCount = 0,
}) => {
const totalCountMinusDeleted =
totalCount > 0 ? totalCount - deletedEventIds.length : 0;
{combinedQueries != null ? (
<TimelineQuery
fields={queryFields}
filterQuery={combinedQueries.filterQuery}
id={id}
indexPattern={indexPattern}
limit={itemsPerPage}
sortField={{
sortFieldId: sort.columnId,
direction: sort.sortDirection as Direction,
}}
sourceId="default"
>
{({
events,
getUpdatedAt,
inspect,
loading,
loadMore,
pageInfo,
refetch,
totalCount = 0,
}) => {
const totalCountMinusDeleted =
totalCount > 0 ? totalCount - deletedEventIds.length : 0;
const subtitle = `${
i18n.SHOWING
}: ${totalCountMinusDeleted.toLocaleString()} ${timelineTypeContext.unit?.(
totalCountMinusDeleted
) ?? i18n.UNIT(totalCountMinusDeleted)}`;
const subtitle = `${
i18n.SHOWING
}: ${totalCountMinusDeleted.toLocaleString()} ${timelineTypeContext.unit?.(
totalCountMinusDeleted
) ?? i18n.UNIT(totalCountMinusDeleted)}`;
// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
return (
<>
<HeaderSection
// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
return (
<>
<HeaderSection
id={id}
subtitle={utilityBar ? undefined : subtitle}
title={timelineTypeContext?.title ?? i18n.EVENTS}
>
{headerFilterGroup}
</HeaderSection>
{utilityBar?.(refetch, totalCountMinusDeleted)}
<div
data-test-subj={`events-container-loading-${loading}`}
style={{ width: `${width}px` }}
>
<ManageTimelineContext
loading={loading}
width={width}
type={timelineTypeContext}
>
<TimelineRefetch
id={id}
subtitle={utilityBar ? undefined : subtitle}
title={timelineTypeContext?.title ?? i18n.EVENTS}
>
{headerFilterGroup}
</HeaderSection>
inputId="global"
inspect={inspect}
loading={loading}
refetch={refetch}
/>
{utilityBar?.(refetch, totalCountMinusDeleted)}
<StatefulBody
browserFields={browserFields}
data={events.filter(e => !deletedEventIds.includes(e._id))}
id={id}
isEventViewer={true}
height={height}
sort={sort}
toggleColumn={toggleColumn}
/>
<div
data-test-subj={`events-container-loading-${loading}`}
style={{ width: `${width}px` }}
>
<ManageTimelineContext
loading={loading}
width={width}
type={timelineTypeContext}
>
<TimelineRefetch
id={id}
inputId="global"
inspect={inspect}
loading={loading}
refetch={refetch}
/>
<StatefulBody
browserFields={browserFields}
data={events.filter(e => !deletedEventIds.includes(e._id))}
id={id}
isEventViewer={true}
height={height}
sort={sort}
toggleColumn={toggleColumn}
/>
<Footer
compact={isCompactFooter(width)}
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isEventViewer={true}
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)}
/>
</ManageTimelineContext>
</div>
</>
);
}}
</TimelineQuery>
) : null}
</>
)}
</AutoSizer>
<Footer
compact={isCompactFooter(width)}
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isEventViewer={true}
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)}
/>
</ManageTimelineContext>
</div>
</>
);
}}
</TimelineQuery>
) : null}
</>
</StyledEuiPanel>
);
};

View file

@ -6,6 +6,7 @@
import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import useResizeObserver from 'use-resize-observer';
import { wait } from '../../lib/helpers';
import { mockIndexPattern, TestProviders } from '../../mock';
@ -26,6 +27,10 @@ mockUseFetchIndexPatterns.mockImplementation(() => [
},
]);
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer');
mockUseResizeObserver.mockImplementation(() => ({}));
const from = 1566943856794;
const to = 1566857456791;

View file

@ -1,10 +1,793 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Timeline rendering renders correctly against snapshot 1`] = `
<AutoSizer
content={true}
detectAnyWindowResize={true}
<TimelineContainer
data-test-subj="timeline"
direction="column"
gutterSize="none"
justifyContent="flexStart"
>
<Component />
</AutoSizer>
<WrappedByAutoSizer>
<TimelineHeader
browserFields={
Object {
"agent": Object {
"fields": Object {
"agent.ephemeral_id": Object {
"aggregatable": true,
"category": "agent",
"description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.",
"example": "8a4f500f",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "agent.ephemeral_id",
"searchable": true,
"type": "string",
},
"agent.hostname": Object {
"aggregatable": true,
"category": "agent",
"description": null,
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "agent.hostname",
"searchable": true,
"type": "string",
},
"agent.id": Object {
"aggregatable": true,
"category": "agent",
"description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.",
"example": "8a4f500d",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "agent.id",
"searchable": true,
"type": "string",
},
"agent.name": Object {
"aggregatable": true,
"category": "agent",
"description": "Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.",
"example": "foo",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "agent.name",
"searchable": true,
"type": "string",
},
},
},
"auditd": Object {
"fields": Object {
"auditd.data.a0": Object {
"aggregatable": true,
"category": "auditd",
"description": null,
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
],
"name": "auditd.data.a0",
"searchable": true,
"type": "string",
},
"auditd.data.a1": Object {
"aggregatable": true,
"category": "auditd",
"description": null,
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
],
"name": "auditd.data.a1",
"searchable": true,
"type": "string",
},
"auditd.data.a2": Object {
"aggregatable": true,
"category": "auditd",
"description": null,
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
],
"name": "auditd.data.a2",
"searchable": true,
"type": "string",
},
},
},
"base": Object {
"fields": Object {
"@timestamp": Object {
"aggregatable": true,
"category": "base",
"description": "Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.",
"example": "2016-05-23T08:05:34.853Z",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "@timestamp",
"searchable": true,
"type": "date",
},
},
},
"client": Object {
"fields": Object {
"client.address": Object {
"aggregatable": true,
"category": "client",
"description": "Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "client.address",
"searchable": true,
"type": "string",
},
"client.bytes": Object {
"aggregatable": true,
"category": "client",
"description": "Bytes sent from the client to the server.",
"example": "184",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "client.bytes",
"searchable": true,
"type": "number",
},
"client.domain": Object {
"aggregatable": true,
"category": "client",
"description": "Client domain.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "client.domain",
"searchable": true,
"type": "string",
},
"client.geo.country_iso_code": Object {
"aggregatable": true,
"category": "client",
"description": "Country ISO code.",
"example": "CA",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "client.geo.country_iso_code",
"searchable": true,
"type": "string",
},
},
},
"cloud": Object {
"fields": Object {
"cloud.account.id": Object {
"aggregatable": true,
"category": "cloud",
"description": "The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.",
"example": "666777888999",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "cloud.account.id",
"searchable": true,
"type": "string",
},
"cloud.availability_zone": Object {
"aggregatable": true,
"category": "cloud",
"description": "Availability zone in which this host is running.",
"example": "us-east-1c",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "cloud.availability_zone",
"searchable": true,
"type": "string",
},
},
},
"container": Object {
"fields": Object {
"container.id": Object {
"aggregatable": true,
"category": "container",
"description": "Unique container id.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "container.id",
"searchable": true,
"type": "string",
},
"container.image.name": Object {
"aggregatable": true,
"category": "container",
"description": "Name of the image the container was built on.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "container.image.name",
"searchable": true,
"type": "string",
},
"container.image.tag": Object {
"aggregatable": true,
"category": "container",
"description": "Container image tag.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "container.image.tag",
"searchable": true,
"type": "string",
},
},
},
"destination": Object {
"fields": Object {
"destination.address": Object {
"aggregatable": true,
"category": "destination",
"description": "Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "destination.address",
"searchable": true,
"type": "string",
},
"destination.bytes": Object {
"aggregatable": true,
"category": "destination",
"description": "Bytes sent from the destination to the source.",
"example": "184",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "destination.bytes",
"searchable": true,
"type": "number",
},
"destination.domain": Object {
"aggregatable": true,
"category": "destination",
"description": "Destination domain.",
"example": null,
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "destination.domain",
"searchable": true,
"type": "string",
},
"destination.ip": Object {
"aggregatable": true,
"category": "destination",
"description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.",
"example": "",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "destination.ip",
"searchable": true,
"type": "ip",
},
"destination.port": Object {
"aggregatable": true,
"category": "destination",
"description": "Port of the destination.",
"example": "",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "destination.port",
"searchable": true,
"type": "long",
},
},
},
"event": Object {
"fields": Object {
"event.end": Object {
"aggregatable": true,
"category": "event",
"description": "event.end contains the date when the event ended or when the activity was last observed.",
"example": null,
"format": "",
"indexes": Array [
"apm-*-transaction*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
"packetbeat-*",
"winlogbeat-*",
],
"name": "event.end",
"searchable": true,
"type": "date",
},
},
},
"source": Object {
"fields": Object {
"source.ip": Object {
"aggregatable": true,
"category": "source",
"description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.",
"example": "",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "source.ip",
"searchable": true,
"type": "ip",
},
"source.port": Object {
"aggregatable": true,
"category": "source",
"description": "Port of the source.",
"example": "",
"format": "",
"indexes": Array [
"auditbeat",
"filebeat",
"packetbeat",
],
"name": "source.port",
"searchable": true,
"type": "long",
},
},
},
}
}
dataProviders={
Array [
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 1",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 2",
"kqlQuery": "",
"name": "Provider 2",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 2",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 3",
"kqlQuery": "",
"name": "Provider 3",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 3",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 4",
"kqlQuery": "",
"name": "Provider 4",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 4",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 5",
"kqlQuery": "",
"name": "Provider 5",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 5",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 6",
"kqlQuery": "",
"name": "Provider 6",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 6",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 7",
"kqlQuery": "",
"name": "Provider 7",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 7",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 8",
"kqlQuery": "",
"name": "Provider 8",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 8",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 9",
"kqlQuery": "",
"name": "Provider 9",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 9",
},
},
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "id-Provider 10",
"kqlQuery": "",
"name": "Provider 10",
"queryMatch": Object {
"field": "name",
"operator": ":",
"value": "Provider 10",
},
},
]
}
id="foo"
indexPattern={
Object {
"fields": Array [
Object {
"aggregatable": true,
"name": "@timestamp",
"searchable": true,
"type": "date",
},
Object {
"aggregatable": true,
"name": "@version",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.ephemeral_id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.hostname",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test1",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test2",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test3",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test4",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test5",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test6",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test7",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test8",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "host.name",
"searchable": true,
"type": "string",
},
],
"title": "filebeat-*,auditbeat-*,packetbeat-*",
}
}
onChangeDataProviderKqlQuery={[MockFunction]}
onChangeDroppableAndProvider={[MockFunction]}
onDataProviderEdited={[MockFunction]}
onDataProviderRemoved={[MockFunction]}
onToggleDataProviderEnabled={[MockFunction]}
onToggleDataProviderExcluded={[MockFunction]}
show={true}
showCallOutUnauthorizedMsg={false}
sort={
Object {
"columnId": "@timestamp",
"sortDirection": "desc",
}
}
/>
</WrappedByAutoSizer>
<Connect(Component)
id="foo"
indexPattern={
Object {
"fields": Array [
Object {
"aggregatable": true,
"name": "@timestamp",
"searchable": true,
"type": "date",
},
Object {
"aggregatable": true,
"name": "@version",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.ephemeral_id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.hostname",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test1",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test2",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test3",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test4",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test5",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test6",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test7",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test8",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "host.name",
"searchable": true,
"type": "string",
},
],
"title": "filebeat-*,auditbeat-*,packetbeat-*",
}
}
inputId="timeline"
/>
<Connect(Component)
eventType="raw"
fields={
Array [
"@timestamp",
"event.severity",
"event.category",
"event.action",
"host.name",
"source.ip",
"destination.ip",
"destination.bytes",
"user.name",
"_id",
"message",
]
}
filterQuery="{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 2\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 3\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 4\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 5\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 6\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 7\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 8\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 9\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 10\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"gte\\":1521830963132}}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"lte\\":1521862432253}}}],\\"minimum_should_match\\":1}}]}}]}}],\\"should\\":[],\\"must_not\\":[]}}"
id="foo"
indexToAdd={Array []}
limit={5}
sortField={
Object {
"direction": "desc",
"sortFieldId": "@timestamp",
}
}
sourceId="default"
>
<Component />
</Connect(Component)>
</TimelineContainer>
`;

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import useResizeObserver from 'use-resize-observer';
import { timelineQuery } from '../../containers/timeline/index.gql_query';
import { mockBrowserFields } from '../../containers/source/mock';
@ -29,6 +30,10 @@ const testFlyoutHeight = 980;
jest.mock('../../lib/kibana');
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer');
mockUseResizeObserver.mockImplementation(() => ({}));
describe('Timeline', () => {
const sort: Sort = {
columnId: '@timestamp',

View file

@ -8,13 +8,13 @@ import { EuiFlexGroup } from '@elastic/eui';
import { getOr, isEmpty } from 'lodash/fp';
import React from 'react';
import styled from 'styled-components';
import useResizeObserver from 'use-resize-observer';
import { BrowserFields } from '../../containers/source';
import { TimelineQuery } from '../../containers/timeline';
import { Direction } from '../../graphql/types';
import { useKibana } from '../../lib/kibana';
import { KqlMode, EventType } from '../../store/timeline/model';
import { AutoSizer } from '../auto_sizer';
import { ColumnHeader } from './body/column_headers/column_header';
import { defaultHeaders } from './body/column_headers/default_headers';
import { Sort } from './body/sort';
@ -88,7 +88,7 @@ interface Props {
}
/** The parent Timeline component */
export const TimelineComponent = ({
export const TimelineComponent: React.FC<Props> = ({
browserFields,
columns,
dataProviders,
@ -118,7 +118,10 @@ export const TimelineComponent = ({
start,
sort,
toggleColumn,
}: Props) => {
}) => {
const { ref: measureRef, width = 0, height: timelineHeaderHeight = 0 } = useResizeObserver<
HTMLDivElement
>({});
const kibana = useKibana();
const combinedQueries = combineQueries({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
@ -132,101 +135,98 @@ export const TimelineComponent = ({
end,
});
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
return (
<AutoSizer detectAnyWindowResize={true} content>
{({ measureRef, content: { height: timelineHeaderHeight = 0, width = 0 } }) => (
<TimelineContainer
data-test-subj="timeline"
direction="column"
gutterSize="none"
justifyContent="flexStart"
<TimelineContainer
data-test-subj="timeline"
direction="column"
gutterSize="none"
justifyContent="flexStart"
>
<WrappedByAutoSizer ref={measureRef as React.RefObject<HTMLDivElement>}>
<TimelineHeader
browserFields={browserFields}
id={id}
indexPattern={indexPattern}
dataProviders={dataProviders}
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
onDataProviderEdited={onDataProviderEdited}
onDataProviderRemoved={onDataProviderRemoved}
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
show={show}
showCallOutUnauthorizedMsg={showCallOutUnauthorizedMsg}
sort={sort}
/>
</WrappedByAutoSizer>
<TimelineKqlFetch id={id} indexPattern={indexPattern} inputId="timeline" />
{combinedQueries != null ? (
<TimelineQuery
eventType={eventType}
id={id}
indexToAdd={indexToAdd}
fields={columnsHeader.map(c => c.id)}
sourceId="default"
limit={itemsPerPage}
filterQuery={combinedQueries.filterQuery}
sortField={{
sortFieldId: sort.columnId,
direction: sort.sortDirection as Direction,
}}
>
<WrappedByAutoSizer ref={measureRef}>
<TimelineHeader
browserFields={browserFields}
id={id}
indexPattern={indexPattern}
dataProviders={dataProviders}
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
onDataProviderEdited={onDataProviderEdited}
onDataProviderRemoved={onDataProviderRemoved}
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
show={show}
showCallOutUnauthorizedMsg={showCallOutUnauthorizedMsg}
sort={sort}
/>
</WrappedByAutoSizer>
<TimelineKqlFetch id={id} indexPattern={indexPattern} inputId="timeline" />
{combinedQueries != null ? (
<TimelineQuery
eventType={eventType}
id={id}
indexToAdd={indexToAdd}
fields={columnsHeader.map(c => c.id)}
sourceId="default"
limit={itemsPerPage}
filterQuery={combinedQueries.filterQuery}
sortField={{
sortFieldId: sort.columnId,
direction: sort.sortDirection as Direction,
}}
>
{({
events,
inspect,
loading,
totalCount,
pageInfo,
loadMore,
getUpdatedAt,
refetch,
}) => (
<ManageTimelineContext loading={loading || loadingIndexName} width={width}>
<TimelineRefetch
id={id}
inputId="timeline"
inspect={inspect}
loading={loading}
refetch={refetch}
/>
<StatefulBody
browserFields={browserFields}
data={events}
id={id}
height={calculateBodyHeight({
flyoutHeight,
flyoutHeaderHeight,
timelineHeaderHeight,
timelineFooterHeight: footerHeight,
})}
sort={sort}
toggleColumn={toggleColumn}
/>
<Footer
serverSideEventCount={totalCount}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isLive={isLive}
isLoading={loading || loadingIndexName}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
getUpdatedAt={getUpdatedAt}
compact={isCompactFooter(width)}
/>
</ManageTimelineContext>
)}
</TimelineQuery>
) : null}
</TimelineContainer>
)}
</AutoSizer>
{({
events,
inspect,
loading,
totalCount,
pageInfo,
loadMore,
getUpdatedAt,
refetch,
}) => (
<ManageTimelineContext loading={loading || loadingIndexName} width={width}>
<TimelineRefetch
id={id}
inputId="timeline"
inspect={inspect}
loading={loading}
refetch={refetch}
/>
<StatefulBody
browserFields={browserFields}
data={events}
id={id}
height={calculateBodyHeight({
flyoutHeight,
flyoutHeaderHeight,
timelineHeaderHeight,
timelineFooterHeight: footerHeight,
})}
sort={sort}
toggleColumn={toggleColumn}
/>
<Footer
serverSideEventCount={totalCount}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isLive={isLive}
isLoading={loading || loadingIndexName}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
getUpdatedAt={getUpdatedAt}
compact={isCompactFooter(width)}
/>
</ManageTimelineContext>
)}
</TimelineQuery>
) : null}
</TimelineContainer>
);
};

View file

@ -7,8 +7,8 @@
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import styled from 'styled-components';
import useResizeObserver from 'use-resize-observer';
import { AutoSizer } from '../../components/auto_sizer';
import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper';
import { Flyout, flyoutHeaderHeight } from '../../components/flyout';
import { HeaderGlobal } from '../../components/header_global';
@ -61,96 +61,91 @@ const calculateFlyoutHeight = ({
windowHeight: number;
}): number => Math.max(0, windowHeight - globalHeaderSize);
export const HomePage: React.FC = () => (
<AutoSizer detectAnyWindowResize={true} content>
{({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => (
<WrappedByAutoSizer data-test-subj="wrapped-by-auto-sizer" ref={measureRef}>
<HeaderGlobal />
export const HomePage: React.FC = () => {
const { ref: measureRef, height: windowHeight = 0 } = useResizeObserver<HTMLDivElement>({});
const flyoutHeight = calculateFlyoutHeight({
globalHeaderSize: globalHeaderHeightPx,
windowHeight,
});
<Main data-test-subj="pageContainer">
<WithSource sourceId="default">
{({ browserFields, indexPattern, indicesExist }) => (
<DragDropContextWrapper browserFields={browserFields}>
<UseUrlState indexPattern={indexPattern} navTabs={navTabs} />
{indicesExistOrDataTemporarilyUnavailable(indicesExist) && (
<>
<AutoSaveWarningMsg />
<Flyout
flyoutHeight={calculateFlyoutHeight({
globalHeaderSize: globalHeaderHeightPx,
windowHeight,
})}
headerHeight={flyoutHeaderHeight}
timelineId="timeline-1"
usersViewing={usersViewing}
>
<StatefulTimeline
flyoutHeaderHeight={flyoutHeaderHeight}
flyoutHeight={calculateFlyoutHeight({
globalHeaderSize: globalHeaderHeightPx,
windowHeight,
})}
id="timeline-1"
/>
</Flyout>
</>
)}
return (
<WrappedByAutoSizer data-test-subj="wrapped-by-auto-sizer" ref={measureRef}>
<HeaderGlobal />
<Switch>
<Redirect exact from="/" to={`/${SiemPageName.overview}`} />
<Route
path={`/:pageName(${SiemPageName.overview})`}
render={() => <Overview />}
/>
<Route
path={`/:pageName(${SiemPageName.hosts})`}
render={({ match }) => <HostsContainer url={match.url} />}
/>
<Route
path={`/:pageName(${SiemPageName.network})`}
render={({ location, match }) => (
<NetworkContainer location={location} url={match.url} />
)}
/>
<Route
path={`/:pageName(${SiemPageName.detections})`}
render={({ location, match }) => (
<DetectionEngineContainer location={location} url={match.url} />
)}
/>
<Route
path={`/:pageName(${SiemPageName.timelines})`}
render={() => <Timelines />}
/>
<Route path="/link-to" render={props => <LinkToPage {...props} />} />
<Route
path="/ml-hosts"
render={({ location, match }) => (
<MlHostConditionalContainer location={location} url={match.url} />
)}
/>
<Route
path="/ml-network"
render={({ location, match }) => (
<MlNetworkConditionalContainer location={location} url={match.url} />
)}
/>
<Route path={`/:pageName(${SiemPageName.case})`}>
<Case />
</Route>
<Route render={() => <NotFoundPage />} />
</Switch>
</DragDropContextWrapper>
)}
</WithSource>
</Main>
<Main data-test-subj="pageContainer">
<WithSource sourceId="default">
{({ browserFields, indexPattern, indicesExist }) => (
<DragDropContextWrapper browserFields={browserFields}>
<UseUrlState indexPattern={indexPattern} navTabs={navTabs} />
{indicesExistOrDataTemporarilyUnavailable(indicesExist) && (
<>
<AutoSaveWarningMsg />
<Flyout
flyoutHeight={flyoutHeight}
headerHeight={flyoutHeaderHeight}
timelineId="timeline-1"
usersViewing={usersViewing}
>
<StatefulTimeline
flyoutHeaderHeight={flyoutHeaderHeight}
flyoutHeight={flyoutHeight}
id="timeline-1"
/>
</Flyout>
</>
)}
<HelpMenu />
<Switch>
<Redirect exact from="/" to={`/${SiemPageName.overview}`} />
<Route path={`/:pageName(${SiemPageName.overview})`} render={() => <Overview />} />
<Route
path={`/:pageName(${SiemPageName.hosts})`}
render={({ match }) => <HostsContainer url={match.url} />}
/>
<Route
path={`/:pageName(${SiemPageName.network})`}
render={({ location, match }) => (
<NetworkContainer location={location} url={match.url} />
)}
/>
<Route
path={`/:pageName(${SiemPageName.detections})`}
render={({ location, match }) => (
<DetectionEngineContainer location={location} url={match.url} />
)}
/>
<Route
path={`/:pageName(${SiemPageName.timelines})`}
render={() => <Timelines />}
/>
<Route path="/link-to" render={props => <LinkToPage {...props} />} />
<Route
path="/ml-hosts"
render={({ location, match }) => (
<MlHostConditionalContainer location={location} url={match.url} />
)}
/>
<Route
path="/ml-network"
render={({ location, match }) => (
<MlNetworkConditionalContainer location={location} url={match.url} />
)}
/>
<Route path={`/:pageName(${SiemPageName.case})`}>
<Case />
</Route>
<Route render={() => <NotFoundPage />} />
</Switch>
</DragDropContextWrapper>
)}
</WithSource>
</Main>
<SpyRoute />
</WrappedByAutoSizer>
)}
</AutoSizer>
);
<HelpMenu />
<SpyRoute />
</WrappedByAutoSizer>
);
};
HomePage.displayName = 'HomePage';

View file

@ -7,6 +7,7 @@
import React from 'react';
import { IIndexPattern } from 'src/plugins/data/public';
import { MemoryRouter } from 'react-router-dom';
import useResizeObserver from 'use-resize-observer';
import { mockIndexPattern } from '../../../mock/index_pattern';
import { TestProviders } from '../../../mock/test_providers';
@ -35,6 +36,10 @@ jest.mock('../../../components/query_bar', () => ({
QueryBar: () => null,
}));
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer');
mockUseResizeObserver.mockImplementation(() => ({}));
describe('body', () => {
const scenariosMap = {
authentications: 'AuthenticationsQueryTabBody',

View file

@ -102,6 +102,7 @@
"@types/supertest": "^2.0.5",
"@types/tar-fs": "^1.16.1",
"@types/tinycolor2": "^1.4.1",
"@types/use-resize-observer": "^6.0.0",
"@types/uuid": "^3.4.4",
"@types/xml-crypto": "^1.4.0",
"@types/xml2js": "^0.4.5",
@ -344,6 +345,7 @@
"typescript-fsa-reducers": "^1.2.1",
"ui-select": "0.19.8",
"unstated": "^2.1.1",
"use-resize-observer": "^6.0.0",
"uuid": "3.3.2",
"venn.js": "0.2.20",
"vscode-languageserver": "^5.2.1",

View file

@ -5337,6 +5337,13 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
"@types/use-resize-observer@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/use-resize-observer/-/use-resize-observer-6.0.0.tgz#d1b162b2733b22225908a7877ca7115c67f3752e"
integrity sha512-8RD06szR+wzHpfCBFbcTEQ0OCoogoSWCyyUmWyqc5qGG2fa1sOUdwNte5dwoJWG/sh5sBU0QVZ1+9zcQGLUSVg==
dependencies:
"@types/react" "*"
"@types/uuid@^3.4.4":
version "3.4.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5"
@ -30379,6 +30386,13 @@ use-memo-one@^1.1.1:
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c"
integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==
use-resize-observer@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-6.0.0.tgz#8450ade735c386e8d93cdf983c26d34a9f478a54"
integrity sha512-Zfe1qsZVhzfgECs+L/ZcSukyVPFGOmWtC7xZI5Lpn4PR2hNgVvD1NmQ/GYBoCSV2pJskxflJWcDzpTAt84nhvw==
dependencies:
resize-observer-polyfill "^1.5.1"
use@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"