Siem inspect query (#39980)

* First query inspection on a component

* add inspect on all the query of the server side

* add inspect in all component in host and details page

* add inspect on network and ip detail page

* fix server-side unit tests

* fix unit test

* fix UI types + old unit test

* implement design/Michael suggestion

* add inspect on overview page + some clean up

* fix tooltip

* fix unit test

* clean up

* remove the fancy skipquery since it broke everything

* add timeline inspect + fade-in/out of the inspect button to avoid ink on the page

* disable inspect timeline when no data

* clean up + add unit testing

* update inspect query for Kpi widgets

* Fix api integration test

* Review II

* review II missing
This commit is contained in:
Xavier Mouligneau 2019-07-02 21:19:43 -04:00 committed by GitHub
parent 84952e0c91
commit 49c5f7b8ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
163 changed files with 4303 additions and 1011 deletions

View file

@ -56,4 +56,9 @@ export const sharedSchema = gql`
endCursor: CursorType
hasNextPage: Boolean
}
type Inspect {
dsl: [String!]!
response: [String!]!
}
`;

View file

@ -319,5 +319,5 @@ const SuggestionsPanel = euiStyled(EuiPanel).attrs({
width: 100%;
margin-top: 2px;
overflow: hidden;
z-index: ${props => props.theme.eui.euiZLevel1};;
z-index: ${props => props.theme.eui.euiZLevel1};
`;

View file

@ -10,6 +10,7 @@ import { pure } from 'recompose';
import { Dispatch } from 'redux';
import { ActionCreator } from 'typescript-fsa';
import { isEmpty, get } from 'lodash/fp';
import { History } from '../../../lib/history';
import { Note } from '../../../lib/note';
import {
@ -37,8 +38,9 @@ interface OwnProps {
interface StateReduxProps {
description: string;
getNotesByIds: (noteIds: string[]) => Note[];
isFavorite: boolean;
isDataInTimeline: boolean;
isDatepickerLocked: boolean;
isFavorite: boolean;
noteIds: string[];
title: string;
width: number;
@ -78,6 +80,7 @@ const statefulFlyoutHeader = pure<Props>(
description,
getNotesByIds,
isFavorite,
isDataInTimeline,
isDatepickerLocked,
title,
width = DEFAULT_TIMELINE_WIDTH,
@ -95,6 +98,7 @@ const statefulFlyoutHeader = pure<Props>(
createTimeline={createTimeline}
description={description}
getNotesByIds={getNotesByIds}
isDataInTimeline={isDataInTimeline}
isDatepickerLocked={isDatepickerLocked}
isFavorite={isFavorite}
title={title}
@ -121,8 +125,10 @@ const makeMapStateToProps = () => {
const timeline: TimelineModel = getTimeline(state, timelineId);
const globalInput: inputsModel.InputsRange = getGlobalInput(state);
const {
dataProviders,
description = '',
isFavorite = false,
kqlQuery,
title = '',
noteIds = [],
width = DEFAULT_TIMELINE_WIDTH,
@ -134,6 +140,8 @@ const makeMapStateToProps = () => {
description,
getNotesByIds: getNotesByIds(state),
history,
isDataInTimeline:
!isEmpty(dataProviders) || !isEmpty(get('filterQuery.kuery.expression', kqlQuery)),
isFavorite,
isDatepickerLocked: globalInput.linkTo.includes('timeline'),
noteIds,

View file

@ -9,6 +9,8 @@ import React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { InspectButton } from '../inspect';
const Header = styled.header<{ border?: boolean }>`
${props => `
margin-bottom: ${props.theme.eui.euiSizeL};
@ -24,28 +26,37 @@ const Header = styled.header<{ border?: boolean }>`
export interface HeaderPanelProps {
border?: boolean;
children?: React.ReactNode;
id?: string;
subtitle?: string | React.ReactNode;
showInspect?: boolean;
title: string | React.ReactNode;
tooltip?: string;
}
export const HeaderPanel = pure<HeaderPanelProps>(
({ border, children, subtitle, title, tooltip }) => (
({ border, children, id, showInspect = false, subtitle, title, tooltip }) => (
<Header border={border}>
<EuiFlexGroup alignItems="center">
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem>
<EuiTitle>
<h2 data-test-subj="page_headline_title">
{title}{' '}
{tooltip && <EuiIconTip color="subdued" content={tooltip} position="top" size="l" />}
</h2>
</EuiTitle>
<EuiFlexGroup alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiTitle>
<h2 data-test-subj="page_headline_title">{title}</h2>
</EuiTitle>
</EuiFlexItem>
{tooltip && (
<EuiFlexItem grow={false}>
<EuiIconTip color="subdued" content={tooltip} position="top" size="l" />
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiText color="subdued" size="s">
{subtitle}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{id && <InspectButton queryId={id} inspectIndex={0} show={showInspect} title={title} />}
</EuiFlexItem>
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
</EuiFlexGroup>
</Header>

View file

@ -0,0 +1,173 @@
/*
* 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 * as React from 'react';
import {
TestProviderWithoutDragAndDrop,
mockGlobalState,
apolloClientObservable,
} from '../../mock';
import { createStore, State } from '../../store';
import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers';
import { InspectButton } from '.';
import { cloneDeep } from 'lodash/fp';
describe('Inspect Button', () => {
const refetch = jest.fn();
const state: State = mockGlobalState;
const newQuery: UpdateQueryParams = {
inputId: 'global',
id: 'myQuery',
inspect: null,
loading: false,
refetch,
state: state.inputs,
};
let store = createStore(state, apolloClientObservable);
describe('Render', () => {
beforeEach(() => {
const myState = cloneDeep(state);
myState.inputs = upsertQuery(newQuery);
store = createStore(myState, apolloClientObservable);
});
test('Eui Empty Button', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton queryId={newQuery.id} inputId="timeline" show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
expect(
wrapper
.find('button[data-test-subj="inspect-empty-button"]')
.first()
.exists()
).toBe(true);
});
test('Eui Icon Button', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
expect(
wrapper
.find('button[data-test-subj="inspect-icon-button"]')
.first()
.exists()
).toBe(true);
});
test('Eui Empty Button disabled', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton isDisabled={true} queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true);
});
test('Eui Icon Button disabled', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton isDisabled={true} queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true);
});
});
describe('Modal Inspect - happy path', () => {
beforeEach(() => {
const myState = cloneDeep(state);
const myQuery = cloneDeep(newQuery);
myQuery.inspect = {
dsl: ['my dsl'],
response: ['my response'],
};
myState.inputs = upsertQuery(myQuery);
store = createStore(myState, apolloClientObservable);
});
test('Open Inspect Modal', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
wrapper
.find('button[data-test-subj="inspect-icon-button"]')
.first()
.simulate('click');
wrapper.update();
expect(store.getState().inputs.global.query[0].isInspected).toBe(true);
expect(
wrapper
.find('button[data-test-subj="modal-inspect-close"]')
.first()
.exists()
).toBe(true);
});
test('Close Inspect Modal', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
wrapper
.find('button[data-test-subj="inspect-icon-button"]')
.first()
.simulate('click');
wrapper.update();
wrapper
.find('button[data-test-subj="modal-inspect-close"]')
.first()
.simulate('click');
wrapper.update();
expect(store.getState().inputs.global.query[0].isInspected).toBe(false);
expect(
wrapper
.find('button[data-test-subj="modal-inspect-close"]')
.first()
.exists()
).toBe(false);
});
test('Do not Open Inspect Modal if it is loading', () => {
const wrapper = mount(
<TestProviderWithoutDragAndDrop store={store}>
<InspectButton queryId={newQuery.id} show={true} title="My title" />
</TestProviderWithoutDragAndDrop>
);
store.getState().inputs.global.query[0].loading = true;
wrapper
.find('button[data-test-subj="inspect-icon-button"]')
.first()
.simulate('click');
wrapper.update();
expect(store.getState().inputs.global.query[0].isInspected).toBe(true);
expect(
wrapper
.find('button[data-test-subj="modal-inspect-close"]')
.first()
.exists()
).toBe(false);
});
});
});

View file

@ -0,0 +1,152 @@
/*
* 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 { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import { getOr } from 'lodash/fp';
import React from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import styled from 'styled-components';
import { pure } from 'recompose';
import { inputsModel, inputsSelectors, State } from '../../store';
import { InputsModelId } from '../../store/inputs/constants';
import { inputsActions } from '../../store/inputs';
import { ModalInspectQuery } from './modal';
import * as i18n from './translations';
const InspectContainer = styled.div<{ showInspect: boolean }>`
.euiButtonIcon {
${props => (props.showInspect ? 'opacity: 1;' : 'opacity: 0')}
transition: opacity ${props => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease;
}
`;
interface OwnProps {
queryId: string;
inputId?: InputsModelId;
inspectIndex?: number;
isDisabled?: boolean;
onCloseInspect?: () => void;
show: boolean;
title: string | React.ReactElement | React.ReactNode;
}
interface InspectButtonReducer {
id: string;
isInspected: boolean;
loading: boolean;
inspect: inputsModel.InspectQuery | null;
selectedInspectIndex: number;
}
interface InspectButtonDispatch {
setIsInspected: ActionCreator<{
id: string;
inputId: InputsModelId;
isInspected: boolean;
selectedInspectIndex: number;
}>;
}
type InspectButtonProps = OwnProps & InspectButtonReducer & InspectButtonDispatch;
const InspectButtonComponent = pure<InspectButtonProps>(
({
inputId = 'global',
inspect,
isDisabled,
isInspected,
loading,
inspectIndex = 0,
onCloseInspect,
queryId = '',
selectedInspectIndex,
setIsInspected,
show,
title = '',
}: InspectButtonProps) => (
<InspectContainer showInspect={show}>
{inputId === 'timeline' && (
<EuiButtonEmpty
aria-label={i18n.INSPECT}
data-test-subj="inspect-empty-button"
color="text"
iconSide="left"
iconType="inspect"
isDisabled={loading || isDisabled}
isLoading={loading}
onClick={() => {
setIsInspected({
id: queryId,
inputId,
isInspected: true,
selectedInspectIndex: inspectIndex,
});
}}
>
{i18n.INSPECT}
</EuiButtonEmpty>
)}
{inputId === 'global' && (
<EuiButtonIcon
aria-label={i18n.INSPECT}
className={show ? '' : ''}
data-test-subj="inspect-icon-button"
iconSize="m"
iconType="inspect"
isDisabled={loading || isDisabled}
title={i18n.INSPECT}
onClick={() => {
setIsInspected({
id: queryId,
inputId,
isInspected: true,
selectedInspectIndex: inspectIndex,
});
}}
/>
)}
<ModalInspectQuery
closeModal={() => {
if (onCloseInspect != null) {
onCloseInspect();
}
setIsInspected({
id: queryId,
inputId,
isInspected: false,
selectedInspectIndex: inspectIndex,
});
}}
isShowing={!loading && selectedInspectIndex === inspectIndex && isInspected}
request={inspect != null && inspect.dsl.length > 0 ? inspect.dsl[inspectIndex] : null}
response={
inspect != null && inspect.response.length > 0 ? inspect.response[inspectIndex] : null
}
title={title}
data-test-subj="inspect-modal"
/>
</InspectContainer>
)
);
const makeMapStateToProps = () => {
const getGlobalQuery = inputsSelectors.globalQueryByIdSelector();
const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector();
const mapStateToProps = (state: State, { inputId = 'global', queryId }: OwnProps) => {
return inputId === 'global' ? getGlobalQuery(state, queryId) : getTimelineQuery(state, queryId);
};
return mapStateToProps;
};
export const InspectButton = connect(
makeMapStateToProps,
{
setIsInspected: inputsActions.setInspectionParameter,
}
)(InspectButtonComponent);

View file

@ -0,0 +1,163 @@
/*
* 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 { ModalInspectQuery } from './modal';
describe('Modal Inspect', () => {
const closeModal = jest.fn();
describe('rendering', () => {
test('when isShowing is positive and request and response are not null', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request="My request"
response="My response"
title="My title"
/>
);
expect(
wrapper
.find('[data-test-subj="modal-inspect-euiModal"]')
.first()
.exists()
).toBe(true);
expect(
wrapper
.find('.euiModalHeader__title')
.first()
.text()
).toBe('Inspect My title');
});
test('when isShowing is negative and request and response are not null', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={false}
request="My request"
response="My response"
title="My title"
/>
);
expect(
wrapper
.find('[data-test-subj="modal-inspect-euiModal"]')
.first()
.exists()
).toBe(false);
});
test('when isShowing is positive and request is null and response is not null', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request={null}
response="My response"
title="My title"
/>
);
expect(
wrapper
.find('[data-test-subj="modal-inspect-euiModal"]')
.first()
.exists()
).toBe(false);
});
test('when isShowing is positive and request is not null and response is null', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request="My request"
response={null}
title="My title"
/>
);
expect(
wrapper
.find('[data-test-subj="modal-inspect-euiModal"]')
.first()
.exists()
).toBe(false);
});
});
describe('functionality from tab request/response', () => {
test('Click on request Tab', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request="My request"
response="My response"
title="My title"
/>
);
wrapper
.find('.euiTab')
.first()
.simulate('click');
wrapper.update();
expect(
wrapper
.find('.euiCodeBlock')
.first()
.text()
).toBe('My request');
});
test('Click on response Tab', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request="My request"
response="My response"
title="My title"
/>
);
wrapper
.find('.euiTab')
.at(1)
.simulate('click');
wrapper.update();
expect(
wrapper
.find('.euiCodeBlock')
.first()
.text()
).toBe('My response');
});
});
describe('events', () => {
test('Make sure that toggle function has been called when you click on the close button', () => {
const wrapper = mount(
<ModalInspectQuery
closeModal={closeModal}
isShowing={true}
request="My request"
response="my response"
title="My title"
/>
);
wrapper.find('button[data-test-subj="modal-inspect-close"]').simulate('click');
wrapper.update();
expect(closeModal).toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,114 @@
/*
* 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 {
EuiButton,
EuiCodeBlock,
EuiModal,
EuiModalBody,
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
EuiOverlayMask,
EuiSpacer,
EuiTabbedContent,
} from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
interface ModalInspectProps {
closeModal: () => void;
isShowing: boolean;
request: string | null;
response: string | null;
title: string | React.ReactElement | React.ReactNode;
}
const MyEuiModal = styled(EuiModal)`
.euiModal__flex {
width: 60vw;
}
.euiCodeBlock {
height: auto !important;
max-width: 718px;
}
`;
export const ModalInspectQuery = ({
closeModal,
isShowing = false,
request,
response,
title,
}: ModalInspectProps) => {
if (!isShowing || request == null || response == null) {
return null;
}
const tabs = [
{
id: 'request',
name: 'Request',
content: (
<>
<EuiSpacer />
<EuiCodeBlock
language="js"
fontSize="m"
paddingSize="m"
color="dark"
overflowHeight={300}
isCopyable
>
{request}
</EuiCodeBlock>
</>
),
},
{
id: 'response',
name: 'Response',
content: (
<>
<EuiSpacer />
<EuiCodeBlock
language="js"
fontSize="m"
paddingSize="m"
color="dark"
overflowHeight={300}
isCopyable
>
{response}
</EuiCodeBlock>
</>
),
},
];
return (
<EuiOverlayMask>
<MyEuiModal onClose={closeModal} data-test-subj="modal-inspect-euiModal">
<EuiModalHeader>
<EuiModalHeaderTitle>
{i18n.INSPECT} {title}
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[1]} autoFocus="selected" />
</EuiModalBody>
<EuiModalFooter>
<EuiButton onClick={closeModal} fill data-test-subj="modal-inspect-close">
{i18n.CLOSE}
</EuiButton>
</EuiModalFooter>
</MyEuiModal>
</EuiOverlayMask>
);
};

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export const INSPECT = i18n.translate('xpack.siem.inspectDescription', {
defaultMessage: 'Inspect',
});
export const CLOSE = i18n.translate('xpack.siem.inspect.modal.closeTitle', {
defaultMessage: 'Close',
});

View file

@ -81,6 +81,7 @@ interface BasicTableProps<T, U = T, V = T, W = T, X = T, Y = T, Z = T, AA = T, A
headerTitle: string | React.ReactElement;
headerTooltip?: string;
headerUnit: string | React.ReactElement;
id?: string;
itemsPerRow?: ItemsPerRow[];
limit: number;
loading: boolean;
@ -97,6 +98,7 @@ interface BasicTableState {
isEmptyTable: boolean;
isPopoverOpen: boolean;
paginationLoading: boolean;
showInspect: boolean;
}
type Func<T> = (arg: T) => string | number;
@ -120,6 +122,7 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
isEmptyTable: this.props.pageOfItems.length === 0,
isPopoverOpen: false,
paginationLoading: false,
showInspect: false,
};
static getDerivedStateFromProps<T, U, V, W, X, Y, Z, AA, AB>(
@ -145,6 +148,7 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
headerTitle,
headerTooltip,
headerUnit,
id,
itemsPerRow,
limit,
loading,
@ -196,7 +200,11 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
</EuiContextMenuItem>
));
return (
<EuiPanel data-test-subj={dataTestSubj}>
<EuiPanel
data-test-subj={dataTestSubj}
onMouseEnter={this.mouseEnter}
onMouseLeave={this.mouseLeave}
>
<BasicTableContainer>
{loading && (
<>
@ -213,7 +221,9 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
)}
<HeaderPanel
subtitle={`${i18n.SHOWING}: ${headerCount.toLocaleString()} ${headerUnit}`}
id={id}
showInspect={this.state.showInspect}
subtitle={<>{`${i18n.SHOWING}: ${headerCount.toLocaleString()} ${headerUnit}`}</>}
title={headerTitle}
tooltip={headerTooltip}
>
@ -287,6 +297,20 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
);
}
private mouseEnter = () => {
this.setState(prevState => ({
...prevState,
showInspect: true,
}));
};
private mouseLeave = () => {
this.setState(prevState => ({
...prevState,
showInspect: false,
}));
};
private onButtonClick = () => {
this.setState(prevState => ({
...prevState,

View file

@ -101,6 +101,7 @@ exports[`Authentication Table Component rendering it renders the authentication
]
}
hasNextPage={true}
id="authentication"
loadMore={[MockFunction]}
loading={false}
nextCursor="aa7ca589f1b8220002f2fc61c64cfbf1"

View file

@ -34,12 +34,13 @@ describe('Authentication Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<AuthenticationTable
loading={false}
data={mockData.Authentications.edges}
totalCount={mockData.Authentications.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Authentications.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Authentications.pageInfo)}
id="authentication"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.Authentications.pageInfo)}
totalCount={mockData.Authentications.totalCount}
type={hostsModel.HostsType.page}
/>
</ReduxStoreProvider>

View file

@ -29,6 +29,7 @@ import { getRowItemDraggables } from '../../../tables/helpers';
interface OwnProps {
data: AuthenticationsEdges[];
loading: boolean;
id: string;
hasNextPage: boolean;
nextCursor: string;
totalCount: number;
@ -71,6 +72,7 @@ const AuthenticationTableComponent = pure<AuthenticationTableProps>(
({
data,
hasNextPage,
id,
limit,
loading,
loadMore,
@ -85,6 +87,7 @@ const AuthenticationTableComponent = pure<AuthenticationTableProps>(
headerCount={totalCount}
headerTitle={i18n.AUTHENTICATIONS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -137,6 +137,7 @@ exports[`Load More Events Table Component rendering it renders the events table
]
}
hasNextPage={true}
id="events"
loadMore={[MockFunction]}
loading={false}
nextCursor="1546878704036"

View file

@ -34,13 +34,14 @@ describe('Load More Events Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<EventsTable
loading={false}
data={mockData.Events.edges.map(i => i.node)}
totalCount={mockData.Events.totalCount}
tiebreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)!}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)}
id="events"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)}
tiebreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)!}
totalCount={mockData.Events.totalCount}
type={hostsModel.HostsType.page}
/>
</ReduxStoreProvider>

View file

@ -27,6 +27,7 @@ interface OwnProps {
data: Ecs[];
loading: boolean;
hasNextPage: boolean;
id: string;
nextCursor: string;
tiebreaker: string;
totalCount: number;
@ -67,6 +68,7 @@ const EventsTableComponent = pure<EventsTableProps>(
({
data,
hasNextPage,
id,
limit,
loading,
loadMore,
@ -82,6 +84,7 @@ const EventsTableComponent = pure<EventsTableProps>(
headerCount={totalCount}
headerTitle={i18n.EVENTS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -194,6 +194,7 @@ exports[`Host Summary Component rendering it renders the default Host Summary 1`
}
}
endDate={1560837600000}
id="hostOverview"
isLoadingAnomaliesData={false}
loading={false}
narrowDateRange={[MockFunction]}

View file

@ -40,13 +40,14 @@ describe('Host Summary Component', () => {
const wrapper = shallow(
<TestProviders>
<HostOverview
startDate={new Date('2019-06-15T06:00:00.000Z').valueOf()}
endDate={new Date('2019-06-18T06:00:00.000Z').valueOf()}
loading={false}
data={mockData.Hosts.edges[0].node}
anomaliesData={mockAnomalies}
data={mockData.Hosts.edges[0].node}
endDate={new Date('2019-06-18T06:00:00.000Z').valueOf()}
id="hostOverview"
isLoadingAnomaliesData={false}
loading={false}
narrowDateRange={jest.fn()}
startDate={new Date('2019-06-15T06:00:00.000Z').valueOf()}
/>
</TestProviders>
);

View file

@ -6,26 +6,28 @@
import { EuiDescriptionList, EuiFlexItem } from '@elastic/eui';
import { getOr } from 'lodash/fp';
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import styled from 'styled-components';
import { DescriptionList } from '../../../../../common/utility_types';
import { HostItem } from '../../../../graphql/types';
import { getEmptyTagValue } from '../../../empty_value';
import * as i18n from './translations';
import { FirstLastSeenHost, FirstLastSeenHostType } from '../first_last_seen_host';
import { DefaultFieldRenderer, hostIdRenderer } from '../../../field_renderers/field_renderers';
import { InspectButton } from '../../../inspect';
import { HostItem } from '../../../../graphql/types';
import { LoadingPanel } from '../../../loading';
import { LoadingOverlay, OverviewWrapper } from '../../index';
import { IPDetailsLink } from '../../../links';
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
import { Anomalies, NarrowDateRange } from '../../../ml/types';
import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider';
import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions';
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
import { Anomalies, NarrowDateRange } from '../../../ml/types';
import { LoadingOverlay, OverviewWrapper } from '../../index';
import { FirstLastSeenHost, FirstLastSeenHostType } from '../first_last_seen_host';
import * as i18n from './translations';
interface HostSummaryProps {
data: HostItem;
id: string;
loading: boolean;
isLoadingAnomaliesData: boolean;
anomaliesData: Anomalies | null;
@ -52,14 +54,17 @@ export const HostOverview = React.memo<HostSummaryProps>(
({
data,
loading,
id,
startDate,
endDate,
isLoadingAnomaliesData,
anomaliesData,
narrowDateRange,
}) => {
const [showInspect, setShowInspect] = useState(false);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const getDefaultRenderer = (fieldName: string, fieldData: HostItem) => (
<DefaultFieldRenderer
rowItems={getOr([], fieldName, fieldData)}
@ -165,7 +170,10 @@ export const HostOverview = React.memo<HostSummaryProps>(
];
return (
<OverviewWrapper>
<OverviewWrapper
onMouseEnter={() => setShowInspect(true)}
onMouseLeave={() => setShowInspect(false)}
>
{loading && (
<>
<LoadingOverlay />
@ -179,6 +187,12 @@ export const HostOverview = React.memo<HostSummaryProps>(
/>
</>
)}
<InspectButton
queryId={id}
show={showInspect}
title={i18n.INSPECT_TITLE}
inspectIndex={0}
/>
{descriptionLists.map((descriptionList, index) =>
getDescriptionList(descriptionList, index)
)}

View file

@ -71,3 +71,7 @@ export const INSTANCE_ID = i18n.translate('xpack.siem.host.details.overview.inst
export const MACHINE_TYPE = i18n.translate('xpack.siem.host.details.overview.machineTypeTitle', {
defaultMessage: 'Machine Type',
});
export const INSPECT_TITLE = i18n.translate('xpack.siem.host.details.overview.inspectTitle', {
defaultMessage: 'Host overview',
});

View file

@ -77,6 +77,7 @@ exports[`Load More Table Component rendering it renders the default Hosts table
]
}
hasNextPage={true}
id="hostsQuery"
indexPattern={
Object {
"fields": Array [

View file

@ -40,13 +40,14 @@ describe('Load More Table Component', () => {
<ReduxStoreProvider store={store}>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<HostsTable
indexPattern={mockIndexPattern}
loading={false}
data={mockData.Hosts.edges}
totalCount={mockData.Hosts.totalCount}
id="hostsQuery"
indexPattern={mockIndexPattern}
hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)}
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)}
totalCount={mockData.Hosts.totalCount}
type={hostsModel.HostsType.page}
/>
</KibanaConfigContext.Provider>
@ -61,6 +62,7 @@ describe('Load More Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<HostsTable
id="hostsQuery"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.Hosts.edges}
@ -79,6 +81,7 @@ describe('Load More Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<HostsTable
id="hostsQuery"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.Hosts.edges}

View file

@ -35,6 +35,7 @@ import * as i18n from './translations';
interface OwnProps {
data: HostsEdges[];
loading: boolean;
id: string;
indexPattern: StaticIndexPattern;
hasNextPage: boolean;
nextCursor: string;
@ -105,6 +106,7 @@ class HostsTableComponent extends React.PureComponent<HostsTableProps> {
data,
direction,
hasNextPage,
id,
indexPattern,
limit,
loading,
@ -120,6 +122,7 @@ class HostsTableComponent extends React.PureComponent<HostsTableProps> {
headerCount={totalCount}
headerTitle={i18n.HOSTS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -25,6 +25,7 @@ exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = `
]
}
grow={1}
index={0}
key="authentication"
/>
<Memo()
@ -51,6 +52,7 @@ exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = `
]
}
grow={1}
index={1}
key="uniqueIps"
/>
</EuiFlexGroup>
@ -72,6 +74,7 @@ exports[`kpiHostsComponent render it should render KpiHostsData 1`] = `
]
}
grow={2}
index={0}
key="hosts"
/>
<Memo()
@ -97,6 +100,7 @@ exports[`kpiHostsComponent render it should render KpiHostsData 1`] = `
]
}
grow={4}
index={1}
key="authentication"
/>
<Memo()
@ -123,6 +127,7 @@ exports[`kpiHostsComponent render it should render KpiHostsData 1`] = `
]
}
grow={4}
index={2}
key="uniqueIps"
/>
</EuiFlexGroup>

View file

@ -14,24 +14,33 @@ import { kpiHostsMapping } from './kpi_hosts_mapping';
import { kpiHostDetailsMapping } from './kpi_host_details_mapping';
describe('kpiHostsComponent', () => {
const ID = 'kpiHost';
const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
describe('render', () => {
test('it should render spinner if it is loading', () => {
const wrapper: ShallowWrapper = shallow(
<KpiHostsComponent data={mockKpiHostsData} loading={true} />
<KpiHostsComponent data={mockKpiHostsData} from={from} id={ID} loading={true} to={to} />
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it should render KpiHostsData', () => {
const wrapper: ShallowWrapper = shallow(
<KpiHostsComponent data={mockKpiHostsData} loading={false} />
<KpiHostsComponent data={mockKpiHostsData} from={from} id={ID} loading={false} to={to} />
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it should render KpiHostDetailsData', () => {
const wrapper: ShallowWrapper = shallow(
<KpiHostsComponent data={mockKpiHostDetailsData} loading={false} />
<KpiHostsComponent
data={mockKpiHostDetailsData}
from={from}
id={ID}
loading={false}
to={to}
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
@ -47,7 +56,7 @@ describe('kpiHostsComponent', () => {
});
beforeEach(() => {
shallow(<KpiHostsComponent data={data} loading={false} />);
shallow(<KpiHostsComponent data={data} from={from} id={ID} loading={false} to={to} />);
});
afterEach(() => {
@ -59,7 +68,7 @@ describe('kpiHostsComponent', () => {
});
test(`it should apply correct mapping by given data type`, () => {
expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data);
expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to);
});
});
});

View file

@ -16,14 +16,19 @@ import { kpiHostDetailsMapping } from './kpi_host_details_mapping';
const kpiWidgetHeight = 247;
interface KpiHostsProps {
data: KpiHostsData;
interface GenericKpiHostProps {
from: number;
id: string;
loading: boolean;
to: number;
}
interface KpiHostDetailsProps {
interface KpiHostsProps extends GenericKpiHostProps {
data: KpiHostsData;
}
interface KpiHostDetailsProps extends GenericKpiHostProps {
data: KpiHostDetailsData;
loading: boolean;
}
const FlexGroupSpinner = styled(EuiFlexGroup)`
@ -32,10 +37,16 @@ const FlexGroupSpinner = styled(EuiFlexGroup)`
}
`;
export const KpiHostsComponent = ({ data, loading }: KpiHostsProps | KpiHostDetailsProps) => {
export const KpiHostsComponent = ({
data,
from,
loading,
id,
to,
}: KpiHostsProps | KpiHostDetailsProps) => {
const mappings =
(data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping;
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(mappings, data);
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(mappings, data, id, from, to);
return loading ? (
<FlexGroupSpinner justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
@ -44,7 +55,7 @@ export const KpiHostsComponent = ({ data, loading }: KpiHostsProps | KpiHostDeta
</FlexGroupSpinner>
) : (
<EuiFlexGroup>
{statItemsProps.map(mappedStatItemProps => {
{statItemsProps.map((mappedStatItemProps, idx) => {
return <StatItemsComponent {...mappedStatItemProps} />;
})}
</EuiFlexGroup>

View file

@ -15,6 +15,7 @@ const euiColorVis9 = '#920000';
export const kpiHostDetailsMapping: Readonly<StatItems[]> = [
{
key: 'authentication',
index: 0,
fields: [
{
key: 'authSuccess',
@ -38,6 +39,7 @@ export const kpiHostDetailsMapping: Readonly<StatItems[]> = [
},
{
key: 'uniqueIps',
index: 1,
fields: [
{
key: 'uniqueSourceIps',

View file

@ -16,6 +16,7 @@ const euiColorVis9 = '#920000';
export const kpiHostsMapping: Readonly<StatItems[]> = [
{
key: 'hosts',
index: 0,
fields: [
{
key: 'hosts',
@ -30,6 +31,7 @@ export const kpiHostsMapping: Readonly<StatItems[]> = [
},
{
key: 'authentication',
index: 1,
fields: [
{
key: 'authSuccess',
@ -53,6 +55,7 @@ export const kpiHostsMapping: Readonly<StatItems[]> = [
},
{
key: 'uniqueIps',
index: 2,
fields: [
{
key: 'uniqueSourceIps',

View file

@ -202,6 +202,7 @@ exports[`UncommonProcess Table Component rendering it renders the default Uncomm
]
}
hasNextPage={true}
id="uncommonProcess"
loadMore={[MockFunction]}
loading={false}
nextCursor="aa7ca589f1b8220002f2fc61c64cfbf1"

View file

@ -24,12 +24,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = shallow(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -42,12 +43,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -66,12 +68,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -91,12 +94,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -116,12 +120,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -141,12 +146,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -166,12 +172,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -190,12 +197,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -214,12 +222,13 @@ describe('UncommonProcess Table Component', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
loading={false}
data={mockData.UncommonProcess.edges}
totalCount={mockData.UncommonProcess.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
id="uncommonProcess"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)}
totalCount={mockData.UncommonProcess.totalCount}
type={hostsModel.HostsType.page}
/>
</TestProviders>

View file

@ -23,6 +23,7 @@ interface OwnProps {
data: UncommonProcessesEdges[];
loading: boolean;
hasNextPage: boolean;
id: string;
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
@ -72,6 +73,7 @@ const UncommonProcessTableComponent = pure<UncommonProcessTableProps>(
({
data,
hasNextPage,
id,
limit,
loading,
loadMore,
@ -86,6 +88,7 @@ const UncommonProcessTableComponent = pure<UncommonProcessTableProps>(
headerCount={totalCount}
headerTitle={i18n.UNCOMMON_PROCESSES}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -119,6 +119,11 @@ export const MoreRowItems = styled(EuiIcon)`
export const OverviewWrapper = styled(EuiFlexGroup)`
position: relative;
.euiButtonIcon {
position: absolute;
top: -10px;
right: 10px;
}
`;
export const LoadingOverlay = styled.div`

View file

@ -13,20 +13,28 @@ interface OwnProps {
id: string;
loading: boolean;
refetch: inputsModel.Refetch;
setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void;
setQuery: (
params: {
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}
) => void;
inspect?: inputsModel.InspectQuery;
}
export function manageQuery<T>(WrappedComponent: React.ComponentClass<T> | React.ComponentType<T>) {
class ManageQuery extends React.PureComponent<OwnProps & T> {
public componentDidUpdate(prevProps: OwnProps) {
const { loading, id, refetch, setQuery } = this.props;
const { loading, id, refetch, setQuery, inspect = null } = this.props;
if (prevProps.loading !== loading) {
setQuery({ id, loading, refetch });
setQuery({ id, inspect, loading, refetch });
}
}
public render() {
const otherProps = omit(['id', 'refetch', 'setQuery'], this.props);
const otherProps = omit(['refetch', 'setQuery'], this.props);
return <WrappedComponent {...otherProps} />;
}
}

View file

@ -65,6 +65,7 @@ exports[`Domains Table Component Rendering it renders the default Domains table
}
flowTarget="source"
hasNextPage={false}
id="domains"
indexPattern={
Object {
"fields": Array [

View file

@ -39,15 +39,16 @@ describe('Domains Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<DomainsTable
indexPattern={mockIndexPattern}
ip={ip}
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockDomainsData.edges}
flowTarget={FlowTarget.source}
hasNextPage={getOr(false, 'hasNextPage', mockDomainsData.pageInfo)!}
id="domains"
indexPattern={mockIndexPattern}
ip={ip}
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockDomainsData.pageInfo)}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</ReduxStoreProvider>
@ -63,15 +64,16 @@ describe('Domains Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<DomainsTable
indexPattern={mockIndexPattern}
ip={ip}
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockDomainsData.edges}
flowTarget={FlowTarget.source}
indexPattern={mockIndexPattern}
hasNextPage={getOr(false, 'hasNextPage', mockDomainsData.pageInfo)!}
id="domains"
ip={ip}
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockDomainsData.pageInfo)}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</TestProviders>

View file

@ -31,6 +31,7 @@ interface OwnProps {
flowTarget: FlowTarget;
loading: boolean;
hasNextPage: boolean;
id: string;
indexPattern: StaticIndexPattern;
ip: string;
nextCursor: string;
@ -89,6 +90,7 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
data,
domainsSortField,
hasNextPage,
id,
indexPattern,
ip,
limit,
@ -122,6 +124,7 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
}
headerTitle={i18n.DOMAINS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -150,6 +150,7 @@ exports[`IP Overview Component rendering it renders the default IP Overview 1`]
}
endDate={1560837600000}
flowTarget="source"
id="ipOverview"
ip="10.10.10.10"
isLoadingAnomaliesData={false}
loading={false}

View file

@ -29,19 +29,20 @@ describe('IP Overview Component', () => {
describe('rendering', () => {
const mockProps = {
anomaliesData: mockAnomalies,
data: mockData.IpOverview,
endDate: new Date('2019-06-18T06:00:00.000Z').valueOf(),
flowTarget: FlowTarget.source,
loading: false,
id: 'ipOverview',
ip: '10.10.10.10',
data: mockData.IpOverview,
isLoadingAnomaliesData: false,
narrowDateRange: (jest.fn() as unknown) as NarrowDateRange,
startDate: new Date('2019-06-15T06:00:00.000Z').valueOf(),
type: networkModel.NetworkType.details,
updateFlowTargetAction: (jest.fn() as unknown) as ActionCreator<{
flowTarget: FlowTarget;
}>,
startDate: new Date('2019-06-15T06:00:00.000Z').valueOf(),
endDate: new Date('2019-06-18T06:00:00.000Z').valueOf(),
anomaliesData: mockAnomalies,
isLoadingAnomaliesData: false,
narrowDateRange: (jest.fn() as unknown) as NarrowDateRange,
};
test('it renders the default IP Overview', () => {

View file

@ -5,7 +5,7 @@
*/
import { EuiDescriptionList, EuiFlexItem } from '@elastic/eui';
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
@ -30,10 +30,12 @@ import { Anomalies, NarrowDateRange } from '../../../ml/types';
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider';
import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions';
import { InspectButton } from '../../../inspect';
interface OwnProps {
data: IpOverviewData;
flowTarget: FlowTarget;
id: string;
ip: string;
loading: boolean;
isLoadingAnomaliesData: boolean;
@ -64,6 +66,7 @@ const getDescriptionList = (descriptionList: DescriptionList[], key: number) =>
export const IpOverview = pure<IpOverviewProps>(
({
id,
ip,
data,
loading,
@ -74,6 +77,7 @@ export const IpOverview = pure<IpOverviewProps>(
anomaliesData,
narrowDateRange,
}) => {
const [showInspect, setShowInspect] = useState(false);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const typeData: Overview = data[flowTarget]!;
@ -135,7 +139,10 @@ export const IpOverview = pure<IpOverviewProps>(
],
];
return (
<OverviewWrapper>
<OverviewWrapper
onMouseEnter={() => setShowInspect(true)}
onMouseLeave={() => setShowInspect(false)}
>
{loading && (
<>
<LoadingOverlay />
@ -149,6 +156,12 @@ export const IpOverview = pure<IpOverviewProps>(
/>
</>
)}
<InspectButton
queryId={id}
show={showInspect}
title={i18n.INSPECT_TITLE}
inspectIndex={0}
/>
{descriptionLists.map((descriptionList, index) =>
getDescriptionList(descriptionList, index)
)}

View file

@ -83,3 +83,10 @@ export const AS_DESTINATION = i18n.translate(
defaultMessage: 'As Destination',
}
);
export const INSPECT_TITLE = i18n.translate(
'xpack.siem.network.ipDetails.ipOverview.inspectTitle',
{
defaultMessage: 'IP overview',
}
);

View file

@ -32,7 +32,10 @@ exports[`KpiNetwork Component rendering it renders loading icons 1`] = `
],
}
}
from={1560578400000}
id="kpiNetwork"
loading={true}
to={1560837600000}
/>
`;
@ -68,6 +71,9 @@ exports[`KpiNetwork Component rendering it renders the default widget 1`] = `
],
}
}
from={1560578400000}
id="kpiNetwork"
loading={false}
to={1560837600000}
/>
`;

View file

@ -17,6 +17,8 @@ import { mockData } from './mock';
describe('KpiNetwork Component', () => {
const state: State = mockGlobalState;
const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
let store = createStore(state, apolloClientObservable);
@ -28,7 +30,13 @@ describe('KpiNetwork Component', () => {
test('it renders loading icons', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<KpiNetworkComponent data={mockData.KpiNetwork} loading={true} />
<KpiNetworkComponent
data={mockData.KpiNetwork}
from={from}
id="kpiNetwork"
loading={true}
to={to}
/>
</ReduxStoreProvider>
);
@ -38,7 +46,13 @@ describe('KpiNetwork Component', () => {
test('it renders the default widget', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<KpiNetworkComponent data={mockData.KpiNetwork} loading={false} />
<KpiNetworkComponent
data={mockData.KpiNetwork}
from={from}
id="kpiNetwork"
loading={false}
to={to}
/>
</ReduxStoreProvider>
);

View file

@ -30,12 +30,16 @@ const euiColorVis3 = '#490092';
interface KpiNetworkProps {
data: KpiNetworkData;
from: number;
id: string;
loading: boolean;
to: number;
}
export const fieldTitleChartMapping: Readonly<StatItems[]> = [
{
key: 'UniqueIps',
index: 4,
fields: [
{
key: 'uniqueSourcePrivateIps',
@ -64,6 +68,7 @@ export const fieldTitleChartMapping: Readonly<StatItems[]> = [
const fieldTitleMatrixMapping: Readonly<StatItems[]> = [
{
key: 'networkEvents',
index: 0,
fields: [
{
key: 'networkEvents',
@ -76,6 +81,7 @@ const fieldTitleMatrixMapping: Readonly<StatItems[]> = [
},
{
key: 'dnsQueries',
index: 1,
fields: [
{
key: 'dnsQueries',
@ -86,6 +92,7 @@ const fieldTitleMatrixMapping: Readonly<StatItems[]> = [
},
{
key: 'uniqueFlowId',
index: 2,
fields: [
{
key: 'uniqueFlowId',
@ -96,6 +103,7 @@ const fieldTitleMatrixMapping: Readonly<StatItems[]> = [
},
{
key: 'tlsHandshakes',
index: 3,
fields: [
{
key: 'tlsHandshakes',
@ -113,41 +121,61 @@ const FlexGroup = styled(EuiFlexGroup)`
export const KpiNetworkBaseComponent = ({
fieldsMapping,
data,
id,
from,
to,
}: {
fieldsMapping: Readonly<StatItems[]>;
data: KpiNetworkData;
id: string;
from: number;
to: number;
}) => {
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(fieldsMapping, data);
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(fieldsMapping, data, id, from, to);
return (
<EuiFlexGroup wrap>
{statItemsProps.map(mappedStatItemProps => {
{statItemsProps.map((mappedStatItemProps, idx) => {
return <StatItemsComponent {...mappedStatItemProps} />;
})}
</EuiFlexGroup>
);
};
export const KpiNetworkComponent = React.memo<KpiNetworkProps>(({ data, loading }) => {
return loading ? (
<FlexGroup justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</FlexGroup>
) : (
<EuiFlexGroup wrap>
<EuiFlexItem grow={1}>
{_chunk(kipsPerRow, fieldTitleMatrixMapping).map((mappingsPerLine, idx) => (
<React.Fragment key={`kpi-network-row-${idx}`}>
{idx % kipsPerRow === 1 && <EuiSpacer size="l" />}
<KpiNetworkBaseComponent data={data} fieldsMapping={mappingsPerLine} />
</React.Fragment>
))}
</EuiFlexItem>
<EuiFlexItem grow={1}>
<KpiNetworkBaseComponent data={data} fieldsMapping={fieldTitleChartMapping} />
</EuiFlexItem>
</EuiFlexGroup>
);
});
export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
({ data, from, id, loading, to }) => {
return loading ? (
<FlexGroup justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</FlexGroup>
) : (
<EuiFlexGroup wrap>
<EuiFlexItem grow={1}>
{_chunk(kipsPerRow, fieldTitleMatrixMapping).map((mappingsPerLine, idx) => (
<React.Fragment key={`kpi-network-row-${idx}`}>
{idx % kipsPerRow === 1 && <EuiSpacer size="l" />}
<KpiNetworkBaseComponent
data={data}
id={id}
fieldsMapping={mappingsPerLine}
from={from}
to={to}
/>
</React.Fragment>
))}
</EuiFlexItem>
<EuiFlexItem grow={1}>
<KpiNetworkBaseComponent
data={data}
id={id}
fieldsMapping={fieldTitleChartMapping}
from={from}
to={to}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
);

View file

@ -40,6 +40,7 @@ export const mockData: { KpiNetwork: KpiNetworkData } = {
const mockMappingItems: StatItems = {
key: 'UniqueIps',
index: 0,
fields: [
{
key: 'uniqueSourcePrivateIps',
@ -150,28 +151,6 @@ export const mockEnableChartsInitialData = {
};
export const mockEnableChartsData = {
fields: [
{
key: 'uniqueSourcePrivateIps',
value: 383,
name: 'Src.',
description: 'Source',
color: '#DB1374',
icon: 'visMapCoordinate',
},
{
key: 'uniqueDestinationPrivateIps',
value: 18,
name: 'Dest.',
description: 'Destination',
color: '#490092',
icon: 'visMapCoordinate',
},
],
description: 'Unique Private IPs',
enableAreaChart: true,
enableBarChart: true,
grow: 2,
areaChart: [
{
key: 'uniqueSourcePrivateIpsHistogram',
@ -211,4 +190,30 @@ export const mockEnableChartsData = {
value: [{ x: 'Dest.', y: 18, g: 'uniqueDestinationPrivateIps' }],
},
],
description: 'Unique Private IPs',
enableAreaChart: true,
enableBarChart: true,
fields: [
{
key: 'uniqueSourcePrivateIps',
value: 383,
name: 'Src.',
description: 'Source',
color: '#DB1374',
icon: 'visMapCoordinate',
},
{
key: 'uniqueDestinationPrivateIps',
value: 18,
name: 'Dest.',
description: 'Destination',
color: '#490092',
icon: 'visMapCoordinate',
},
],
from: 1560578400000,
grow: 2,
id: 'statItem',
index: 4,
to: 1560837600000,
};

View file

@ -137,6 +137,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
]
}
hasNextPage={true}
id="dns"
loadMore={[MockFunction]}
loading={false}
nextCursor="10"

View file

@ -32,12 +32,13 @@ describe('NetworkTopNFlow Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkDnsTable
loading={false}
data={mockData.NetworkDns.edges}
totalCount={mockData.NetworkDns.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkDns.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)}
id="dns"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)}
totalCount={mockData.NetworkDns.totalCount}
type={networkModel.NetworkType.page}
/>
</ReduxStoreProvider>
@ -53,12 +54,13 @@ describe('NetworkTopNFlow Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<NetworkDnsTable
loading={false}
data={mockData.NetworkDns.edges}
totalCount={mockData.NetworkDns.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkDns.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)}
id="dns"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)}
totalCount={mockData.NetworkDns.totalCount}
type={networkModel.NetworkType.page}
/>
</TestProviders>

View file

@ -22,6 +22,7 @@ interface OwnProps {
data: NetworkDnsEdges[];
loading: boolean;
hasNextPage: boolean;
id: string;
nextCursor: string;
loadMore: (cursor: string) => void;
totalCount: number;
@ -80,6 +81,7 @@ class NetworkDnsTableComponent extends React.PureComponent<NetworkDnsTableProps>
limit,
loading,
loadMore,
id,
nextCursor,
totalCount,
type,
@ -96,6 +98,7 @@ class NetworkDnsTableComponent extends React.PureComponent<NetworkDnsTableProps>
headerTitle={i18n.TOP_DNS_DOMAINS}
headerTooltip={i18n.TOOLTIP}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -52,6 +52,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
]
}
hasNextPage={true}
id="topNFlow"
indexPattern={
Object {
"fields": Array [

View file

@ -38,13 +38,14 @@ describe('NetworkTopNFlow Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkTopNFlowTable
data={mockData.NetworkTopNFlow.edges}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
id="topNFlow"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.NetworkTopNFlow.edges}
totalCount={mockData.NetworkTopNFlow.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
totalCount={mockData.NetworkTopNFlow.totalCount}
type={networkModel.NetworkType.page}
/>
</ReduxStoreProvider>
@ -64,13 +65,14 @@ describe('NetworkTopNFlow Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<NetworkTopNFlowTable
data={mockData.NetworkTopNFlow.edges}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
id="topNFlow"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.NetworkTopNFlow.edges}
totalCount={mockData.NetworkTopNFlow.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
totalCount={mockData.NetworkTopNFlow.totalCount}
type={networkModel.NetworkType.page}
/>
</TestProviders>
@ -100,13 +102,14 @@ describe('NetworkTopNFlow Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<NetworkTopNFlowTable
data={mockData.NetworkTopNFlow.edges}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
id="topNFlow"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.NetworkTopNFlow.edges}
totalCount={mockData.NetworkTopNFlow.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
totalCount={mockData.NetworkTopNFlow.totalCount}
type={networkModel.NetworkType.page}
/>
</TestProviders>
@ -141,13 +144,14 @@ describe('NetworkTopNFlow Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<NetworkTopNFlowTable
data={mockData.NetworkTopNFlow.edges}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
id="topNFlow"
indexPattern={mockIndexPattern}
loading={false}
data={mockData.NetworkTopNFlow.edges}
totalCount={mockData.NetworkTopNFlow.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)}
totalCount={mockData.NetworkTopNFlow.totalCount}
type={networkModel.NetworkType.page}
/>
</TestProviders>

View file

@ -29,6 +29,7 @@ import * as i18n from './translations';
interface OwnProps {
data: NetworkTopNFlowEdges[];
id: string;
indexPattern: StaticIndexPattern;
loading: boolean;
hasNextPage: boolean;
@ -93,6 +94,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
const {
data,
hasNextPage,
id,
indexPattern,
limit,
loading,
@ -124,7 +126,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
hasNextPage={hasNextPage}
headerCount={totalCount}
headerSupplement={
<EuiFlexGroup alignItems="center">
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<SelectTypeItem
grow={false}
@ -156,6 +158,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
}
headerTitle={i18n.TOP_TALKERS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -79,6 +79,7 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
]
}
hasNextPage={false}
id="tls"
loadMore={[MockFunction]}
loading={false}
nextCursor="10"

View file

@ -32,12 +32,13 @@ describe('Tls Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<TlsTable
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockTlsData.edges}
hasNextPage={getOr(false, 'hasNextPage', mockTlsData.pageInfo)!}
id="tls"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockTlsData.pageInfo)}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</ReduxStoreProvider>
@ -53,12 +54,13 @@ describe('Tls Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<TlsTable
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockTlsData.edges}
hasNextPage={getOr(false, 'hasNextPage', mockTlsData.pageInfo)!}
id="tls"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockTlsData.pageInfo)}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</TestProviders>

View file

@ -20,6 +20,7 @@ interface OwnProps {
data: TlsEdges[];
loading: boolean;
hasNextPage: boolean;
id: string;
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
@ -74,6 +75,7 @@ class TlsTableComponent extends React.PureComponent<TlsTableProps> {
limit,
loading,
loadMore,
id,
totalCount,
nextCursor,
updateTlsLimit,
@ -86,6 +88,7 @@ class TlsTableComponent extends React.PureComponent<TlsTableProps> {
headerCount={totalCount}
headerTitle={i18n.TRANSPORT_LAYER_SECURITY}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -72,6 +72,7 @@ exports[`Users Table Component Rendering it renders the default Users table 1`]
}
flowTarget="source"
hasNextPage={false}
id="user"
loadMore={[MockFunction]}
loading={false}
nextCursor="10"

View file

@ -33,13 +33,14 @@ describe('Users Table Component', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<UsersTable
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockUsersData.edges}
flowTarget={FlowTarget.source}
hasNextPage={getOr(false, 'hasNextPage', mockUsersData.pageInfo)!}
id="user"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockUsersData.pageInfo)!}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</ReduxStoreProvider>
@ -55,13 +56,14 @@ describe('Users Table Component', () => {
<MockedProvider>
<TestProviders store={store}>
<UsersTable
totalCount={1}
loading={false}
loadMore={loadMore}
data={mockUsersData.edges}
flowTarget={FlowTarget.source}
hasNextPage={getOr(false, 'hasNextPage', mockUsersData.pageInfo)!}
id="user"
loading={false}
loadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockUsersData.pageInfo)!}
totalCount={1}
type={networkModel.NetworkType.details}
/>
</TestProviders>

View file

@ -23,6 +23,7 @@ interface OwnProps {
flowTarget: FlowTarget;
loading: boolean;
hasNextPage: boolean;
id: string;
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
@ -77,6 +78,7 @@ class UsersTableComponent extends React.PureComponent<UsersTableProps> {
limit,
loading,
loadMore,
id,
totalCount,
nextCursor,
updateUsersLimit,
@ -91,6 +93,7 @@ class UsersTableComponent extends React.PureComponent<UsersTableProps> {
headerCount={totalCount}
headerTitle={i18n.USERS}
headerUnit={i18n.UNIT(totalCount)}
id={id}
itemsPerRow={rowItems}
limit={limit}
loading={loading}

View file

@ -6,12 +6,15 @@
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import React, { useState } from 'react';
import { pure } from 'recompose';
import { HeaderPanel } from '../../../header_panel';
import { manageQuery } from '../../../page/manage_query';
import { OverviewHostQuery } from '../../../../containers/overview/overview_host';
import {
ID as OverviewHostQueryId,
OverviewHostQuery,
} from '../../../../containers/overview/overview_host';
import { inputsModel } from '../../../../store/inputs';
import { OverviewHostStats } from '../overview_host_stats';
@ -19,43 +22,59 @@ export interface OwnProps {
startDate: number;
endDate: number;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
{
id,
inspect,
loading,
refetch,
}: {
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}
) => void;
}
const OverviewHostStatsManage = manageQuery(OverviewHostStats);
type OverviewHostProps = OwnProps;
export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQuery }) => (
<EuiFlexItem>
<EuiPanel>
<HeaderPanel
border
subtitle={
<FormattedMessage
id="xpack.siem.overview.hostsSubtitle"
defaultMessage="Showing: Last 24 Hours"
/>
}
title={
<FormattedMessage id="xpack.siem.overview.hostsTitle" defaultMessage="Host Events" />
}
>
<EuiButton href="#/link-to/hosts">
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View Hosts" />
</EuiButton>
</HeaderPanel>
export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQuery }) => {
const [isHover, setIsHover] = useState(false);
return (
<EuiFlexItem>
<EuiPanel onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
<HeaderPanel
border
id={OverviewHostQueryId}
showInspect={isHover}
subtitle={
<FormattedMessage
id="xpack.siem.overview.hostsSubtitle"
defaultMessage="Showing: Last 24 Hours"
/>
}
title={
<FormattedMessage id="xpack.siem.overview.hostsTitle" defaultMessage="Host Events" />
}
>
<EuiButton href="#/link-to/hosts">
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View Hosts" />
</EuiButton>
</HeaderPanel>
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewHost, loading, id, refetch }) => (
<OverviewHostStatsManage
loading={loading}
data={overviewHost}
setQuery={setQuery}
id={id}
refetch={refetch}
/>
)}
</OverviewHostQuery>
</EuiPanel>
</EuiFlexItem>
));
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewHost, loading, id, inspect, refetch }) => (
<OverviewHostStatsManage
loading={loading}
data={overviewHost}
setQuery={setQuery}
id={id}
inspect={inspect}
refetch={refetch}
/>
)}
</OverviewHostQuery>
</EuiPanel>
</EuiFlexItem>
);
});

View file

@ -6,12 +6,15 @@
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import React, { useState } from 'react';
import { pure } from 'recompose';
import { HeaderPanel } from '../../../header_panel';
import { manageQuery } from '../../../page/manage_query';
import { OverviewNetworkQuery } from '../../../../containers/overview/overview_network';
import {
ID as OverviewNetworkQueryId,
OverviewNetworkQuery,
} from '../../../../containers/overview/overview_network';
import { inputsModel } from '../../../../store/inputs';
import { OverviewNetworkStats } from '../overview_network_stats';
@ -19,43 +22,65 @@ export interface OwnProps {
startDate: number;
endDate: number;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
{
id,
inspect,
loading,
refetch,
}: {
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}
) => void;
}
const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats);
export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery }) => (
<EuiFlexItem>
<EuiPanel>
<HeaderPanel
border
subtitle={
<FormattedMessage
id="xpack.siem.overview.networkSubtitle"
defaultMessage="Showing: Last 24 Hours"
/>
}
title={
<FormattedMessage id="xpack.siem.overview.networkTitle" defaultMessage="Network Events" />
}
>
<EuiButton href="#/link-to/network/">
<FormattedMessage id="xpack.siem.overview.networkAction" defaultMessage="View Network" />
</EuiButton>
</HeaderPanel>
export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery }) => {
const [isHover, setIsHover] = useState(false);
return (
<EuiFlexItem>
<EuiPanel onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
<HeaderPanel
border
id={OverviewNetworkQueryId}
showInspect={isHover}
subtitle={
<FormattedMessage
id="xpack.siem.overview.networkSubtitle"
defaultMessage="Showing: Last 24 Hours"
/>
}
title={
<FormattedMessage
id="xpack.siem.overview.networkTitle"
defaultMessage="Network Events"
/>
}
>
<EuiButton href="#/link-to/network/">
<FormattedMessage
id="xpack.siem.overview.networkAction"
defaultMessage="View Network"
/>
</EuiButton>
</HeaderPanel>
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewNetwork, loading, id, refetch }) => (
<OverviewNetworkStatsManage
loading={loading}
data={overviewNetwork}
setQuery={setQuery}
id={id}
refetch={refetch}
/>
)}
</OverviewNetworkQuery>
</EuiPanel>
</EuiFlexItem>
));
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewNetwork, loading, id, inspect, refetch }) => (
<OverviewNetworkStatsManage
loading={loading}
data={overviewNetwork}
id={id}
inspect={inspect}
setQuery={setQuery}
refetch={refetch}
/>
)}
</OverviewNetworkQuery>
</EuiPanel>
</EuiFlexItem>
);
});

View file

@ -24,32 +24,158 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
},
]
}
from={1560578400000}
id="statItems"
index={0}
key="mock-keys"
to={1560837600000}
>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiPanel
grow={true}
hasShadow={false}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
paddingSize="m"
>
<div
className="euiPanel euiPanel--paddingMedium"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<EuiTitle
size="xxxs"
<EuiFlexGroup
gutterSize="none"
>
<h6
className="euiTitle euiTitle--xxxsmall"
<div
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
>
HOSTS
</h6>
</EuiTitle>
<EuiFlexItem>
<div
className="euiFlexItem"
>
<EuiTitle
size="xxxs"
>
<h6
className="euiTitle euiTitle--xxxsmall"
>
HOSTS
</h6>
</EuiTitle>
</div>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<Connect(pure(Component))
inspectIndex={0}
queryId="statItems"
show={false}
title="KPI HOSTS"
>
<pure(Component)
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI HOSTS"
>
<Component
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI HOSTS"
>
<styled.div
showInspect={false}
>
<div
className="sc-htpNat inAMCx"
>
<EuiButtonIcon
aria-label="Inspect"
className=""
color="primary"
data-test-subj="inspect-icon-button"
iconSize="m"
iconType="inspect"
onClick={[Function]}
title="Inspect"
type="button"
>
<button
aria-label="Inspect"
className="euiButtonIcon euiButtonIcon--primary"
data-test-subj="inspect-icon-button"
onClick={[Function]}
title="Inspect"
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="inspect"
>
<EuiIconEmpty
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
style={null}
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</EuiIconEmpty>
</EuiIcon>
</button>
</EuiButtonIcon>
<ModalInspectQuery
closeModal={[Function]}
data-test-subj="inspect-modal"
isShowing={false}
request={null}
response={null}
title="KPI HOSTS"
/>
</div>
</styled.div>
</Component>
</pure(Component)>
</Connect(pure(Component))>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiFlexGroup>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
@ -58,10 +184,10 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
key="stat-items-field-hosts"
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiFlexGroup
alignItems="center"
@ -73,17 +199,17 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Styled(EuiTitle)>
<EuiTitle
className="sc-htpNat bTDdmQ"
className="sc-ifAKCX bfXlPM"
>
<p
className="euiTitle euiTitle--medium sc-htpNat bTDdmQ"
className="euiTitle euiTitle--medium sc-ifAKCX bfXlPM"
data-test-subj="stat-title"
>
--
@ -141,32 +267,158 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
},
]
}
from={1560578400000}
id="statItems"
index={0}
key="mock-keys"
to={1560837600000}
>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiPanel
grow={true}
hasShadow={false}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
paddingSize="m"
>
<div
className="euiPanel euiPanel--paddingMedium"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<EuiTitle
size="xxxs"
<EuiFlexGroup
gutterSize="none"
>
<h6
className="euiTitle euiTitle--xxxsmall"
<div
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
>
HOSTS
</h6>
</EuiTitle>
<EuiFlexItem>
<div
className="euiFlexItem"
>
<EuiTitle
size="xxxs"
>
<h6
className="euiTitle euiTitle--xxxsmall"
>
HOSTS
</h6>
</EuiTitle>
</div>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<Connect(pure(Component))
inspectIndex={0}
queryId="statItems"
show={false}
title="KPI HOSTS"
>
<pure(Component)
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI HOSTS"
>
<Component
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI HOSTS"
>
<styled.div
showInspect={false}
>
<div
className="sc-htpNat inAMCx"
>
<EuiButtonIcon
aria-label="Inspect"
className=""
color="primary"
data-test-subj="inspect-icon-button"
iconSize="m"
iconType="inspect"
onClick={[Function]}
title="Inspect"
type="button"
>
<button
aria-label="Inspect"
className="euiButtonIcon euiButtonIcon--primary"
data-test-subj="inspect-icon-button"
onClick={[Function]}
title="Inspect"
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="inspect"
>
<EuiIconEmpty
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
style={null}
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</EuiIconEmpty>
</EuiIcon>
</button>
</EuiButtonIcon>
<ModalInspectQuery
closeModal={[Function]}
data-test-subj="inspect-modal"
isShowing={false}
request={null}
response={null}
title="KPI HOSTS"
/>
</div>
</styled.div>
</Component>
</pure(Component)>
</Connect(pure(Component))>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiFlexGroup>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
@ -175,10 +427,10 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
key="stat-items-field-hosts"
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiFlexGroup
alignItems="center"
@ -191,17 +443,17 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
0
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Styled(EuiTitle)>
<EuiTitle
className="sc-htpNat bTDdmQ"
className="sc-ifAKCX bfXlPM"
>
<p
className="euiTitle euiTitle--medium sc-htpNat bTDdmQ"
className="euiTitle euiTitle--medium sc-ifAKCX bfXlPM"
data-test-subj="stat-title"
>
--
@ -331,32 +583,158 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
},
]
}
from={1560578400000}
id="statItems"
index={0}
key="mock-keys"
to={1560837600000}
>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiPanel
grow={true}
hasShadow={false}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
paddingSize="m"
>
<div
className="euiPanel euiPanel--paddingMedium"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<EuiTitle
size="xxxs"
<EuiFlexGroup
gutterSize="none"
>
<h6
className="euiTitle euiTitle--xxxsmall"
<div
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
>
UNIQUE_PRIVATE_IPS
</h6>
</EuiTitle>
<EuiFlexItem>
<div
className="euiFlexItem"
>
<EuiTitle
size="xxxs"
>
<h6
className="euiTitle euiTitle--xxxsmall"
>
UNIQUE_PRIVATE_IPS
</h6>
</EuiTitle>
</div>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<Connect(pure(Component))
inspectIndex={0}
queryId="statItems"
show={false}
title="KPI UNIQUE_PRIVATE_IPS"
>
<pure(Component)
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI UNIQUE_PRIVATE_IPS"
>
<Component
id=""
inspect={null}
inspectIndex={0}
isInspected={false}
loading={false}
queryId="statItems"
refetch={null}
selectedInspectIndex={0}
setIsInspected={[Function]}
show={false}
title="KPI UNIQUE_PRIVATE_IPS"
>
<styled.div
showInspect={false}
>
<div
className="sc-htpNat inAMCx"
>
<EuiButtonIcon
aria-label="Inspect"
className=""
color="primary"
data-test-subj="inspect-icon-button"
iconSize="m"
iconType="inspect"
onClick={[Function]}
title="Inspect"
type="button"
>
<button
aria-label="Inspect"
className="euiButtonIcon euiButtonIcon--primary"
data-test-subj="inspect-icon-button"
onClick={[Function]}
title="Inspect"
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="inspect"
>
<EuiIconEmpty
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
style={null}
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</EuiIconEmpty>
</EuiIcon>
</button>
</EuiButtonIcon>
<ModalInspectQuery
closeModal={[Function]}
data-test-subj="inspect-modal"
isShowing={false}
request={null}
response={null}
title="KPI UNIQUE_PRIVATE_IPS"
/>
</div>
</styled.div>
</Component>
</pure(Component)>
</Connect(pure(Component))>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiFlexGroup>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
@ -365,10 +743,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
key="stat-items-field-uniqueSourceIps"
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiFlexGroup
alignItems="center"
@ -382,11 +760,11 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
grow={false}
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-bwzfXH hrDDAP"
className="euiFlexItem euiFlexItem--flexGrowZero sc-bxivhb bQCHAr"
>
<EuiIcon
color="#DB1374"
@ -425,17 +803,17 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
</Styled(EuiFlexItem)>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Styled(EuiTitle)>
<EuiTitle
className="sc-htpNat bTDdmQ"
className="sc-ifAKCX bfXlPM"
>
<p
className="euiTitle euiTitle--medium sc-htpNat bTDdmQ"
className="euiTitle euiTitle--medium sc-ifAKCX bfXlPM"
data-test-subj="stat-title"
>
1,714
@ -456,10 +834,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
key="stat-items-field-uniqueDestinationIps"
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<EuiFlexGroup
alignItems="center"
@ -473,11 +851,11 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
grow={false}
>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-bwzfXH hrDDAP"
className="euiFlexItem euiFlexItem--flexGrowZero sc-bxivhb bQCHAr"
>
<EuiIcon
color="#490092"
@ -516,17 +894,17 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
</Styled(EuiFlexItem)>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Styled(EuiTitle)>
<EuiTitle
className="sc-htpNat bTDdmQ"
className="sc-ifAKCX bfXlPM"
>
<p
className="euiTitle euiTitle--medium sc-htpNat bTDdmQ"
className="euiTitle euiTitle--medium sc-ifAKCX bfXlPM"
data-test-subj="stat-title"
>
2,359
@ -556,10 +934,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Memo(mockConstructor)
barChart={
@ -603,74 +981,65 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
</Styled(EuiFlexItem)>
<Styled(EuiFlexItem)>
<EuiFlexItem
className="sc-bwzfXH hrDDAP"
className="sc-bxivhb bQCHAr"
>
<div
className="euiFlexItem sc-bwzfXH hrDDAP"
className="euiFlexItem sc-bxivhb bQCHAr"
>
<Connect(GlobalTimeComponent)>
<GlobalTimeComponent
deleteAllQuery={[Function]}
from={0}
setGlobalQuery={[Function]}
to={1}
>
<Memo(mockConstructor)
areaChart={
Array [
<Memo(mockConstructor)
areaChart={
Array [
Object {
"color": "#DB1374",
"key": "uniqueSourceIpsHistogram",
"value": Array [
Object {
"color": "#DB1374",
"key": "uniqueSourceIpsHistogram",
"value": Array [
Object {
"x": 1556888400000,
"y": 565975,
},
Object {
"x": 1556931600000,
"y": 1084366,
},
Object {
"x": 1556974800000,
"y": 12280,
},
],
"x": 1556888400000,
"y": 565975,
},
Object {
"color": "#490092",
"key": "uniqueDestinationIpsHistogram",
"value": Array [
Object {
"x": 1556888400000,
"y": 565975,
},
Object {
"x": 1556931600000,
"y": 1084366,
},
Object {
"x": 1556974800000,
"y": 12280,
},
],
"x": 1556931600000,
"y": 1084366,
},
]
}
configs={
Object {
"axis": Object {
"xTickFormatter": [Function],
"yTickFormatter": [Function],
Object {
"x": 1556974800000,
"y": 12280,
},
"series": Object {
"xScaleType": "time",
"yScaleType": "linear",
],
},
Object {
"color": "#490092",
"key": "uniqueDestinationIpsHistogram",
"value": Array [
Object {
"x": 1556888400000,
"y": 565975,
},
}
}
/>
</GlobalTimeComponent>
</Connect(GlobalTimeComponent)>
Object {
"x": 1556931600000,
"y": 1084366,
},
Object {
"x": 1556974800000,
"y": 12280,
},
],
},
]
}
configs={
Object {
"axis": Object {
"xTickFormatter": [Function],
"yTickFormatter": [Function],
},
"series": Object {
"xScaleType": "time",
"yScaleType": "linear",
},
}
}
/>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>

View file

@ -33,9 +33,11 @@ import { KpiNetworkData, KpiHostsData } from '../../graphql/types';
jest.mock('../charts/barchart');
jest.mock('../charts/areachart');
const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
describe('Stat Items Component', () => {
const state: State = mockGlobalState;
const store = createStore(state, apolloClientObservable);
describe.each([
@ -43,9 +45,13 @@ describe('Stat Items Component', () => {
mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
from={from}
id="statItems"
index={0}
key="mock-keys"
to={to}
/>
</ReduxStoreProvider>
),
@ -54,11 +60,15 @@ describe('Stat Items Component', () => {
mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
areaChart={[]}
barChart={[]}
description="HOSTS"
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
from={from}
id="statItems"
index={0}
key="mock-keys"
to={to}
/>
</ReduxStoreProvider>
),
@ -91,24 +101,6 @@ describe('Stat Items Component', () => {
describe('rendering kpis with charts', () => {
const mockStatItemsData: StatItemsProps = {
fields: [
{
key: 'uniqueSourceIps',
description: 'Source',
value: 1714,
color: '#DB1374',
icon: 'cross',
},
{
key: 'uniqueDestinationIps',
description: 'Dest.',
value: 2359,
color: '#490092',
icon: 'cross',
},
],
enableAreaChart: true,
enableBarChart: true,
areaChart: [
{
key: 'uniqueSourceIpsHistogram',
@ -138,7 +130,29 @@ describe('Stat Items Component', () => {
},
],
description: 'UNIQUE_PRIVATE_IPS',
enableAreaChart: true,
enableBarChart: true,
fields: [
{
key: 'uniqueSourceIps',
description: 'Source',
value: 1714,
color: '#DB1374',
icon: 'cross',
},
{
key: 'uniqueDestinationIps',
description: 'Dest.',
value: 2359,
color: '#490092',
icon: 'cross',
},
],
from,
id: 'statItems',
index: 0,
key: 'mock-keys',
to,
};
let wrapper: ReactWrapper;
beforeAll(() => {
@ -212,7 +226,13 @@ describe('useKpiMatrixStatus', () => {
fieldsMapping: Readonly<StatItems[]>;
data: KpiNetworkData | KpiHostsData;
}) => {
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(fieldsMapping, data);
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
fieldsMapping,
data,
'statItem',
from,
to
);
return (
<div>

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ScaleType, niceTimeFormatter } from '@elastic/charts';
import {
EuiFlexGroup,
EuiFlexItem,
@ -13,17 +14,17 @@ import {
EuiTitle,
IconType,
} from '@elastic/eui';
import { get, getOr } from 'lodash/fp';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { get, getOr } from 'lodash/fp';
import { ScaleType, niceTimeFormatter } from '@elastic/charts';
import { BarChart } from '../charts/barchart';
import { AreaChart } from '../charts/areachart';
import { getEmptyTagValue } from '../empty_value';
import { ChartConfigsData, ChartData, ChartSeriesConfigs } from '../charts/common';
import { KpiHostsData, KpiNetworkData } from '../../graphql/types';
import { GlobalTime } from '../../containers/global_time';
import { AreaChart } from '../charts/areachart';
import { BarChart } from '../charts/barchart';
import { ChartConfigsData, ChartData, ChartSeriesConfigs } from '../charts/common';
import { getEmptyTagValue } from '../empty_value';
import { InspectButton } from '../inspect';
const FlexItem = styled(EuiFlexItem)`
min-width: 0;
@ -51,6 +52,7 @@ export interface StatItems {
enableAreaChart?: boolean;
enableBarChart?: boolean;
grow?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | true | false | null;
index: number;
areachartConfigs?: ChartSeriesConfigs;
barchartConfigs?: ChartSeriesConfigs;
}
@ -58,6 +60,10 @@ export interface StatItems {
export interface StatItemsProps extends StatItems {
areaChart?: ChartConfigsData[];
barChart?: ChartConfigsData[];
from: number;
id: string;
to: number;
}
export const numberFormatter = (value: string | number): string => value.toLocaleString();
@ -128,7 +134,10 @@ export const addValueToBarChart = (
export const useKpiMatrixStatus = (
mappings: Readonly<StatItems[]>,
data: KpiHostsData | KpiNetworkData
data: KpiHostsData | KpiNetworkData,
id: string,
from: number,
to: number
): StatItemsProps[] => {
const [statItemsProps, setStatItemsProps] = useState(mappings as StatItemsProps[]);
@ -138,10 +147,13 @@ export const useKpiMatrixStatus = (
mappings.map(stat => {
return {
...stat,
key: `kpi-summary-${stat.key}`,
fields: addValueToFields(stat.fields, data),
areaChart: stat.enableAreaChart ? addValueToAreaChart(stat.fields, data) : undefined,
barChart: stat.enableBarChart ? addValueToBarChart(stat.fields, data) : undefined,
fields: addValueToFields(stat.fields, data),
id,
key: `kpi-summary-${stat.key}`,
from,
to,
};
})
);
@ -153,7 +165,20 @@ export const useKpiMatrixStatus = (
};
export const StatItemsComponent = React.memo<StatItemsProps>(
({ fields, description, grow, barChart, areaChart, enableAreaChart, enableBarChart }) => {
({
areaChart,
barChart,
description,
enableAreaChart,
enableBarChart,
fields,
from,
grow,
id,
index,
to,
}) => {
const [isHover, setIsHover] = useState(false);
const isBarChartDataAvailable =
barChart &&
barChart.length &&
@ -164,10 +189,22 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
areaChart.every(item => item.value != null && item.value.length > 0);
return (
<FlexItem grow={grow}>
<EuiPanel>
<EuiTitle size="xxxs">
<h6>{description}</h6>
</EuiTitle>
<EuiPanel onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
<EuiFlexGroup gutterSize={'none'}>
<EuiFlexItem>
<EuiTitle size="xxxs">
<h6>{description}</h6>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<InspectButton
queryId={id}
title={`KPI ${description}`}
inspectIndex={index}
show={isHover}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
{fields.map(field => (
@ -198,7 +235,6 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
</EuiFlexGroup>
{(enableAreaChart || enableBarChart) && <EuiHorizontalRule />}
<EuiFlexGroup>
{enableBarChart && (
<FlexItem>
@ -206,13 +242,9 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
</FlexItem>
)}
{enableAreaChart && (
{enableAreaChart && from != null && to != null && (
<FlexItem>
<GlobalTime>
{({ from, to }) => (
<AreaChart areaChart={areaChart} configs={areachartConfigs(from, to)} />
)}
</GlobalTime>
<AreaChart areaChart={areaChart} configs={areachartConfigs(from, to)} />
</FlexItem>
)}
</EuiFlexGroup>

View file

@ -30,6 +30,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -56,6 +57,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -83,6 +85,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={true}
title=""
@ -112,6 +115,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title={title}
@ -144,6 +148,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -176,6 +181,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={true}
isFavorite={false}
title=""
@ -207,6 +213,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -241,6 +248,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -277,6 +285,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -311,6 +320,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -345,6 +355,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -377,6 +388,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""
@ -406,6 +418,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title={title}
@ -433,6 +446,7 @@ describe('Properties', () => {
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDataInTimeline={false}
isDatepickerLocked={false}
isFavorite={false}
title=""

View file

@ -17,7 +17,10 @@ import * as React from 'react';
import styled, { injectGlobal } from 'styled-components';
import { Note } from '../../../lib/note';
import { InputsModelId } from '../../../store/inputs/constants';
import { InspectButton } from '../../inspect';
import { AssociateNote, UpdateNote } from '../../notes/helpers';
import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal';
import { SuperDatePicker } from '../../super_date_picker';
import { Description, Name, NewTimeline, NotesButton, StarIcon } from './helpers';
@ -29,8 +32,6 @@ import {
LockIconContainer,
} from './styles';
import * as i18n from './translations';
import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal';
import { InputsModelId } from '../../../store/inputs/constants';
type CreateTimeline = ({ id, show }: { id: string; show?: boolean }) => void;
type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void;
@ -70,6 +71,7 @@ const HiddenFlexItem = styled(EuiFlexItem)`
interface Props {
associateNote: AssociateNote;
createTimeline: CreateTimeline;
isDataInTimeline: boolean;
isDatepickerLocked: boolean;
isFavorite: boolean;
title: string;
@ -136,6 +138,7 @@ export class Properties extends React.PureComponent<Props, State> {
description,
getNotesByIds,
isFavorite,
isDataInTimeline,
isDatepickerLocked,
title,
noteIds,
@ -269,6 +272,18 @@ export class Properties extends React.PureComponent<Props, State> {
<OpenTimelineModalButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<InspectButton
queryId={timelineId}
inputId="timeline"
inspectIndex={0}
isDisabled={!isDataInTimeline}
onCloseInspect={this.onClosePopover}
show={true}
title={i18n.INSPECT_TIMELINE_TITLE}
/>
</EuiFlexItem>
{width < showNotesThreshold ? (
<EuiFlexItem grow={false}>
<NotesButton

View file

@ -32,6 +32,13 @@ export const TIMELINE_TITLE = i18n.translate(
}
);
export const INSPECT_TIMELINE_TITLE = i18n.translate(
'xpack.siem.timeline.properties.inspectTimelineTitle',
{
defaultMessage: 'Timeline',
}
);
export const UNTITLED_TIMELINE = i18n.translate(
'xpack.siem.timeline.properties.untitledTimelinePlaceholder',
{

View file

@ -14,8 +14,9 @@ import { InputsModelId } from '../../store/inputs/constants';
interface TimelineRefetchDispatch {
setTimelineQuery: ActionCreator<{
inputId: InputsModelId;
id: string;
inputId: InputsModelId;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}>;
@ -24,6 +25,7 @@ interface TimelineRefetchDispatch {
interface TimelineRefetchProps {
children: React.ReactNode;
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}
@ -32,9 +34,9 @@ type OwnProps = TimelineRefetchDispatch & TimelineRefetchProps;
class TimelineRefetchComponent extends React.PureComponent<OwnProps> {
public componentDidUpdate(prevProps: OwnProps) {
const { loading, id, refetch } = this.props;
const { loading, id, inspect, refetch } = this.props;
if (prevProps.loading !== loading) {
this.props.setTimelineQuery({ inputId: 'timeline', id, loading, refetch });
this.props.setTimelineQuery({ id, inputId: 'timeline', inspect, loading, refetch });
}
}

View file

@ -140,6 +140,7 @@ export const Timeline = pure<Props>(
{combinedQueries != null ? (
<TimelineQuery
id={id}
fields={columnsHeader.map(c => c.id)}
sourceId="default"
limit={itemsPerPage}
@ -149,8 +150,17 @@ export const Timeline = pure<Props>(
direction: sort.sortDirection as Direction,
}}
>
{({ events, loading, totalCount, pageInfo, loadMore, getUpdatedAt, refetch }) => (
<TimelineRefetch loading={loading} id={id} refetch={refetch}>
{({
events,
inspect,
loading,
totalCount,
pageInfo,
loadMore,
getUpdatedAt,
refetch,
}) => (
<TimelineRefetch loading={loading} id={id} inspect={inspect} refetch={refetch}>
<TimelineContext.Provider value={{ isLoading: loading }} />
<StatefulBody
browserFields={browserFields}

View file

@ -13,6 +13,7 @@ export const authenticationsQuery = gql`
$pagination: PaginationInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -62,6 +63,10 @@ export const authenticationsQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -12,14 +12,17 @@ import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { AuthenticationsEdges, GetAuthenticationsQuery, PageInfo } from '../../graphql/types';
import { hostsModel, hostsSelectors, inputsModel, State } from '../../store';
import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store';
import { createFilter, getDefaultFetchPolicy } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { authenticationsQuery } from './index.gql_query';
const ID = 'authenticationQuery';
export interface AuthenticationArgs {
id: string;
inspect: inputsModel.InspectQuery;
authentications: AuthenticationsEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -34,6 +37,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface AuthenticationsComponentReduxProps {
isInspected: boolean;
limit: number;
}
@ -46,7 +50,8 @@ class AuthenticationsComponentQuery extends QueryTemplate<
> {
public render() {
const {
id = 'authenticationQuery',
id = ID,
isInspected,
children,
filterQuery,
skip,
@ -75,6 +80,7 @@ class AuthenticationsComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -108,6 +114,7 @@ class AuthenticationsComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.Authentications.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Authentications.totalCount', data),
@ -123,8 +130,13 @@ class AuthenticationsComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getAuthenticationsSelector = hostsSelectors.authenticationsSelector();
const mapStateToProps = (state: State, { type }: OwnProps) => {
return getAuthenticationsSelector(state, type);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getAuthenticationsSelector(state, type),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -17,6 +17,7 @@ export const domainsQuery = gql`
$sort: DomainsSortField!
$timerange: TimerangeInput!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -61,6 +62,10 @@ export const domainsQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -19,14 +19,17 @@ import {
FlowTarget,
PageInfo,
} from '../../graphql/types';
import { inputsModel, networkModel, networkSelectors, State } from '../../store';
import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { domainsQuery } from './index.gql_query';
const ID = 'domainsQuery';
export interface DomainsArgs {
id: string;
inspect: inputsModel.InspectQuery;
domains: DomainsEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -43,6 +46,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface DomainsComponentReduxProps {
isInspected: boolean;
limit: number;
domainsSortField: DomainsSortField;
flowDirection: FlowDirection;
@ -57,7 +61,8 @@ class DomainsComponentQuery extends QueryTemplate<
> {
public render() {
const {
id = 'domainsQuery',
id = ID,
isInspected,
children,
domainsSortField,
filterQuery,
@ -94,6 +99,7 @@ class DomainsComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -124,6 +130,7 @@ class DomainsComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.Domains.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Domains.totalCount', data),
@ -139,9 +146,14 @@ class DomainsComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getDomainsSelector = networkSelectors.domainsSelector();
const mapStateToProps = (state: State) => ({
...getDomainsSelector(state),
});
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getDomainsSelector(state),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -14,6 +14,7 @@ export const eventsQuery = gql`
$sortField: SortField!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -32,6 +33,10 @@ export const eventsQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
edges {
node {
_id

View file

@ -12,14 +12,17 @@ import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { Direction, Ecs, GetEventsQuery, PageInfo } from '../../graphql/types';
import { hostsModel, hostsSelectors, inputsModel, State } from '../../store';
import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store';
import { createFilter, getDefaultFetchPolicy } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { eventsQuery } from './index.gql_query';
const ID = 'eventsQuery';
export interface EventsArgs {
id: string;
inspect: inputsModel.InspectQuery;
events: Ecs[];
loading: boolean;
loadMore: (cursor: string, tiebreaker: string) => void;
@ -34,6 +37,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface EventsComponentReduxProps {
isInspected: boolean;
limit: number;
}
@ -48,7 +52,8 @@ class EventsComponentQuery extends QueryTemplate<
const {
children,
filterQuery,
id = 'eventsQuery',
id = ID,
isInspected,
limit,
skip,
sourceId,
@ -79,6 +84,7 @@ class EventsComponentQuery extends QueryTemplate<
to: endDate!,
},
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -110,6 +116,7 @@ class EventsComponentQuery extends QueryTemplate<
}));
return children!({
id,
inspect: getOr(null, 'source.Events.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Events.totalCount', data),
@ -125,8 +132,13 @@ class EventsComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getEventsSelector = hostsSelectors.eventsSelector();
const mapStateToProps = (state: State, { type }: OwnProps) => {
return getEventsSelector(state, type);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getEventsSelector(state, type),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -16,7 +16,17 @@ interface GlobalTimeArgs {
from: number;
to: number;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
{
id,
inspect,
loading,
refetch,
}: {
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}
) => void;
}
@ -28,6 +38,7 @@ interface GlobalTimeDispatch {
setGlobalQuery: ActionCreator<{
inputId: InputsModelId;
id: string;
inspect: inputsModel.InspectQuery | null;
loading: boolean;
refetch: inputsModel.Refetch;
}>;
@ -42,7 +53,7 @@ interface GlobalTimeReduxState {
type GlobalTimeProps = OwnProps & GlobalTimeReduxState & GlobalTimeDispatch;
class GlobalTimeComponent extends React.PureComponent<GlobalTimeProps> {
public componentDidMount() {
public componentWillUnmount() {
this.props.deleteAllQuery({ id: 'global' });
}
@ -53,8 +64,8 @@ class GlobalTimeComponent extends React.PureComponent<GlobalTimeProps> {
{children({
from,
to,
setQuery: ({ id, loading, refetch }) =>
setGlobalQuery({ inputId: 'global', id, loading, refetch }),
setQuery: ({ id, inspect, loading, refetch }) =>
setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }),
})}
</>
);

View file

@ -14,6 +14,7 @@ export const HostsTableQuery = gql`
$sort: HostsSortField!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -48,6 +49,10 @@ export const HostsTableQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -19,7 +19,7 @@ import {
HostsFields,
PageInfo,
} from '../../graphql/types';
import { hostsModel, hostsSelectors, inputsModel, State } from '../../store';
import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
@ -27,8 +27,11 @@ import { HostsTableQuery } from './hosts_table.gql_query';
export { HostsFilter } from './filter';
const ID = 'hostsQuery';
export interface HostsArgs {
id: string;
inspect: inputsModel.InspectQuery;
hosts: HostsEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -47,6 +50,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface HostsComponentReduxProps {
isInspected: boolean;
limit: number;
sortField: HostsFields;
direction: Direction;
@ -71,7 +75,8 @@ class HostsComponentQuery extends QueryTemplate<
public render() {
const {
id = 'hostsQuery',
id = ID,
isInspected,
children,
direction,
filterQuery,
@ -82,6 +87,7 @@ class HostsComponentQuery extends QueryTemplate<
sourceId,
sortField,
} = this.props;
const variables: GetHostsTableQuery.Variables = {
sourceId,
timerange: {
@ -100,6 +106,7 @@ class HostsComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
};
return (
<Query<GetHostsTableQuery.Query, GetHostsTableQuery.Variables>
@ -136,6 +143,7 @@ class HostsComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.Hosts.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Hosts.totalCount', data),
@ -158,8 +166,13 @@ class HostsComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getHostsSelector = hostsSelectors.hostsSelector();
const mapStateToProps = (state: State, { type }: OwnProps) => {
return getHostsSelector(state, type);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getHostsSelector(state, type),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -12,6 +12,7 @@ export const HostOverviewQuery = gql`
$hostName: String!
$timerange: TimerangeInput!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -41,6 +42,10 @@ export const HostOverviewQuery = gql`
provider
region
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -8,16 +8,20 @@ import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import chrome from 'ui/chrome';
import { connect } from 'react-redux';
import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel } from '../../../store';
import { inputsModel, inputsSelectors, State } from '../../../store';
import { getDefaultFetchPolicy } from '../../helpers';
import { QueryTemplate, QueryTemplateProps } from '../../query_template';
import { HostOverviewQuery } from './host_overview.gql_query';
import { GetHostOverviewQuery, HostItem } from '../../../graphql/types';
const ID = 'hostOverviewQuery';
export interface HostOverviewArgs {
id: string;
inspect: inputsModel.InspectQuery;
hostOverview: HostItem;
loading: boolean;
refetch: inputsModel.Refetch;
@ -25,6 +29,10 @@ export interface HostOverviewArgs {
endDate: number;
}
export interface HostOverviewReduxProps {
isInspected: boolean;
}
export interface OwnProps extends QueryTemplateProps {
children: (args: HostOverviewArgs) => React.ReactNode;
hostName: string;
@ -32,14 +40,15 @@ export interface OwnProps extends QueryTemplateProps {
endDate: number;
}
export class HostOverviewByNameQuery extends QueryTemplate<
OwnProps,
class HostOverviewByNameComponentQuery extends QueryTemplate<
OwnProps & HostOverviewReduxProps,
GetHostOverviewQuery.Query,
GetHostOverviewQuery.Variables
> {
public render() {
const {
id = 'hostOverviewQuery',
id = ID,
isInspected,
children,
hostName,
skip,
@ -62,12 +71,14 @@ export class HostOverviewByNameQuery extends QueryTemplate<
to: endDate,
},
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const hostOverview = getOr([], 'source.HostOverview', data);
return children({
id,
inspect: getOr(null, 'source.HostOverview.inspect', data),
refetch,
loading,
hostOverview,
@ -79,3 +90,18 @@ export class HostOverviewByNameQuery extends QueryTemplate<
);
}
}
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const HostOverviewByNameQuery = connect(makeMapStateToProps)(
HostOverviewByNameComponentQuery
);

View file

@ -12,6 +12,7 @@ export const ipOverviewQuery = gql`
$filterQuery: String
$ip: String!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -72,6 +73,10 @@ export const ipOverviewQuery = gql`
}
type
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,21 +7,30 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { GetIpOverviewQuery, IpOverviewData } from '../../graphql/types';
import { networkModel } from '../../store';
import { networkModel, inputsModel, inputsSelectors, State } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplateProps } from '../query_template';
import { ipOverviewQuery } from './index.gql_query';
const ID = 'ipOverviewQuery';
export interface IpOverviewArgs {
id: string;
inspect: inputsModel.InspectQuery;
ipOverviewData: IpOverviewData;
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface IpOverviewReduxProps {
isInspected: boolean;
}
export interface IpOverviewProps extends QueryTemplateProps {
@ -30,8 +39,8 @@ export interface IpOverviewProps extends QueryTemplateProps {
ip: string;
}
export const IpOverviewQuery = pure<IpOverviewProps>(
({ id = 'ipOverviewQuery', children, filterQuery, skip, sourceId, ip }) => (
const IpOverviewComponentQuery = pure<IpOverviewProps & IpOverviewReduxProps>(
({ id = ID, isInspected, children, filterQuery, skip, sourceId, ip }) => (
<Query<GetIpOverviewQuery.Query, GetIpOverviewQuery.Variables>
query={ipOverviewQuery}
fetchPolicy="cache-and-network"
@ -42,17 +51,33 @@ export const IpOverviewQuery = pure<IpOverviewProps>(
filterQuery: createFilter(filterQuery),
ip,
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading }) => {
{({ data, loading, refetch }) => {
const init: IpOverviewData = { host: {} };
const ipOverviewData: IpOverviewData = getOr(init, 'source.IpOverview', data);
return children({
id,
inspect: getOr(null, 'source.IpOverview.inspect', data),
ipOverviewData,
loading,
refetch,
});
}}
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: IpOverviewProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const IpOverviewQuery = connect(makeMapStateToProps)(IpOverviewComponentQuery);

View file

@ -17,6 +17,7 @@ export const kpiHostDetailsQuery = gql`
$timerange: TimerangeInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -41,6 +42,10 @@ export const kpiHostDetailsQuery = gql`
uniqueDestinationIpsHistogram {
...KpiHostDetailsChartFields
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,18 +7,23 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { KpiHostDetailsData, GetKpiHostDetailsQuery } from '../../graphql/types';
import { inputsModel } from '../../store';
import { inputsModel, inputsSelectors, State } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplateProps } from '../query_template';
import { kpiHostDetailsQuery } from './index.gql_query';
const ID = 'kpiHostDetailsQuery';
export interface KpiHostDetailsArgs {
id: string;
inspect: inputsModel.InspectQuery;
kpiHostDetails: KpiHostDetailsData;
loading: boolean;
refetch: inputsModel.Refetch;
@ -28,8 +33,12 @@ export interface QueryKpiHostDetailsProps extends QueryTemplateProps {
children: (args: KpiHostDetailsArgs) => React.ReactNode;
}
export const KpiHostDetailsQuery = React.memo<QueryKpiHostDetailsProps>(
({ id = 'kpiHostDetailsQuery', children, endDate, filterQuery, skip, sourceId, startDate }) => (
export interface KpiHostDetailsReducer {
isInspected: boolean;
}
const KpiHostDetailsComponentQuery = pure<QueryKpiHostDetailsProps & KpiHostDetailsReducer>(
({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => (
<Query<GetKpiHostDetailsQuery.Query, GetKpiHostDetailsQuery.Variables>
query={kpiHostDetailsQuery}
fetchPolicy="cache-and-network"
@ -44,12 +53,14 @@ export const KpiHostDetailsQuery = React.memo<QueryKpiHostDetailsProps>(
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const kpiHostDetails = getOr({}, `source.KpiHostDetails`, data);
return children({
id,
inspect: getOr(null, 'source.KpiHostDetails.inspect', data),
kpiHostDetails,
loading,
refetch,
@ -58,3 +69,16 @@ export const KpiHostDetailsQuery = React.memo<QueryKpiHostDetailsProps>(
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: QueryKpiHostDetailsProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const KpiHostDetailsQuery = connect(makeMapStateToProps)(KpiHostDetailsComponentQuery);

View file

@ -17,6 +17,7 @@ export const kpiHostsQuery = gql`
$timerange: TimerangeInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -41,6 +42,10 @@ export const kpiHostsQuery = gql`
uniqueDestinationIpsHistogram {
...KpiHostChartFields
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,29 +7,38 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import { pure } from 'recompose';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { GetKpiHostsQuery, KpiHostsData } from '../../graphql/types';
import { inputsModel } from '../../store';
import { inputsModel, inputsSelectors, State } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplateProps } from '../query_template';
import { kpiHostsQuery } from './index.gql_query';
const ID = 'kpiHostsQuery';
export interface KpiHostsArgs {
id: string;
inspect: inputsModel.InspectQuery;
kpiHosts: KpiHostsData;
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface KpiHostsReducer {
isInspected: boolean;
}
export interface KpiHostsProps extends QueryTemplateProps {
children: (args: KpiHostsArgs) => React.ReactNode;
}
export const KpiHostsQuery = React.memo<KpiHostsProps>(
({ id = 'kpiHostsQuery', children, endDate, filterQuery, skip, sourceId, startDate }) => (
const KpiHostsComponentQuery = pure<KpiHostsProps & KpiHostsReducer>(
({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => (
<Query<GetKpiHostsQuery.Query, GetKpiHostsQuery.Variables>
query={kpiHostsQuery}
fetchPolicy="cache-and-network"
@ -44,12 +53,14 @@ export const KpiHostsQuery = React.memo<KpiHostsProps>(
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const kpiHosts = getOr({}, `source.KpiHosts`, data);
return children({
id,
inspect: getOr(null, 'source.KpiHosts.inspect', data),
kpiHosts,
loading,
refetch,
@ -58,3 +69,16 @@ export const KpiHostsQuery = React.memo<KpiHostsProps>(
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: KpiHostsProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const KpiHostsQuery = connect(makeMapStateToProps)(KpiHostsComponentQuery);

View file

@ -17,6 +17,7 @@ export const kpiNetworkQuery = gql`
$timerange: TimerangeInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -33,6 +34,10 @@ export const kpiNetworkQuery = gql`
}
dnsQueries
tlsHandshakes
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,29 +7,38 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { GetKpiNetworkQuery, KpiNetworkData } from '../../graphql/types';
import { inputsModel } from '../../store';
import { inputsModel, inputsSelectors, State } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplateProps } from '../query_template';
import { kpiNetworkQuery } from './index.gql_query';
const ID = 'kpiNetworkQuery';
export interface KpiNetworkArgs {
id: string;
inspect: inputsModel.InspectQuery;
kpiNetwork: KpiNetworkData;
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface KpiNetworkReducer {
isInspected: boolean;
}
export interface KpiNetworkProps extends QueryTemplateProps {
children: (args: KpiNetworkArgs) => React.ReactNode;
}
export const KpiNetworkQuery = React.memo<KpiNetworkProps>(
({ id = 'kpiNetworkQuery', children, filterQuery, skip, sourceId, startDate, endDate }) => (
const KpiNetworkComponentQuery = pure<KpiNetworkProps & KpiNetworkReducer>(
({ id = ID, children, filterQuery, isInspected, skip, sourceId, startDate, endDate }) => (
<Query<GetKpiNetworkQuery.Query, GetKpiNetworkQuery.Variables>
query={kpiNetworkQuery}
fetchPolicy="cache-and-network"
@ -44,12 +53,14 @@ export const KpiNetworkQuery = React.memo<KpiNetworkProps>(
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const kpiNetwork = getOr({}, `source.KpiNetwork`, data);
return children({
id,
inspect: getOr(null, 'source.KpiNetwork.inspect', data),
kpiNetwork,
loading,
refetch,
@ -58,3 +69,16 @@ export const KpiNetworkQuery = React.memo<KpiNetworkProps>(
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: KpiNetworkProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const KpiNetworkQuery = connect(makeMapStateToProps)(KpiNetworkComponentQuery);

View file

@ -15,6 +15,7 @@ export const networkDnsQuery = gql`
$pagination: PaginationInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -46,6 +47,10 @@ export const networkDnsQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -17,14 +17,17 @@ import {
NetworkDnsSortField,
PageInfo,
} from '../../graphql/types';
import { inputsModel, networkModel, networkSelectors, State } from '../../store';
import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { networkDnsQuery } from './index.gql_query';
const ID = 'networkDnsQuery';
export interface NetworkDnsArgs {
id: string;
inspect: inputsModel.InspectQuery;
networkDns: NetworkDnsEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -39,6 +42,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface NetworkDnsComponentReduxProps {
isInspected: boolean;
limit: number;
dnsSortField: NetworkDnsSortField;
isPtrIncluded: boolean;
@ -53,7 +57,8 @@ class NetworkDnsComponentQuery extends QueryTemplate<
> {
public render() {
const {
id = 'networkDnsQuery',
id = ID,
isInspected,
children,
dnsSortField,
filterQuery,
@ -86,6 +91,7 @@ class NetworkDnsComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -119,6 +125,7 @@ class NetworkDnsComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.NetworkDns.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.NetworkDns.totalCount', data),
@ -134,7 +141,14 @@ class NetworkDnsComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getNetworkDnsSelector = networkSelectors.dnsSelector();
const mapStateToProps = (state: State) => getNetworkDnsSelector(state);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getNetworkDnsSelector(state),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -16,6 +16,7 @@ export const networkTopNFlowQuery = gql`
$flowTarget: FlowTarget!
$timerange: TimerangeInput!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -67,6 +68,10 @@ export const networkTopNFlowQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -19,14 +19,17 @@ import {
NetworkTopNFlowSortField,
PageInfo,
} from '../../graphql/types';
import { inputsModel, networkModel, networkSelectors, State } from '../../store';
import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { networkTopNFlowQuery } from './index.gql_query';
const ID = 'networkTopNFlowQuery';
export interface NetworkTopNFlowArgs {
id: string;
inspect: inputsModel.InspectQuery;
networkTopNFlow: NetworkTopNFlowEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -41,6 +44,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface NetworkTopNFlowComponentReduxProps {
isInspected: boolean;
limit: number;
flowDirection: FlowDirection;
topNFlowSort: NetworkTopNFlowSortField;
@ -56,7 +60,8 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
> {
public render() {
const {
id = 'networkTopNFlowQuery',
id = ID,
isInspected,
children,
filterQuery,
skip,
@ -91,6 +96,7 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -124,6 +130,7 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.NetworkTopNFlow.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.NetworkTopNFlow.totalCount', data),
@ -139,8 +146,14 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getNetworkTopNFlowSelector = networkSelectors.topNFlowSelector();
const mapStateToProps = (state: State) => getNetworkTopNFlowSelector(state);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getNetworkTopNFlowSelector(state),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -12,6 +12,7 @@ export const overviewHostQuery = gql`
$timerange: TimerangeInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -24,6 +25,10 @@ export const overviewHostQuery = gql`
auditbeatUser
filebeatSystemModule
winlogbeat
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,24 +7,33 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { GetOverviewHostQuery, OverviewHostData } from '../../../graphql/types';
import { inputsModel } from '../../../store/inputs';
import { inputsModel, inputsSelectors } from '../../../store/inputs';
import { State } from '../../../store';
import { createFilter } from '../../helpers';
import { QueryTemplateProps } from '../../query_template';
import { overviewHostQuery } from './index.gql_query';
export const ID = 'overviewHostQuery';
export interface OverviewHostArgs {
id: string;
overviewHost: OverviewHostData;
inspect: inputsModel.InspectQuery;
loading: boolean;
overviewHost: OverviewHostData;
refetch: inputsModel.Refetch;
}
export interface OverviewHostReducer {
isInspected: boolean;
}
export interface OverviewHostProps extends QueryTemplateProps {
children: (args: OverviewHostArgs) => React.ReactNode;
sourceId: string;
@ -32,8 +41,8 @@ export interface OverviewHostProps extends QueryTemplateProps {
startDate: number;
}
export const OverviewHostQuery = pure<OverviewHostProps>(
({ id = 'overviewHostQuery', children, filterQuery, sourceId, startDate, endDate }) => (
const OverviewHostComponentQuery = pure<OverviewHostProps & OverviewHostReducer>(
({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => (
<Query<GetOverviewHostQuery.Query, GetOverviewHostQuery.Variables>
query={overviewHostQuery}
fetchPolicy="cache-and-network"
@ -46,12 +55,14 @@ export const OverviewHostQuery = pure<OverviewHostProps>(
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const overviewHost = getOr({}, `source.OverviewHost`, data);
return children({
id,
inspect: getOr(null, 'source.OverviewHost.inspect', data),
overviewHost,
loading,
refetch,
@ -60,3 +71,16 @@ export const OverviewHostQuery = pure<OverviewHostProps>(
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OverviewHostProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const OverviewHostQuery = connect(makeMapStateToProps)(OverviewHostComponentQuery);

View file

@ -12,6 +12,7 @@ export const overviewNetworkQuery = gql`
$timerange: TimerangeInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -29,6 +30,10 @@ export const overviewNetworkQuery = gql`
packetbeatDNS
packetbeatFlow
packetbeatTLS
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -7,24 +7,33 @@
import { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { GetOverviewNetworkQuery, OverviewNetworkData } from '../../../graphql/types';
import { inputsModel } from '../../../store/inputs';
import { State } from '../../../store';
import { inputsModel, inputsSelectors } from '../../../store/inputs';
import { createFilter } from '../../helpers';
import { QueryTemplateProps } from '../../query_template';
import { overviewNetworkQuery } from './index.gql_query';
export const ID = 'overviewNetworkQuery';
export interface OverviewNetworkArgs {
id: string;
inspect: inputsModel.InspectQuery;
overviewNetwork: OverviewNetworkData;
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface OverviewNetworkReducer {
isInspected: boolean;
}
export interface OverviewNetworkProps extends QueryTemplateProps {
children: (args: OverviewNetworkArgs) => React.ReactNode;
sourceId: string;
@ -32,8 +41,8 @@ export interface OverviewNetworkProps extends QueryTemplateProps {
startDate: number;
}
export const OverviewNetworkQuery = pure<OverviewNetworkProps>(
({ id = 'overviewNetworkQuery', children, filterQuery, sourceId, startDate, endDate }) => (
export const OverviewNetworkComponentQuery = pure<OverviewNetworkProps & OverviewNetworkReducer>(
({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => (
<Query<GetOverviewNetworkQuery.Query, GetOverviewNetworkQuery.Variables>
query={overviewNetworkQuery}
fetchPolicy="cache-and-network"
@ -47,12 +56,14 @@ export const OverviewNetworkQuery = pure<OverviewNetworkProps>(
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, refetch }) => {
const overviewNetwork = getOr({}, `source.OverviewNetwork`, data);
return children({
id,
inspect: getOr(null, 'source.OverviewNetwork.inspect', data),
overviewNetwork,
loading,
refetch,
@ -61,3 +72,16 @@ export const OverviewNetworkQuery = pure<OverviewNetworkProps>(
</Query>
)
);
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OverviewNetworkProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const OverviewNetworkQuery = connect(makeMapStateToProps)(OverviewNetworkComponentQuery);

View file

@ -14,6 +14,7 @@ export const timelineQuery = gql`
$sortField: SortField!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -25,6 +26,10 @@ export const timelineQuery = gql`
defaultIndex: $defaultIndex
) {
totalCount
inspect @include(if: $inspect) {
dsl
response
}
pageInfo {
endCursor {
value

View file

@ -10,6 +10,7 @@ import React from 'react';
import { Query } from 'react-apollo';
import chrome from 'ui/chrome';
import { connect } from 'react-redux';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import {
GetTimelineQuery,
@ -18,7 +19,7 @@ import {
TimelineEdges,
TimelineItem,
} from '../../graphql/types';
import { inputsModel } from '../../store';
import { inputsModel, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
@ -27,6 +28,7 @@ import { timelineQuery } from './index.gql_query';
export interface TimelineArgs {
events: TimelineItem[];
id: string;
inspect: inputsModel.InspectQuery;
loading: boolean;
loadMore: (cursor: string, tieBreaker: string) => void;
pageInfo: PageInfo;
@ -35,22 +37,28 @@ export interface TimelineArgs {
getUpdatedAt: () => number;
}
export interface TimelineQueryReduxProps {
isInspected: boolean;
}
export interface OwnProps extends QueryTemplateProps {
children?: (args: TimelineArgs) => React.ReactNode;
id: string;
limit: number;
sortField: SortField;
fields: string[];
}
type TimelineQueryProps = OwnProps & TimelineQueryReduxProps;
export class TimelineQuery extends QueryTemplate<
OwnProps,
class TimelineQueryComponent extends QueryTemplate<
TimelineQueryProps,
GetTimelineQuery.Query,
GetTimelineQuery.Variables
> {
private updatedDate: number = Date.now();
private memoizedTimelineEvents: (variables: string, events: TimelineEdges[]) => TimelineItem[];
constructor(props: OwnProps) {
constructor(props: TimelineQueryProps) {
super(props);
this.memoizedTimelineEvents = memoizeOne(this.getTimelineEvents);
}
@ -58,7 +66,8 @@ export class TimelineQuery extends QueryTemplate<
public render() {
const {
children,
id = 'timelineQuery',
id,
isInspected,
limit,
fields,
filterQuery,
@ -72,6 +81,7 @@ export class TimelineQuery extends QueryTemplate<
pagination: { limit, cursor: null, tiebreaker: null },
sortField,
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
};
return (
<Query<GetTimelineQuery.Query, GetTimelineQuery.Variables>
@ -113,6 +123,7 @@ export class TimelineQuery extends QueryTemplate<
this.updatedDate = Date.now();
return children!({
id,
inspect: getOr(null, 'source.Timeline.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Timeline.totalCount', data),
@ -131,3 +142,16 @@ export class TimelineQuery extends QueryTemplate<
private getTimelineEvents = (variables: string, timelineEdges: TimelineEdges[]): TimelineItem[] =>
timelineEdges.map((e: TimelineEdges) => e.node);
}
const makeMapStateToProps = () => {
const getQuery = inputsSelectors.timelineQueryByIdSelector();
const mapStateToProps = (state: State, { id }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
isInspected,
};
};
return mapStateToProps;
};
export const TimelineQuery = connect(makeMapStateToProps)(TimelineQueryComponent);

View file

@ -16,6 +16,7 @@ export const tlsQuery = gql`
$sort: TlsSortField!
$timerange: TimerangeInput!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -48,6 +49,10 @@ export const tlsQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -12,14 +12,17 @@ import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { FlowTarget, PageInfo, TlsEdges, TlsSortField, GetTlsQuery } from '../../graphql/types';
import { inputsModel, networkModel, networkSelectors, State } from '../../store';
import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { tlsQuery } from './index.gql_query';
const ID = 'tlsQuery';
export interface TlsArgs {
id: string;
inspect: inputsModel.InspectQuery;
tls: TlsEdges[];
totalCount: number;
pageInfo: PageInfo;
@ -36,6 +39,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface TlsComponentReduxProps {
isInspected: boolean;
limit: number;
tlsSortField: TlsSortField;
}
@ -45,7 +49,8 @@ type TlsProps = OwnProps & TlsComponentReduxProps;
class TlsComponentQuery extends QueryTemplate<TlsProps, GetTlsQuery.Query, GetTlsQuery.Variables> {
public render() {
const {
id = 'tlsQuery',
id = ID,
isInspected,
children,
tlsSortField,
filterQuery,
@ -80,6 +85,7 @@ class TlsComponentQuery extends QueryTemplate<TlsProps, GetTlsQuery.Query, GetTl
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -110,6 +116,7 @@ class TlsComponentQuery extends QueryTemplate<TlsProps, GetTlsQuery.Query, GetTl
}));
return children({
id,
inspect: getOr(null, 'source.Tls.inspect', data),
refetch,
loading,
totalCount: getOr(0, 'source.Tls.totalCount', data),
@ -125,9 +132,14 @@ class TlsComponentQuery extends QueryTemplate<TlsProps, GetTlsQuery.Query, GetTl
const makeMapStateToProps = () => {
const getTlsSelector = networkSelectors.tlsSelector();
const mapStateToProps = (state: State) => ({
...getTlsSelector(state),
});
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getTlsSelector(state),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -13,6 +13,7 @@ export const uncommonProcessesQuery = gql`
$pagination: PaginationInput!
$filterQuery: String
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -49,6 +50,10 @@ export const uncommonProcessesQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

View file

@ -12,12 +12,14 @@ import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { GetUncommonProcessesQuery, PageInfo, UncommonProcessesEdges } from '../../graphql/types';
import { hostsModel, hostsSelectors, inputsModel, State } from '../../store';
import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store';
import { createFilter, getDefaultFetchPolicy } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { uncommonProcessesQuery } from './index.gql_query';
const ID = 'uncommonProcessesQuery';
export interface UncommonProcessesArgs {
id: string;
uncommonProcesses: UncommonProcessesEdges[];
@ -26,6 +28,7 @@ export interface UncommonProcessesArgs {
loading: boolean;
loadMore: (cursor: string) => void;
refetch: inputsModel.Refetch;
inspect: inputsModel.InspectQuery;
}
export interface OwnProps extends QueryTemplateProps {
@ -34,6 +37,7 @@ export interface OwnProps extends QueryTemplateProps {
}
export interface UncommonProcessesComponentReduxProps {
isInspected: boolean;
limit: number;
}
@ -46,9 +50,10 @@ class UncommonProcessesComponentQuery extends QueryTemplate<
> {
public render() {
const {
id = 'uncommonProcessesQuery',
id = ID,
children,
filterQuery,
isInspected,
skip,
sourceId,
startDate,
@ -75,6 +80,7 @@ class UncommonProcessesComponentQuery extends QueryTemplate<
},
filterQuery: createFilter(filterQuery),
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
inspect: isInspected,
}}
>
{({ data, loading, fetchMore, refetch }) => {
@ -108,6 +114,7 @@ class UncommonProcessesComponentQuery extends QueryTemplate<
}));
return children({
id,
inspect: getOr(null, 'source.UncommonProcesses.inspect', data),
loading,
refetch,
totalCount: getOr(0, 'source.UncommonProcesses.totalCount', data),
@ -123,8 +130,13 @@ class UncommonProcessesComponentQuery extends QueryTemplate<
const makeMapStateToProps = () => {
const getUncommonProcessesSelector = hostsSelectors.uncommonProcessesSelector();
const mapStateToProps = (state: State, { type }: OwnProps) => {
return getUncommonProcessesSelector(state, type);
const getQuery = inputsSelectors.globalQueryByIdSelector();
const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getUncommonProcessesSelector(state, type),
isInspected,
};
};
return mapStateToProps;
};

View file

@ -16,6 +16,7 @@ export const usersQuery = gql`
$sort: UsersSortField!
$timerange: TimerangeInput!
$defaultIndex: [String!]!
$inspect: Boolean!
) {
source(id: $sourceId) {
id
@ -49,6 +50,10 @@ export const usersQuery = gql`
}
hasNextPage
}
inspect @include(if: $inspect) {
dsl
response
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more