[SIEM] Refactor hosts routing (#47459)

This commit is contained in:
patrykkopycinski 2019-10-28 14:16:49 +01:00 committed by GitHub
parent 49804a2645
commit d01099778b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 583 additions and 684 deletions

View file

@ -1,15 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`body render snapshot 1`] = `
<Component>
<Connect(HostDetailsBodyComponent)
detailName="host-1"
from={0}
isInitializing={false}
setQuery={[Function]}
to={0}
>
<Component />
</Connect(HostDetailsBodyComponent)>
</Component>
`;

View file

@ -1,99 +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 { shallow, mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import React from 'react';
import { StaticIndexPattern } from 'ui/index_patterns';
import { mockIndexPattern } from '../../../mock/index_pattern';
import { TestProviders } from '../../../mock/test_providers';
import { mockUiSettings } from '../../../mock/ui_settings';
import { CommonChildren } from '../navigation/types';
import { HostDetailsBody } from './body';
import { useKibanaCore } from '../../../lib/compose/kibana_core';
const mockUseKibanaCore = useKibanaCore as jest.Mock;
jest.mock('../../../lib/compose/kibana_core');
mockUseKibanaCore.mockImplementation(() => ({
uiSettings: mockUiSettings,
}));
jest.mock('../../../containers/source', () => ({
indicesExistOrDataTemporarilyUnavailable: () => true,
WithSource: ({
children,
}: {
children: (args: {
indicesExist: boolean;
indexPattern: StaticIndexPattern;
}) => React.ReactNode;
}) => children({ indicesExist: true, indexPattern: mockIndexPattern }),
}));
describe('body', () => {
test('render snapshot', () => {
const child: CommonChildren = () => <span>{'I am a child'}</span>;
const wrapper = shallow(
<TestProviders>
<HostDetailsBody
children={child}
from={0}
isInitializing={false}
detailName={'host-1'}
setQuery={() => {}}
to={0}
/>
</TestProviders>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it should pass expected object properties to children', () => {
const child = jest.fn();
mount(
<TestProviders>
<HostDetailsBody
children={child}
from={0}
isInitializing={false}
detailName={'host-1'}
setQuery={() => {}}
to={0}
/>
</TestProviders>
);
// match against everything but the functions to ensure they are there as expected
expect(child.mock.calls[0][0]).toMatchObject({
endDate: 0,
filterQuery:
'{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}',
skip: false,
startDate: 0,
type: 'details',
indexPattern: {
fields: [
{ name: '@timestamp', searchable: true, type: 'date', aggregatable: true },
{ name: '@version', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.id', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test1', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test2', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test3', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test4', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test5', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test6', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test7', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test8', searchable: true, type: 'string', aggregatable: true },
{ name: 'host.name', searchable: true, type: 'string', aggregatable: true },
],
title: 'filebeat-*,auditbeat-*,packetbeat-*',
},
hostName: 'host-1',
});
});
});

View file

@ -1,106 +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 { getEsQueryConfig } from '@kbn/es-query';
import React from 'react';
import { connect } from 'react-redux';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source';
import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions';
import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime';
import { Anomaly } from '../../../components/ml/types';
import { convertToBuildEsQuery } from '../../../lib/keury';
import { useKibanaCore } from '../../../lib/compose/kibana_core';
import { HostDetailsBodyComponentProps } from './types';
import { type, makeMapStateToProps } from './utils';
const HostDetailsBodyComponent = React.memo<HostDetailsBodyComponentProps>(
({
children,
deleteQuery,
detailName,
filters,
from,
isInitializing,
query,
setAbsoluteRangeDatePicker,
setQuery,
to,
}) => {
const core = useKibanaCore();
return (
<WithSource sourceId="default">
{({ indicesExist, indexPattern }) => {
const filterQuery = convertToBuildEsQuery({
config: getEsQueryConfig(core.uiSettings),
indexPattern,
queries: [query],
filters: [
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'host.name',
value: detailName,
params: {
query: detailName,
},
},
query: {
match: {
'host.name': {
query: detailName,
type: 'phrase',
},
},
},
},
...filters,
],
});
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<>
{children({
deleteQuery,
endDate: to,
filterQuery,
skip: isInitializing,
setQuery,
startDate: from,
type,
indexPattern,
hostName: detailName,
narrowDateRange: (score: Anomaly, interval: string) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
},
updateDateRange: (min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
},
})}
</>
) : null;
}}
</WithSource>
);
}
);
HostDetailsBodyComponent.displayName = 'HostDetailsBodyComponent';
export const HostDetailsBody = connect(
makeMapStateToProps,
{
setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker,
}
)(HostDetailsBodyComponent);

View file

@ -0,0 +1,108 @@
/*
* 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 } from 'enzyme';
import React from 'react';
import { StaticIndexPattern } from 'ui/index_patterns';
import { MemoryRouter } from 'react-router-dom';
import { mockIndexPattern } from '../../../mock/index_pattern';
import { TestProviders } from '../../../mock/test_providers';
import { mockUiSettings } from '../../../mock/ui_settings';
import { HostDetailsTabs } from './details_tabs';
import { SetAbsoluteRangeDatePicker } from './types';
import { hostDetailsPagePath } from '../types';
import { type } from './utils';
import { useKibanaCore } from '../../../lib/compose/kibana_core';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
const mockUseKibanaCore = useKibanaCore as jest.Mock;
jest.mock('../../../lib/compose/kibana_core');
mockUseKibanaCore.mockImplementation(() => ({
uiSettings: mockUiSettings,
}));
jest.mock('../../../containers/source', () => ({
indicesExistOrDataTemporarilyUnavailable: () => true,
WithSource: ({
children,
}: {
children: (args: {
indicesExist: boolean;
indexPattern: StaticIndexPattern;
}) => React.ReactNode;
}) => children({ indicesExist: true, indexPattern: mockIndexPattern }),
}));
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar
jest.mock('../../../components/search_bar', () => ({
SiemSearchBar: () => null,
}));
describe('body', () => {
const scenariosMap = {
authentications: 'AuthenticationsQueryTabBody',
allHosts: 'HostsQueryTabBody',
uncommonProcesses: 'UncommonProcessQueryTabBody',
anomalies: 'AnomaliesQueryTabBody',
events: 'EventsQueryTabBody',
};
Object.entries(scenariosMap).forEach(([path, componentName]) =>
test(`it should pass expected object properties to ${componentName}`, () => {
const wrapper = mount(
<TestProviders>
<MemoryRouter initialEntries={[`/hosts/host-1/${path}`]}>
<HostDetailsTabs
from={0}
isInitializing={false}
detailName={'host-1'}
setQuery={() => {}}
to={0}
setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker}
hostDetailsPagePath={hostDetailsPagePath}
indexPattern={mockIndexPattern}
type={type}
filterQuery='{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}'
/>
</MemoryRouter>
</TestProviders>
);
// match against everything but the functions to ensure they are there as expected
expect(wrapper.find(componentName).props()).toMatchObject({
endDate: 0,
filterQuery:
'{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}',
skip: false,
startDate: 0,
type: 'details',
indexPattern: {
fields: [
{ name: '@timestamp', searchable: true, type: 'date', aggregatable: true },
{ name: '@version', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.id', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test1', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test2', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test3', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test4', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test5', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test6', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test7', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.test8', searchable: true, type: 'string', aggregatable: true },
{ name: 'host.name', searchable: true, type: 'string', aggregatable: true },
],
title: 'filebeat-*,auditbeat-*,packetbeat-*',
},
hostName: 'host-1',
});
})
);
});

View file

@ -0,0 +1,100 @@
/*
* 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, { useCallback } from 'react';
import { Route, Switch } from 'react-router-dom';
import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime';
import { Anomaly } from '../../../components/ml/types';
import { HostsTableType } from '../../../store/hosts/model';
import { HostDetailsTabsProps } from './types';
import { type } from './utils';
import {
HostsQueryTabBody,
AuthenticationsQueryTabBody,
UncommonProcessQueryTabBody,
AnomaliesQueryTabBody,
EventsQueryTabBody,
} from '../navigation';
const HostDetailsTabs = React.memo<HostDetailsTabsProps>(
({
deleteQuery,
filterQuery,
from,
isInitializing,
detailName,
setAbsoluteRangeDatePicker,
setQuery,
to,
indexPattern,
hostDetailsPagePath,
}) => {
const narrowDateRange = useCallback(
(score: Anomaly, interval: string) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
},
[setAbsoluteRangeDatePicker, scoreIntervalToDateTime]
);
const updateDateRange = useCallback(
(min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
},
[setAbsoluteRangeDatePicker, scoreIntervalToDateTime]
);
const tabProps = {
deleteQuery,
endDate: to,
filterQuery,
skip: isInitializing,
setQuery,
startDate: from,
type,
indexPattern,
hostName: detailName,
narrowDateRange,
updateDateRange,
};
return (
<Switch>
<Route
path={`${hostDetailsPagePath}/:tabName(${HostsTableType.authentications})`}
render={() => <AuthenticationsQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostDetailsPagePath}/:tabName(${HostsTableType.hosts})`}
render={() => <HostsQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostDetailsPagePath}/:tabName(${HostsTableType.uncommonProcesses})`}
render={() => <UncommonProcessQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostDetailsPagePath}/:tabName(${HostsTableType.anomalies})`}
render={() => <AnomaliesQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostDetailsPagePath}/:tabName(${HostsTableType.events})`}
render={() => <EventsQueryTabBody {...tabProps} />}
/>
</Switch>
);
}
);
HostDetailsTabs.displayName = 'HostDetailsTabs';
export { HostDetailsTabs };

View file

@ -11,6 +11,8 @@ import { compose } from 'redux';
import { connect } from 'react-redux';
import { StickyContainer } from 'react-sticky';
import { inputsSelectors, State } from '../../../store';
import { FiltersGlobal } from '../../../components/filters_global';
import { HeaderPage } from '../../../components/header_page';
import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details';
@ -32,22 +34,19 @@ import { LastEventIndexKey } from '../../../graphql/types';
import { convertToBuildEsQuery } from '../../../lib/keury';
import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions';
import { SpyRoute } from '../../../utils/route/spy_routes';
import { HostsQueryProps } from '../hosts';
import { HostsEmptyPage } from '../hosts_empty_page';
export { HostDetailsBody } from './body';
import { navTabsHostDetails } from './nav_tabs';
import { HostDetailsComponentProps } from './types';
import { makeMapStateToProps } from './utils';
import { useKibanaCore } from '../../../lib/compose/kibana_core';
import { HostsEmptyPage } from '../hosts_empty_page';
import { navTabsHostDetails } from './nav_tabs';
import { HostDetailsComponentProps, HostDetailsProps } from './types';
import { HostDetailsTabs } from './details_tabs';
import { type } from './utils';
const HostOverviewManage = manageQuery(HostOverview);
const KpiHostDetailsManage = manageQuery(KpiHostsComponent);
const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
({
detailName,
filters,
from,
isInitializing,
@ -56,6 +55,9 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
setHostDetailsTablesActivePageToZero,
setQuery,
to,
detailName,
deleteQuery,
hostDetailsPagePath,
}) => {
useEffect(() => {
setHostDetailsTablesActivePageToZero(null);
@ -96,94 +98,111 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
],
});
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<StickyContainer>
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<>
<StickyContainer>
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<HeaderPage
subtitle={
<LastEventTime indexKey={LastEventIndexKey.hostDetails} hostName={detailName} />
}
title={detailName}
/>
<HostOverviewByNameQuery
sourceId="default"
hostName={detailName}
skip={isInitializing}
startDate={from}
endDate={to}
>
{({ hostOverview, loading, id, inspect, refetch }) => (
<AnomalyTableProvider
criteriaFields={hostToCriteria(hostOverview)}
startDate={from}
endDate={to}
skip={isInitializing}
>
{({ isLoadingAnomaliesData, anomaliesData }) => (
<HostOverviewManage
id={id}
inspect={inspect}
refetch={refetch}
setQuery={setQuery}
data={hostOverview}
anomaliesData={anomaliesData}
isLoadingAnomaliesData={isLoadingAnomaliesData}
loading={loading}
startDate={from}
endDate={to}
narrowDateRange={(score, interval) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
}}
/>
)}
</AnomalyTableProvider>
)}
</HostOverviewByNameQuery>
<HeaderPage
subtitle={
<LastEventTime
indexKey={LastEventIndexKey.hostDetails}
hostName={detailName}
/>
}
title={detailName}
/>
<HostOverviewByNameQuery
sourceId="default"
hostName={detailName}
skip={isInitializing}
startDate={from}
endDate={to}
>
{({ hostOverview, loading, id, inspect, refetch }) => (
<AnomalyTableProvider
criteriaFields={hostToCriteria(hostOverview)}
startDate={from}
endDate={to}
skip={isInitializing}
>
{({ isLoadingAnomaliesData, anomaliesData }) => (
<HostOverviewManage
id={id}
inspect={inspect}
refetch={refetch}
setQuery={setQuery}
data={hostOverview}
anomaliesData={anomaliesData}
isLoadingAnomaliesData={isLoadingAnomaliesData}
loading={loading}
startDate={from}
endDate={to}
narrowDateRange={(score, interval) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
}}
/>
)}
</AnomalyTableProvider>
)}
</HostOverviewByNameQuery>
<EuiHorizontalRule />
<EuiHorizontalRule />
<KpiHostDetailsQuery
sourceId="default"
<KpiHostDetailsQuery
sourceId="default"
filterQuery={filterQuery}
skip={isInitializing}
startDate={from}
endDate={to}
>
{({ kpiHostDetails, id, inspect, loading, refetch }) => (
<KpiHostDetailsManage
data={kpiHostDetails}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}}
/>
)}
</KpiHostDetailsQuery>
<SiemNavigation
navTabs={navTabsHostDetails(detailName, hasMlUserPermissions(capabilities))}
display="default"
showBorder={true}
/>
<EuiSpacer />
</StickyContainer>
<HostDetailsTabs
isInitializing={isInitializing}
deleteQuery={deleteQuery}
to={to}
from={from}
detailName={detailName}
type={type}
setQuery={setQuery}
filterQuery={filterQuery}
skip={isInitializing}
startDate={from}
endDate={to}
>
{({ kpiHostDetails, id, inspect, loading, refetch }) => (
<KpiHostDetailsManage
data={kpiHostDetails}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}}
/>
)}
</KpiHostDetailsQuery>
<SiemNavigation
navTabs={navTabsHostDetails(detailName, hasMlUserPermissions(capabilities))}
display="default"
showBorder={true}
hostDetailsPagePath={hostDetailsPagePath}
indexPattern={indexPattern}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
/>
<EuiSpacer />
</StickyContainer>
</>
) : (
<>
<HeaderPage title={detailName} />
<HostsEmptyPage />
</>
);
@ -197,7 +216,16 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
HostDetailsComponent.displayName = 'HostDetailsComponent';
export const HostDetails = compose<React.ComponentClass<HostsQueryProps & { detailName: string }>>(
export const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
return (state: State) => ({
query: getGlobalQuerySelector(state),
filters: getGlobalFiltersQuerySelector(state),
});
};
export const HostDetails = compose<React.ComponentClass<HostDetailsProps>>(
connect(
makeMapStateToProps,
{

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { StaticIndexPattern } from 'ui/index_patterns';
import { Filter } from '@kbn/es-query';
import { ActionCreator } from 'typescript-fsa';
import { Query } from 'src/plugins/data/common';
@ -11,13 +12,10 @@ import { Query } from 'src/plugins/data/common';
import { InputsModelId } from '../../../store/inputs/constants';
import { HostComponentProps } from '../../../components/link_to/redirect_to_hosts';
import { HostsTableType } from '../../../store/hosts/model';
import { HostsQueryProps } from '../hosts';
import { HostsQueryProps } from '../types';
import { NavTab } from '../../../components/navigation/types';
import {
AnomaliesChildren,
CommonChildren,
KeyHostsNavTabWithoutMlPermission,
} from '../navigation/types';
import { KeyHostsNavTabWithoutMlPermission } from '../navigation/types';
import { hostsModel } from '../../../store';
interface HostDetailsComponentReduxProps {
query: Query;
@ -31,14 +29,16 @@ interface HostBodyComponentDispatchProps {
to: number;
}>;
detailName: string;
hostDetailsPagePath: string;
}
interface HostDetailsComponentDispatchProps extends HostBodyComponentDispatchProps {
setHostDetailsTablesActivePageToZero: ActionCreator<null>;
}
export interface HostDetailsBodyProps extends HostsQueryProps {
children: CommonChildren | AnomaliesChildren;
export interface HostDetailsProps extends HostsQueryProps {
detailName: string;
hostDetailsPagePath: string;
}
export type HostDetailsComponentProps = HostDetailsComponentReduxProps &
@ -46,10 +46,6 @@ export type HostDetailsComponentProps = HostDetailsComponentReduxProps &
HostComponentProps &
HostsQueryProps;
export type HostDetailsBodyComponentProps = HostDetailsComponentReduxProps &
HostBodyComponentDispatchProps &
HostDetailsBodyProps;
type KeyHostDetailsNavTabWithoutMlPermission = HostsTableType.authentications &
HostsTableType.uncommonProcesses &
HostsTableType.events;
@ -62,3 +58,16 @@ type KeyHostDetailsNavTab =
| KeyHostDetailsNavTabWithMlPermission;
export type HostDetailsNavTab = Record<KeyHostDetailsNavTab, NavTab>;
export type HostDetailsTabsProps = HostBodyComponentDispatchProps &
HostsQueryProps & {
indexPattern: StaticIndexPattern;
type: hostsModel.HostsType;
filterQuery: string;
};
export type SetAbsoluteRangeDatePicker = ActionCreator<{
id: InputsModelId;
from: number;
to: number;
}>;

View file

@ -6,7 +6,7 @@
import { Breadcrumb } from 'ui/chrome';
import { hostsModel, inputsSelectors, State } from '../../../store';
import { hostsModel } from '../../../store';
import { HostsTableType } from '../../../store/hosts/model';
import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts';
@ -15,15 +15,6 @@ import { RouteSpyState } from '../../../utils/route/types';
export const type = hostsModel.HostsType.details;
export const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
return (state: State) => ({
query: getGlobalQuerySelector(state),
filters: getGlobalFiltersQuerySelector(state),
});
};
const TabNameMappedToI18nKey = {
[HostsTableType.hosts]: i18n.NAVIGATION_ALL_HOSTS_TITLE,
[HostsTableType.authentications]: i18n.NAVIGATION_AUTHENTICATIONS_TITLE,

View file

@ -19,7 +19,8 @@ import { wait } from '../../lib/helpers';
import { TestProviders } from '../../mock';
import { mockUiSettings } from '../../mock/ui_settings';
import { InputsModelId } from '../../store/inputs/constants';
import { Hosts, HostsComponentProps } from './hosts';
import { HostsComponentProps } from './types';
import { Hosts } from './hosts';
import { useKibanaCore } from '../../lib/compose/kibana_core';
jest.mock('../../lib/settings/use_kibana_ui_setting');
@ -97,6 +98,7 @@ describe('Hosts - rendering', () => {
}>,
query: { query: '', language: 'kuery' },
filters: [],
hostsPagePath: '',
};
beforeAll(() => {

View file

@ -5,13 +5,11 @@
*/
import { EuiSpacer } from '@elastic/eui';
import { Filter, getEsQueryConfig } from '@kbn/es-query';
import { getEsQueryConfig } from '@kbn/es-query';
import * as React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { StickyContainer } from 'react-sticky';
import { ActionCreator } from 'typescript-fsa';
import { Query } from 'src/plugins/data/common';
import { FiltersGlobal } from '../../components/filters_global';
import { GlobalTimeArgs } from '../../containers/global_time';
@ -27,41 +25,34 @@ import { SiemSearchBar } from '../../components/search_bar';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
import { LastEventIndexKey } from '../../graphql/types';
import { convertToBuildEsQuery } from '../../lib/keury';
import { inputsSelectors, State } from '../../store';
import { inputsSelectors, State, hostsModel } from '../../store';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { InputsModelId } from '../../store/inputs/constants';
import { SpyRoute } from '../../utils/route/spy_routes';
import { useKibanaCore } from '../../lib/compose/kibana_core';
import { HostsEmptyPage } from './hosts_empty_page';
import { navTabsHosts } from './nav_tabs';
import * as i18n from './translations';
import { useKibanaCore } from '../../lib/compose/kibana_core';
import { HostsComponentProps, HostsComponentReduxProps } from './types';
import { HostsTabs } from './hosts_tabs';
const KpiHostsComponentManage = manageQuery(KpiHostsComponent);
interface HostsComponentReduxProps {
query: Query;
filters: Filter[];
}
interface HostsComponentDispatchProps {
setAbsoluteRangeDatePicker: ActionCreator<{
id: InputsModelId;
from: number;
to: number;
}>;
}
export type HostsQueryProps = GlobalTimeArgs;
export type HostsComponentProps = HostsComponentReduxProps &
HostsComponentDispatchProps &
HostsQueryProps;
const HostsComponent = React.memo<HostsComponentProps>(
({ isInitializing, filters, from, query, setAbsoluteRangeDatePicker, setQuery, to }) => {
({
deleteQuery,
isInitializing,
filters,
from,
query,
setAbsoluteRangeDatePicker,
setQuery,
to,
hostsPagePath,
}) => {
const capabilities = React.useContext(MlCapabilitiesContext);
const core = useKibanaCore();
return (
<>
<WithSource sourceId="default">
@ -73,48 +64,62 @@ const HostsComponent = React.memo<HostsComponentProps>(
filters,
});
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<StickyContainer>
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<>
<StickyContainer>
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<HeaderPage
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
title={i18n.PAGE_TITLE}
/>
<>
<KpiHostsQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId="default"
startDate={from}
>
{({ kpiHosts, loading, id, inspect, refetch }) => (
<KpiHostsComponentManage
data={kpiHosts}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}}
/>
)}
</KpiHostsQuery>
<EuiSpacer />
<SiemNavigation
navTabs={navTabsHosts(hasMlUserPermissions(capabilities))}
display="default"
showBorder={true}
<HeaderPage
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
title={i18n.PAGE_TITLE}
/>
<EuiSpacer />
</>
</StickyContainer>
<>
<KpiHostsQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId="default"
startDate={from}
>
{({ kpiHosts, loading, id, inspect, refetch }) => (
<KpiHostsComponentManage
data={kpiHosts}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}}
/>
)}
</KpiHostsQuery>
<EuiSpacer />
<SiemNavigation
navTabs={navTabsHosts(hasMlUserPermissions(capabilities))}
display="default"
showBorder={true}
/>
<EuiSpacer />
</>
</StickyContainer>
<HostsTabs
deleteQuery={deleteQuery}
to={to}
filterQuery={filterQuery}
isInitializing={isInitializing}
setQuery={setQuery}
from={from}
type={hostsModel.HostsType.page}
indexPattern={indexPattern}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
hostsPagePath={hostsPagePath}
/>
</>
) : (
<>
<HeaderPage title={i18n.PAGE_TITLE} />
@ -142,8 +147,12 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
interface HostsProps extends GlobalTimeArgs {
hostsPagePath: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Hosts = compose<React.ComponentClass<GlobalTimeArgs>>(
export const Hosts = compose<React.ComponentClass<HostsProps>>(
connect(
makeMapStateToProps,
{

View file

@ -1,96 +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 { getEsQueryConfig } from '@kbn/es-query';
import React, { memo } from 'react';
import { connect } from 'react-redux';
import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime';
import { Anomaly } from '../../components/ml/types';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
import { convertToBuildEsQuery } from '../../lib/keury';
import { useKibanaCore } from '../../lib/compose/kibana_core';
import { hostsModel, inputsSelectors, State } from '../../store';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { HostsComponentProps } from './hosts';
import { CommonChildren, AnomaliesChildren } from './navigation/types';
interface HostsBodyComponentProps extends HostsComponentProps {
children: CommonChildren | AnomaliesChildren;
}
const HostsBodyComponent = memo<HostsBodyComponentProps>(
({
children,
deleteQuery,
filters,
from,
isInitializing,
query,
setAbsoluteRangeDatePicker,
setQuery,
to,
}) => {
const core = useKibanaCore();
return (
<WithSource sourceId="default">
{({ indicesExist, indexPattern }) => {
const filterQuery = convertToBuildEsQuery({
config: getEsQueryConfig(core.uiSettings),
indexPattern,
queries: [query],
filters,
});
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<>
{children({
deleteQuery,
endDate: to,
filterQuery,
skip: isInitializing,
setQuery,
startDate: from,
type: hostsModel.HostsType.page,
indexPattern,
narrowDateRange: (score: Anomaly, interval: string) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
},
updateDateRange: (min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
},
})}
</>
) : null;
}}
</WithSource>
);
}
);
HostsBodyComponent.displayName = 'HostsBodyComponent';
const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
const mapStateToProps = (state: State) => ({
query: getGlobalQuerySelector(state),
filters: getGlobalFiltersQuerySelector(state),
});
return mapStateToProps;
};
export const HostsBody = connect(
makeMapStateToProps,
{
setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker,
}
)(HostsBodyComponent);

View file

@ -0,0 +1,87 @@
/*
* 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, { memo } from 'react';
import { Route, Switch } from 'react-router-dom';
import { HostsTabsProps } from './types';
import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime';
import { Anomaly } from '../../components/ml/types';
import { HostsTableType } from '../../store/hosts/model';
import {
HostsQueryTabBody,
AuthenticationsQueryTabBody,
UncommonProcessQueryTabBody,
AnomaliesQueryTabBody,
EventsQueryTabBody,
} from './navigation';
const HostsTabs = memo<HostsTabsProps>(
({
deleteQuery,
filterQuery,
setAbsoluteRangeDatePicker,
to,
from,
setQuery,
isInitializing,
type,
indexPattern,
hostsPagePath,
}) => {
const tabProps = {
deleteQuery,
endDate: to,
filterQuery,
skip: isInitializing,
setQuery,
startDate: from,
type,
indexPattern,
narrowDateRange: (score: Anomaly, interval: string) => {
const fromTo = scoreIntervalToDateTime(score, interval);
setAbsoluteRangeDatePicker({
id: 'global',
from: fromTo.from,
to: fromTo.to,
});
},
updateDateRange: (min: number, max: number) => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
},
};
return (
<Switch>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.hosts})`}
render={() => <HostsQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.authentications})`}
render={() => <AuthenticationsQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.uncommonProcesses})`}
render={() => <UncommonProcessQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.anomalies})`}
render={() => <AnomaliesQueryTabBody {...tabProps} />}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.events})`}
render={() => <EventsQueryTabBody {...tabProps} />}
/>
</Switch>
);
}
);
HostsTabs.displayName = 'HostsTabs';
export { HostsTabs };

View file

@ -7,21 +7,13 @@
import React from 'react';
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
import { HostDetailsBody, HostDetails } from './details';
import {
HostsQueryTabBody,
AuthenticationsQueryTabBody,
UncommonProcessQueryTabBody,
AnomaliesQueryTabBody,
EventsQueryTabBody,
} from './navigation';
import { HostsBody } from './hosts_body';
import { HostDetails } from './details';
import { HostsTableType } from '../../store/hosts/model';
import { GlobalTime } from '../../containers/global_time';
import { SiemPageName } from '../home/types';
import { Hosts } from './hosts';
const hostsPagePath = `/:pageName(${SiemPageName.hosts})`;
import { hostsPagePath, hostDetailsPagePath } from './types';
const getHostsTabPath = (pagePath: string) =>
`${pagePath}/:tabName(` +
@ -32,7 +24,7 @@ const getHostsTabPath = (pagePath: string) =>
`${HostsTableType.events})`;
const getHostDetailsTabPath = (pagePath: string) =>
`${pagePath}/:detailName/:tabName(` +
`${hostDetailsPagePath}/:tabName(` +
`${HostsTableType.authentications}|` +
`${HostsTableType.uncommonProcesses}|` +
`${HostsTableType.anomalies}|` +
@ -47,188 +39,35 @@ export const HostsContainer = React.memo<Props>(({ url }) => (
<Route
strict
exact
path={hostsPagePath}
path={getHostsTabPath(hostsPagePath)}
render={() => (
<Route
path={hostsPagePath}
render={() => (
<>
<Hosts from={from} to={to} setQuery={setQuery} isInitializing={isInitializing} />
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={HostsQueryTabBody}
/>
</>
)}
<Hosts
hostsPagePath={hostsPagePath}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
deleteQuery={deleteQuery}
/>
)}
/>
<Route
strict
exact
path={getHostsTabPath(hostsPagePath)}
render={() => (
<>
<Hosts from={from} to={to} setQuery={setQuery} isInitializing={isInitializing} />
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.hosts})`}
render={() => (
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={HostsQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.authentications})`}
render={() => (
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={AuthenticationsQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.uncommonProcesses})`}
render={() => (
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={UncommonProcessQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.anomalies})`}
render={() => (
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={AnomaliesQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:tabName(${HostsTableType.events})`}
render={() => (
<HostsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={EventsQueryTabBody}
/>
)}
/>
</>
)}
/>
<Route
strict
exact
path={getHostDetailsTabPath(hostsPagePath)}
render={props => (
<>
<HostDetails
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
detailName={props.match.params.detailName}
/>
<Route
path={`${hostsPagePath}/:detailName/:tabName(${HostsTableType.hosts})`}
render={() => (
<HostDetailsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
children={HostsQueryTabBody}
detailName={props.match.params.detailName}
/>
)}
/>
<Route
path={`${hostsPagePath}/:detailName/:tabName(${HostsTableType.authentications})`}
render={() => (
<HostDetailsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
detailName={props.match.params.detailName}
children={AuthenticationsQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:detailName/:tabName(${HostsTableType.uncommonProcesses})`}
render={() => (
<HostDetailsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
detailName={props.match.params.detailName}
children={UncommonProcessQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:detailName/:tabName(${HostsTableType.anomalies})`}
render={() => (
<HostDetailsBody
deleteQuery={deleteQuery}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
detailName={props.match.params.detailName}
children={AnomaliesQueryTabBody}
/>
)}
/>
<Route
path={`${hostsPagePath}/:detailName/:tabName(${HostsTableType.events})`}
render={() => (
<HostDetailsBody
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
detailName={props.match.params.detailName}
children={EventsQueryTabBody}
/>
)}
/>
</>
<HostDetails
hostDetailsPagePath={hostDetailsPagePath}
detailName={props.match.params.detailName}
from={from}
to={to}
setQuery={setQuery}
isInitializing={isInitializing}
deleteQuery={deleteQuery}
/>
)}
/>
<Route
path={`${url}/:detailName`}
path={hostDetailsPagePath}
render={({
match: {
params: { detailName },
@ -237,12 +76,9 @@ export const HostsContainer = React.memo<Props>(({ url }) => (
}) => <Redirect to={`${url}/${detailName}/${HostsTableType.authentications}${search}`} />}
/>
<Route
path={`/${SiemPageName.hosts}/`}
path={`${hostsPagePath}/`}
render={({ location: { search = '' } }) => (
<Redirect
from={`/${SiemPageName.hosts}/"`}
to={`/${SiemPageName.hosts}/${HostsTableType.hosts}${search}`}
/>
<Redirect to={`/${SiemPageName.hosts}/${HostsTableType.hosts}${search}`} />
)}
/>
</Switch>

View file

@ -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 { StaticIndexPattern } from 'ui/index_patterns';
import { ActionCreator } from 'typescript-fsa';
import { Filter } from '@kbn/es-query';
import { Query } from 'src/plugins/data/common';
import { SiemPageName } from '../home/types';
import { hostsModel } from '../../store';
import { InputsModelId } from '../../store/inputs/constants';
import { GlobalTimeArgs } from '../../containers/global_time';
export const hostsPagePath = `/:pageName(${SiemPageName.hosts})`;
export const hostDetailsPagePath = `${hostsPagePath}/:detailName`;
export interface HostsComponentReduxProps {
query: Query;
filters: Filter[];
}
export interface HostsComponentDispatchProps {
setAbsoluteRangeDatePicker: ActionCreator<{
id: InputsModelId;
from: number;
to: number;
}>;
hostsPagePath: string;
}
export type HostsTabsProps = HostsComponentDispatchProps &
HostsQueryProps & {
filterQuery: string;
type: hostsModel.HostsType;
indexPattern: StaticIndexPattern;
};
export type HostsQueryProps = GlobalTimeArgs;
export type HostsComponentProps = HostsComponentReduxProps &
HostsComponentDispatchProps &
HostsQueryProps;