mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[SIEM] Top Talkers: Split into Source and Destination (#43719)
* wip * sources table working woohoo * rename table back to n flow * wired up both source and destination tables * cleanups * flows and ips sort * flows and ips sort * fix flow sorting * differentiate tables * bring back network * fix tests * integration tests updated * add country names to flags, sort by desc on first click * yarn doing its thing * correct panel width during initial load * remove default props * fix inspect btn placement at small viewport sizes * reformatting bytes * used compressed table prop * fix unit tests bytes * update header subtext size * override table cell display flex * make titles plural * less distracting empty string * simplify markup and align numbers right * improve more items experience * undo compressed tables…looks bad * stack tables * restore compressed and side by side layout * sentence case for titles * table cleanup * force more to separate line * dnd poc changes * jest test updates * TypeScript, i18n, and bean-gen fixes * fix for filter action color * single quotes instead of backticks * use getEmptyValue() instead of static emdash * remove “ AS” prefix * address PR concerns * add space * first attempt at prop change with matchMedia * split table display by mediaMatch * rm comments * fix type issue * correct react hook * lint fix * fix jest * update snapshots
This commit is contained in:
parent
93e2eb3e74
commit
29b8ce2b7e
78 changed files with 4036 additions and 2749 deletions
|
@ -53,6 +53,11 @@ export const sharedSchema = gql`
|
|||
source
|
||||
}
|
||||
|
||||
enum FlowTargetNew {
|
||||
destination
|
||||
source
|
||||
}
|
||||
|
||||
enum FlowDirection {
|
||||
uniDirectional
|
||||
biDirectional
|
||||
|
|
|
@ -39,4 +39,4 @@
|
|||
"**/mocha-multi-reporters/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,6 @@ describe('Bytes', () => {
|
|||
.find(PreferenceFormattedBytes)
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('1.177MB');
|
||||
).toEqual('1.2MB');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Draggable,
|
||||
|
@ -14,7 +13,7 @@ import {
|
|||
Droppable,
|
||||
} from 'react-beautiful-dnd';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
|
||||
import { dragAndDropActions } from '../../store/drag_and_drop';
|
||||
|
@ -28,17 +27,114 @@ export const DragEffects = styled.div``;
|
|||
|
||||
DragEffects.displayName = 'DragEffects';
|
||||
|
||||
const ProviderContainer = styled.div`
|
||||
&:hover {
|
||||
transition: background-color 0.7s ease;
|
||||
background-color: ${props => props.theme.eui.euiColorLightShade};
|
||||
const Wrapper = styled.div`
|
||||
.euiPageBody & {
|
||||
display: inline-block;
|
||||
}
|
||||
`;
|
||||
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
const ProviderContainer = styled.div<{ isDragging: boolean }>`
|
||||
${({ theme, isDragging }) => css`
|
||||
// ALL DRAGGABLES
|
||||
|
||||
&,
|
||||
&::before,
|
||||
&::after {
|
||||
transition: background ${theme.eui.euiAnimSpeedFast} ease,
|
||||
color ${theme.eui.euiAnimSpeedFast} ease;
|
||||
}
|
||||
|
||||
// PAGE DRAGGABLES
|
||||
|
||||
${!isDragging &&
|
||||
`
|
||||
.euiPageBody & {
|
||||
border-radius: 2px;
|
||||
padding: 0 4px 0 8px;
|
||||
position: relative;
|
||||
z-index: ${theme.eui.euiZLevel0} !important;
|
||||
|
||||
&::before {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
${theme.eui.euiColorMediumShade} 25%,
|
||||
transparent 25%
|
||||
),
|
||||
linear-gradient(-135deg, ${theme.eui.euiColorMediumShade} 25%, transparent 25%),
|
||||
linear-gradient(135deg, transparent 75%, ${theme.eui.euiColorMediumShade} 75%),
|
||||
linear-gradient(-135deg, transparent 75%, ${theme.eui.euiColorMediumShade} 75%);
|
||||
background-position: 0 0, 1px 0, 1px -1px, 0px 1px;
|
||||
background-size: 2px 2px;
|
||||
bottom: 2px;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.euiPageBody tr:hover & {
|
||||
background-color: ${theme.eui.euiColorLightShade};
|
||||
|
||||
&::before {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
${theme.eui.euiColorDarkShade} 25%,
|
||||
transparent 25%
|
||||
),
|
||||
linear-gradient(-135deg, ${theme.eui.euiColorDarkShade} 25%, transparent 25%),
|
||||
linear-gradient(135deg, transparent 75%, ${theme.eui.euiColorDarkShade} 75%),
|
||||
linear-gradient(-135deg, transparent 75%, ${theme.eui.euiColorDarkShade} 75%);
|
||||
}
|
||||
}
|
||||
|
||||
.euiPageBody &:hover,
|
||||
.euiPageBody &:focus,
|
||||
.euiPageBody tr:hover &:hover,
|
||||
.euiPageBody tr:hover &:focus {
|
||||
background-color: ${theme.eui.euiColorPrimary};
|
||||
|
||||
&,
|
||||
& a {
|
||||
color: ${theme.eui.euiColorEmptyShade};
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
${theme.eui.euiColorEmptyShade} 25%,
|
||||
transparent 25%
|
||||
),
|
||||
linear-gradient(-135deg, ${theme.eui.euiColorEmptyShade} 25%, transparent 25%),
|
||||
linear-gradient(135deg, transparent 75%, ${theme.eui.euiColorEmptyShade} 75%),
|
||||
linear-gradient(-135deg, transparent 75%, ${theme.eui.euiColorEmptyShade} 75%);
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
${isDragging &&
|
||||
`
|
||||
.euiPageBody & {
|
||||
z-index: ${theme.eui.euiZLevel9} !important;
|
||||
`}
|
||||
|
||||
// TIMELINE DRAGGABLES
|
||||
|
||||
.timeline-flyout &:hover {
|
||||
background-color: ${theme.eui.euiColorLightShade};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
ProviderContainer.displayName = 'ProviderContainer';
|
||||
|
||||
interface OwnProps {
|
||||
dataProvider: DataProvider;
|
||||
inline?: boolean;
|
||||
render: (
|
||||
props: DataProvider,
|
||||
provided: DraggableProvided,
|
||||
|
@ -86,7 +182,7 @@ class DraggableWrapperComponent extends React.Component<Props> {
|
|||
const { dataProvider, render, width } = this.props;
|
||||
|
||||
return (
|
||||
<div data-test-subj="draggableWrapperDiv">
|
||||
<Wrapper data-test-subj="draggableWrapperDiv">
|
||||
<Droppable isDropDisabled={true} droppableId={getDroppableId(dataProvider.id)}>
|
||||
{droppableProvided => (
|
||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
|
@ -95,38 +191,40 @@ class DraggableWrapperComponent extends React.Component<Props> {
|
|||
index={0}
|
||||
key={dataProvider.id}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ProviderContainer
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
innerRef={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
zIndex: 9000, // EuiFlyout has a z-index of 8000
|
||||
}}
|
||||
>
|
||||
{width != null && !snapshot.isDragging ? (
|
||||
<TruncatableText
|
||||
data-test-subj="draggable-truncatable-content"
|
||||
size="s"
|
||||
width={width}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</TruncatableText>
|
||||
) : (
|
||||
<EuiText data-test-subj="draggable-content" size="s">
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</EuiText>
|
||||
)}
|
||||
</ProviderContainer>
|
||||
)}
|
||||
{(provided, snapshot) => {
|
||||
return (
|
||||
<ProviderContainer
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
innerRef={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
isDragging={snapshot.isDragging}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
}}
|
||||
>
|
||||
{width != null && !snapshot.isDragging ? (
|
||||
<TruncatableText
|
||||
data-test-subj="draggable-truncatable-content"
|
||||
size="s"
|
||||
width={width}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</TruncatableText>
|
||||
) : (
|
||||
<span data-test-subj="draggable-content">
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</span>
|
||||
)}
|
||||
</ProviderContainer>
|
||||
);
|
||||
}}
|
||||
</Draggable>
|
||||
{droppableProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('EmptyValue', () => {
|
|||
});
|
||||
|
||||
describe('#getEmptyValue', () => {
|
||||
test('should return an empty value', () => expect(getEmptyValue()).toBe('--'));
|
||||
test('should return an empty value', () => expect(getEmptyValue()).toBe('—'));
|
||||
});
|
||||
|
||||
describe('#getEmptyString', () => {
|
||||
|
@ -44,8 +44,12 @@ describe('EmptyValue', () => {
|
|||
});
|
||||
|
||||
describe('#getEmptyTagValue', () => {
|
||||
const wrapper = mount(<p>{getEmptyTagValue()}</p>);
|
||||
test('should return an empty tag value', () => expect(wrapper.text()).toBe('--'));
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{getEmptyTagValue()}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
test('should return an empty tag value', () => expect(wrapper.text()).toBe('—'));
|
||||
});
|
||||
|
||||
describe('#getEmptyStringTag', () => {
|
||||
|
@ -70,12 +74,20 @@ describe('EmptyValue', () => {
|
|||
|
||||
describe('#defaultToEmptyTag', () => {
|
||||
test('should default to an empty value when a value is null', () => {
|
||||
const wrapper = mount(<p>{defaultToEmptyTag(null)}</p>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{defaultToEmptyTag(null)}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('should default to an empty value when a value is undefined', () => {
|
||||
const wrapper = mount(<p>{defaultToEmptyTag(undefined)}</p>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{defaultToEmptyTag(undefined)}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
|
@ -101,7 +113,11 @@ describe('EmptyValue', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const wrapper = mount(<p>{getOrEmptyTag('a.b.c', test)}</p>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{getOrEmptyTag('a.b.c', test)}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
|
@ -113,7 +129,11 @@ describe('EmptyValue', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const wrapper = mount(<p>{getOrEmptyTag('a.b.c', test)}</p>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{getOrEmptyTag('a.b.c', test)}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
|
@ -123,7 +143,11 @@ describe('EmptyValue', () => {
|
|||
b: {},
|
||||
},
|
||||
};
|
||||
const wrapper = mount(<p>{getOrEmptyTag('a.b.c', test)}</p>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<p>{getOrEmptyTag('a.b.c', test)}</p>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
|
|
|
@ -10,21 +10,17 @@ import styled from 'styled-components';
|
|||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const EmptyString = styled.span`
|
||||
color: ${({
|
||||
theme: {
|
||||
eui: { euiColorMediumShade },
|
||||
},
|
||||
}) => euiColorMediumShade};
|
||||
const EmptyWrapper = styled.span`
|
||||
color: ${props => props.theme.eui.euiColorMediumShade};
|
||||
`;
|
||||
|
||||
EmptyString.displayName = 'EmptyString';
|
||||
EmptyWrapper.displayName = 'EmptyWrapper';
|
||||
|
||||
export const getEmptyValue = () => '--';
|
||||
export const getEmptyValue = () => '—';
|
||||
export const getEmptyString = () => `(${i18n.EMPTY_STRING})`;
|
||||
|
||||
export const getEmptyTagValue = () => <>{getEmptyValue()}</>;
|
||||
export const getEmptyStringTag = () => <EmptyString>{getEmptyString()}</EmptyString>;
|
||||
export const getEmptyTagValue = () => <EmptyWrapper>{getEmptyValue()}</EmptyWrapper>;
|
||||
export const getEmptyStringTag = () => <EmptyWrapper>{getEmptyString()}</EmptyWrapper>;
|
||||
|
||||
export const defaultToEmptyTag = <T extends unknown>(item: T): JSX.Element => {
|
||||
if (item == null) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
import { mount } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
||||
import { mockBrowserFields } from '../../containers/source/mock';
|
||||
|
||||
|
@ -16,17 +18,20 @@ import * as i18n from './translations';
|
|||
const timelineId = 'test';
|
||||
|
||||
describe('CategoriesPane', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
test('it renders the expected title', () => {
|
||||
const wrapper = mount(
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
width={CATEGORY_PANE_WIDTH}
|
||||
onCategorySelected={jest.fn()}
|
||||
onUpdateColumns={jest.fn()}
|
||||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
width={CATEGORY_PANE_WIDTH}
|
||||
onCategorySelected={jest.fn()}
|
||||
onUpdateColumns={jest.fn()}
|
||||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -39,7 +44,7 @@ describe('CategoriesPane', () => {
|
|||
|
||||
test('it renders a "No fields match" message when filteredBrowserFields is empty', () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={{}}
|
||||
|
@ -49,7 +54,7 @@ describe('CategoriesPane', () => {
|
|||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
|
|
@ -12,14 +12,17 @@ import { mockBrowserFields } from '../../containers/source/mock';
|
|||
|
||||
import { CATEGORY_PANE_WIDTH, getFieldCount } from './helpers';
|
||||
import { CategoriesPane } from './categories_pane';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
||||
const timelineId = 'test';
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
describe('getCategoryColumns', () => {
|
||||
Object.keys(mockBrowserFields).forEach(categoryId => {
|
||||
test(`it renders the ${categoryId} category name (from filteredBrowserFields)`, () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -29,7 +32,7 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -44,7 +47,7 @@ describe('getCategoryColumns', () => {
|
|||
Object.keys(mockBrowserFields).forEach(categoryId => {
|
||||
test(`it renders the correct field count for the ${categoryId} category (from filteredBrowserFields)`, () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -54,7 +57,7 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -68,7 +71,7 @@ describe('getCategoryColumns', () => {
|
|||
|
||||
test('it renders a hover actions panel for the category name', () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -78,9 +81,8 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={''}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="category-link"]')
|
||||
|
@ -95,7 +97,7 @@ describe('getCategoryColumns', () => {
|
|||
const selectedCategoryId = 'auditd';
|
||||
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -105,7 +107,7 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={selectedCategoryId}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -118,7 +120,7 @@ describe('getCategoryColumns', () => {
|
|||
const notTheSelectedCategoryId = 'base';
|
||||
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -128,7 +130,7 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={selectedCategoryId}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -143,7 +145,7 @@ describe('getCategoryColumns', () => {
|
|||
const onCategorySelected = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CategoriesPane
|
||||
browserFields={mockBrowserFields}
|
||||
filteredBrowserFields={mockBrowserFields}
|
||||
|
@ -153,7 +155,7 @@ describe('getCategoryColumns', () => {
|
|||
selectedCategoryId={selectedCategoryId}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
exports[`formatted_bytes PreferenceFormattedBytes rendering renders correctly against snapshot 1`] = `
|
||||
<Fragment>
|
||||
2.676MB
|
||||
2.7MB
|
||||
</Fragment>
|
||||
`;
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('formatted_bytes', () => {
|
|||
test('it renders bytes to hardcoded format when no configuration exists', () => {
|
||||
mockUseKibanaUiSetting.mockImplementation(() => [null]);
|
||||
const wrapper = mount(<PreferenceFormattedBytes value={bytes} />);
|
||||
expect(wrapper.text()).toEqual('2.676MB');
|
||||
expect(wrapper.text()).toEqual('2.7MB');
|
||||
});
|
||||
|
||||
test('it renders bytes according to the default format', () => {
|
||||
|
@ -42,7 +42,7 @@ describe('formatted_bytes', () => {
|
|||
getMockKibanaUiSetting(mockFrameworks.default_browser)
|
||||
);
|
||||
const wrapper = mount(<PreferenceFormattedBytes value={bytes} />);
|
||||
expect(wrapper.text()).toEqual('2.676MB');
|
||||
expect(wrapper.text()).toEqual('2.7MB');
|
||||
});
|
||||
|
||||
test('it renders bytes supplied as a number according to the default format', () => {
|
||||
|
@ -50,7 +50,7 @@ describe('formatted_bytes', () => {
|
|||
getMockKibanaUiSetting(mockFrameworks.default_browser)
|
||||
);
|
||||
const wrapper = mount(<PreferenceFormattedBytes value={+bytes} />);
|
||||
expect(wrapper.text()).toEqual('2.676MB');
|
||||
expect(wrapper.text()).toEqual('2.7MB');
|
||||
});
|
||||
|
||||
test('it renders bytes according to new format', () => {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
|
|||
export const PreferenceFormattedBytes = React.memo<{ value: string | number }>(({ value }) => {
|
||||
const [bytesFormat] = useKibanaUiSetting(DEFAULT_BYTES_FORMAT);
|
||||
return (
|
||||
<>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[000]b')}</>
|
||||
<>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')}</>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getEmptyValue } from '../empty_value';
|
||||
import {
|
||||
getFormattedDurationString,
|
||||
getHumanizedDuration,
|
||||
|
@ -15,17 +16,16 @@ import {
|
|||
ONE_SECOND,
|
||||
ONE_YEAR,
|
||||
} from './helpers';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
describe('FormattedDurationHelpers', () => {
|
||||
describe('#getFormattedDurationString', () => {
|
||||
test('it returns a placeholder when the input is undefined', () => {
|
||||
expect(getFormattedDurationString(undefined)).toEqual('--');
|
||||
expect(getFormattedDurationString(undefined)).toEqual(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it returns a placeholder when the input is null', () => {
|
||||
expect(getFormattedDurationString(null)).toEqual('--');
|
||||
expect(getFormattedDurationString(null)).toEqual(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it echos back the input as a string when the input is not a number', () => {
|
||||
|
|
|
@ -49,7 +49,7 @@ export const HeaderPage = pure<HeaderPageProps>(
|
|||
</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
<EuiText color="subdued" size="xs">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -38,27 +38,33 @@ export interface HeaderPanelProps {
|
|||
export const HeaderPanel = pure<HeaderPanelProps>(
|
||||
({ border, children, id, showInspect = false, subtitle, title, tooltip }) => (
|
||||
<Header border={border}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="panel_headline_title">
|
||||
{title}
|
||||
{tooltip && (
|
||||
<>
|
||||
{' '}
|
||||
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="panel_headline_title">
|
||||
{title}
|
||||
{tooltip && (
|
||||
<>
|
||||
{' '}
|
||||
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
{id && <InspectButton queryId={id} inspectIndex={0} show={showInspect} title={title} />}
|
||||
{id && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButton queryId={id} inspectIndex={0} show={showInspect} title={title} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
|
|
|
@ -9,6 +9,7 @@ import * as React from 'react';
|
|||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { render } from 'react-testing-library';
|
||||
|
||||
import { getEmptyValue } from '../empty_value';
|
||||
import { LastEventIndexKey } from '../../graphql/types';
|
||||
import { mockLastEventTimeQuery } from '../../containers/events/last_event_time/mock';
|
||||
import { wait } from '../../lib/helpers';
|
||||
|
@ -92,6 +93,6 @@ describe('Last Event Time Stat', () => {
|
|||
);
|
||||
await wait();
|
||||
|
||||
expect(container.innerHTML).toBe('--');
|
||||
expect(container.innerHTML).toContain(getEmptyValue());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -104,6 +104,7 @@ type Func<T> = (arg: T) => string | number;
|
|||
|
||||
export interface Columns<T, U = T> {
|
||||
field?: string;
|
||||
align?: string;
|
||||
name: string | React.ReactNode;
|
||||
isMobileHeader?: boolean;
|
||||
sortable?: boolean | Func<T>;
|
||||
|
@ -209,8 +210,9 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
|
|||
) : (
|
||||
<>
|
||||
<BasicTable
|
||||
items={pageOfItems}
|
||||
columns={columns}
|
||||
compressed
|
||||
items={pageOfItems}
|
||||
onChange={onChange}
|
||||
sorting={
|
||||
sorting
|
||||
|
@ -311,6 +313,10 @@ const BasicTable = styled(EuiBasicTable)`
|
|||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.euiTableCellContent {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -72,7 +72,13 @@ export const AnomaliesHostTable = React.memo<AnomaliesHostTableProps>(
|
|||
tooltip={i18n.TOOLTIP}
|
||||
/>
|
||||
|
||||
<BasicTable items={hosts} columns={columns} pagination={pagination} sorting={sorting} />
|
||||
<BasicTable
|
||||
columns={columns}
|
||||
compressed
|
||||
items={hosts}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<Loader data-test-subj="anomalies-host-table-loading-panel" overlay size="xl" />
|
||||
|
|
|
@ -71,8 +71,9 @@ export const AnomaliesNetworkTable = React.memo<AnomaliesNetworkTableProps>(
|
|||
/>
|
||||
|
||||
<BasicTable
|
||||
items={networks}
|
||||
columns={columns}
|
||||
compressed
|
||||
items={networks}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
|
|
@ -5,14 +5,22 @@
|
|||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { EuiInMemoryTable } from '@elastic/eui';
|
||||
import { EuiInMemoryTable, EuiInMemoryTableProps } from '@elastic/eui';
|
||||
|
||||
export const BasicTable = styled(EuiInMemoryTable)`
|
||||
// TODO: Remove this once EuiBasicTable supports in its table props the boolean of compressed
|
||||
type ExtendedInMemoryTable = EuiInMemoryTableProps & { compressed: boolean };
|
||||
const Extended: React.FunctionComponent<ExtendedInMemoryTable> = EuiInMemoryTable;
|
||||
|
||||
export const BasicTable = styled(Extended)`
|
||||
tbody {
|
||||
th,
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.euiTableCellContent {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NoteCardBody renders correctly against snapshot 1`] = `
|
||||
<Component
|
||||
<NoteCardBody
|
||||
rawNote="# This is a note"
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
||||
import { NoteCard } from '.';
|
||||
|
||||
|
@ -13,15 +15,24 @@ describe('NoteCard', () => {
|
|||
const created = new Date();
|
||||
const rawNote = 'noteworthy';
|
||||
const user = 'elastic';
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
test('it renders a note card header', () => {
|
||||
const wrapper = mountWithIntl(<NoteCard created={created} rawNote={rawNote} user={user} />);
|
||||
const wrapper = mountWithIntl(
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCard created={created} rawNote={rawNote} user={user} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="note-card-header"]').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('it renders a note card body', () => {
|
||||
const wrapper = mountWithIntl(<NoteCard created={created} rawNote={rawNote} user={user} />);
|
||||
const wrapper = mountWithIntl(
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCard created={created} rawNote={rawNote} user={user} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="note-card-body"]').exists()).toEqual(true);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
import { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
||||
import { NoteCardBody } from './note_card_body';
|
||||
|
||||
|
@ -14,14 +16,23 @@ describe('NoteCardBody', () => {
|
|||
const markdownHeaderPrefix = '# '; // translates to an h1 in markdown
|
||||
const noteText = 'This is a note';
|
||||
const rawNote = `${markdownHeaderPrefix} ${noteText}`;
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
test('renders correctly against snapshot', () => {
|
||||
const wrapper = shallow(<NoteCardBody rawNote={rawNote} />);
|
||||
const wrapper = shallow(
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCardBody rawNote={rawNote} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders the text of the note in an h1', () => {
|
||||
const wrapper = mount(<NoteCardBody rawNote={rawNote} />);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCardBody rawNote={rawNote} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
||||
import { Note } from '../../../lib/note';
|
||||
|
||||
|
@ -13,6 +15,7 @@ import { NoteCards } from '.';
|
|||
|
||||
describe('NoteCards', () => {
|
||||
const noteIds = ['abc', 'def'];
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
const getNotesByIds = (_: string[]): Note[] => [
|
||||
{
|
||||
|
@ -37,15 +40,17 @@ describe('NoteCards', () => {
|
|||
|
||||
test('it renders the notes column when noteIds are specified', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="notes"]').exists()).toEqual(true);
|
||||
|
@ -53,15 +58,17 @@ describe('NoteCards', () => {
|
|||
|
||||
test('it does NOT render the notes column when noteIds are NOT specified', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={[]}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={[]}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="notes"]').exists()).toEqual(false);
|
||||
|
@ -69,15 +76,17 @@ describe('NoteCards', () => {
|
|||
|
||||
test('renders note cards', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -92,15 +101,17 @@ describe('NoteCards', () => {
|
|||
|
||||
test('it shows controls for adding notes when showAddNote is true', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={true}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="add-note"]').exists()).toEqual(true);
|
||||
|
@ -108,15 +119,17 @@ describe('NoteCards', () => {
|
|||
|
||||
test('it does NOT show controls for adding notes when showAddNote is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={false}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NoteCards
|
||||
associateNote={jest.fn()}
|
||||
getNotesByIds={getNotesByIds}
|
||||
getNewNoteId={jest.fn()}
|
||||
noteIds={noteIds}
|
||||
showAddNote={false}
|
||||
toggleShowAddNote={jest.fn()}
|
||||
updateNote={jest.fn()}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="add-note"]').exists()).toEqual(false);
|
||||
|
|
|
@ -187,7 +187,7 @@ describe('NotePreview', () => {
|
|||
.find('[data-test-subj="posted"]')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('Posted: --');
|
||||
).toEqual(`Posted: ${getEmptyValue()}`);
|
||||
});
|
||||
|
||||
test('it renders placeholder text when updated is null', () => {
|
||||
|
@ -202,7 +202,7 @@ describe('NotePreview', () => {
|
|||
.find('[data-test-subj="posted"]')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('Posted: --');
|
||||
).toEqual(`Posted: ${getEmptyValue()}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonIconProps } from '@elastic/eui';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { cloneDeep, omit } from 'lodash/fp';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page';
|
||||
import { mockTimelineResults } from '../../../mock/timeline_results';
|
||||
|
@ -18,6 +20,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
|
|||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('#getActionsColumns', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
let mockResults: OpenTimelineResult[];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -26,23 +29,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it renders the delete timeline (trash icon) when showDeleteAction is true (because showExtendedColumnsAndActions is true)', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(true);
|
||||
|
@ -50,23 +55,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it does NOT render the delete timeline (trash icon) when showDeleteAction is false (because showExtendedColumnsAndActions is false)', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(false);
|
||||
|
@ -74,22 +81,24 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it does NOT render the delete timeline (trash icon) when deleteTimelines is not provided', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(false);
|
||||
|
@ -130,23 +139,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it renders an enabled the open duplicate button if the timeline has have a saved object id', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
const props = wrapper
|
||||
|
@ -161,23 +172,25 @@ describe('#getActionsColumns', () => {
|
|||
const onOpenTimeline = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={onOpenTimeline}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={onOpenTimeline}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
|
|
@ -346,23 +346,25 @@ describe('#getCommonColumns', () => {
|
|||
describe('Timeline Name column', () => {
|
||||
test('it renders the expected column name', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -375,23 +377,25 @@ describe('#getCommonColumns', () => {
|
|||
|
||||
test('it renders the title when the timeline has a title and a saved object id', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -567,23 +571,25 @@ describe('#getCommonColumns', () => {
|
|||
|
||||
test('it renders a hyperlink when the timeline has a saved object id', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -631,23 +637,25 @@ describe('#getCommonColumns', () => {
|
|||
const onOpenTimeline = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={onOpenTimeline}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={onOpenTimeline}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -665,23 +673,25 @@ describe('#getCommonColumns', () => {
|
|||
describe('Description column', () => {
|
||||
test('it renders the expected column name', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -694,23 +704,25 @@ describe('#getCommonColumns', () => {
|
|||
|
||||
test('it renders the description when the timeline has a description', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -725,23 +737,25 @@ describe('#getCommonColumns', () => {
|
|||
const missingDescription: OpenTimelineResult[] = [omit('description', { ...mockResults[0] })];
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingDescription}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingDescription.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingDescription}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingDescription.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
|
@ -757,23 +771,25 @@ describe('#getCommonColumns', () => {
|
|||
];
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={justWhitespaceDescription}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={justWhitespaceDescription.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={justWhitespaceDescription}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={justWhitespaceDescription.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
|
@ -787,23 +803,25 @@ describe('#getCommonColumns', () => {
|
|||
describe('Last Modified column', () => {
|
||||
test('it renders the expected column name', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -816,23 +834,25 @@ describe('#getCommonColumns', () => {
|
|||
|
||||
test('it renders the last modified (updated) date when the timeline has an updated property', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -848,23 +868,25 @@ describe('#getCommonColumns', () => {
|
|||
const missingUpdated: OpenTimelineResult[] = [omit('updated', { ...mockResults[0] })];
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingUpdated}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingUpdated.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingUpdated}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingUpdated.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { cloneDeep, omit } from 'lodash/fp';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page';
|
||||
import { getEmptyValue } from '../../empty_value';
|
||||
|
@ -21,6 +23,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
|
|||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('#getExtendedColumns', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
let mockResults: OpenTimelineResult[];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -30,23 +33,25 @@ describe('#getExtendedColumns', () => {
|
|||
describe('Modified By column', () => {
|
||||
test('it renders the expected column name', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -59,23 +64,25 @@ describe('#getExtendedColumns', () => {
|
|||
|
||||
test('it renders the username when the timeline has an updatedBy property', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -90,23 +97,25 @@ describe('#getExtendedColumns', () => {
|
|||
const missingUpdatedBy: OpenTimelineResult[] = [omit('updatedBy', { ...mockResults[0] })];
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingUpdatedBy}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingUpdatedBy.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={missingUpdatedBy}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={missingUpdatedBy.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { cloneDeep, omit } from 'lodash/fp';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import 'jest-styled-components';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page';
|
||||
import { mockTimelineResults } from '../../../mock/timeline_results';
|
||||
|
@ -18,6 +20,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
|
|||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('#getActionsColumns', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
let mockResults: OpenTimelineResult[];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -26,23 +29,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it renders the pinned events header icon', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="pinned-event-header-icon"]').exists()).toBe(true);
|
||||
|
@ -76,23 +81,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it renders the notes count header icon', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="notes-count-header-icon"]').exists()).toBe(true);
|
||||
|
@ -126,23 +133,25 @@ describe('#getActionsColumns', () => {
|
|||
|
||||
test('it renders the favorites header icon', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="favorites-header-icon"]').exists()).toBe(true);
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page';
|
||||
import { mockTimelineResults } from '../../../mock/timeline_results';
|
||||
|
@ -19,6 +21,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
|
|||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('TimelinesTable', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
let mockResults: OpenTimelineResult[];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -27,23 +30,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it renders the select all timelines header checkbox when showExtendedColumnsAndActions is true', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -56,23 +61,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it does NOT render the select all timelines header checkbox when showExtendedColumnsAndActions is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -85,23 +92,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it renders the Modified By column when showExtendedColumnsAndActions is true ', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -114,23 +123,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it renders the notes column in the position of the Modified By column when showExtendedColumnsAndActions is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -145,23 +156,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it renders the delete timeline (trash icon) when showExtendedColumnsAndActions is true', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -174,23 +187,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it does NOT render the delete timeline (trash icon) when showExtendedColumnsAndActions is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -203,23 +218,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it renders the rows per page selector when showExtendedColumnsAndActions is true', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -232,23 +249,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it does NOT render the rows per page selector when showExtendedColumnsAndActions is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -263,23 +282,25 @@ describe('TimelinesTable', () => {
|
|||
const defaultPageSize = 123;
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={defaultPageSize}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={defaultPageSize}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={defaultPageSize}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={defaultPageSize}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -292,23 +313,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is true ', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -321,23 +344,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is false ', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={false}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -381,23 +406,25 @@ describe('TimelinesTable', () => {
|
|||
const onTableChange = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={onTableChange}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={onTableChange}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -417,23 +444,25 @@ describe('TimelinesTable', () => {
|
|||
const onSelectionChange = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -448,23 +477,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it enables the table loading animation when isLoading is true', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={true}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={true}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
const props = wrapper
|
||||
|
@ -477,23 +508,25 @@ describe('TimelinesTable', () => {
|
|||
|
||||
test('it disables the table loading animation when isLoading is false', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<TimelinesTable
|
||||
deleteTimelines={jest.fn()}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
loading={false}
|
||||
itemIdToExpandedNotesRowMap={{}}
|
||||
onOpenTimeline={jest.fn()}
|
||||
onSelectionChange={jest.fn()}
|
||||
onTableChange={jest.fn()}
|
||||
onToggleShowNotes={jest.fn()}
|
||||
pageIndex={0}
|
||||
pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={mockResults}
|
||||
showExtendedColumnsAndActions={true}
|
||||
sortDirection={DEFAULT_SORT_DIRECTION}
|
||||
sortField={DEFAULT_SORT_FIELD}
|
||||
totalSearchResultsCount={mockResults.length}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
const props = wrapper
|
||||
|
|
|
@ -144,13 +144,19 @@ describe('AddToKql Component', () => {
|
|||
|
||||
expect(store.getState().network.page).toEqual({
|
||||
queries: {
|
||||
topNFlow: {
|
||||
topNFlowDestination: {
|
||||
activePage: 0,
|
||||
limit: 10,
|
||||
flowDirection: 'uniDirectional',
|
||||
flowTarget: 'source',
|
||||
topNFlowSort: {
|
||||
field: 'bytes',
|
||||
field: 'bytes_out',
|
||||
direction: 'desc',
|
||||
},
|
||||
},
|
||||
topNFlowSource: {
|
||||
activePage: 0,
|
||||
limit: 10,
|
||||
topNFlowSort: {
|
||||
field: 'bytes_out',
|
||||
direction: 'desc',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -154,6 +154,7 @@ const getEventsColumns = (pageType: hostsModel.HostsType): EventsTableColumns =>
|
|||
) : (
|
||||
getEmptyTagValue()
|
||||
),
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
field: 'node',
|
||||
|
@ -167,6 +168,7 @@ const getEventsColumns = (pageType: hostsModel.HostsType): EventsTableColumns =>
|
|||
idPrefix: `host-${pageType}-events-table-${node._id}`,
|
||||
render: item => <HostDetailsLink hostName={item} />,
|
||||
}),
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
field: 'node',
|
||||
|
@ -180,7 +182,7 @@ const getEventsColumns = (pageType: hostsModel.HostsType): EventsTableColumns =>
|
|||
attrName: 'event.module',
|
||||
idPrefix: `host-${pageType}-events-table-${node._id}`,
|
||||
})}
|
||||
{'/'}
|
||||
{' / '}
|
||||
{getRowItemDraggables({
|
||||
rowItems: getOr(null, 'event.dataset', node),
|
||||
attrName: 'event.dataset',
|
||||
|
@ -254,7 +256,7 @@ const getEventsColumns = (pageType: hostsModel.HostsType): EventsTableColumns =>
|
|||
name: i18n.MESSAGE,
|
||||
sortable: false,
|
||||
truncateText: true,
|
||||
width: '25%',
|
||||
width: '15%',
|
||||
render: node => {
|
||||
const message = getOr(null, 'message[0]', node);
|
||||
return message != null ? (
|
||||
|
|
|
@ -41,7 +41,7 @@ export const MESSAGE = i18n.translate('xpack.siem.eventsTable.messageTitle', {
|
|||
});
|
||||
|
||||
export const EVENT_MODULE_DATASET = i18n.translate('xpack.siem.eventsTable.moduleDatasetTitle', {
|
||||
defaultMessage: 'Module/dataset',
|
||||
defaultMessage: 'Module / dataset',
|
||||
});
|
||||
|
||||
export const USER = i18n.translate('xpack.siem.eventsTable.userTitle', {
|
||||
|
|
|
@ -151,6 +151,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
|
|||
attrName: 'process.name',
|
||||
idPrefix: `uncommon-process-table-${node._id}-processName`,
|
||||
}),
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
name: i18n.NUMBER_OF_HOSTS,
|
||||
|
@ -175,6 +176,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
|
|||
idPrefix: `uncommon-process-table-${node._id}-processHost`,
|
||||
render: item => <HostDetailsLink hostName={item} />,
|
||||
}),
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
name: i18n.LAST_COMMAND,
|
||||
|
@ -187,6 +189,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
|
|||
idPrefix: `uncommon-process-table-${node._id}-processArgs`,
|
||||
displayCount: 1, // TODO: Change this back once we have improved the UI
|
||||
}),
|
||||
width: '35%',
|
||||
},
|
||||
{
|
||||
name: i18n.LAST_USER,
|
||||
|
|
|
@ -11,18 +11,41 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
|
|||
"node": Object {
|
||||
"destination": null,
|
||||
"network": Object {
|
||||
"bytes": 3826633497,
|
||||
"direction": Array [
|
||||
"inbound",
|
||||
],
|
||||
"packets": 4185805,
|
||||
"bytes_in": 3826633497,
|
||||
"bytes_out": 1083495734,
|
||||
},
|
||||
"source": Object {
|
||||
"count": 1,
|
||||
"autonomous_system": Object {
|
||||
"name": "Google, Inc",
|
||||
"number": 15169,
|
||||
},
|
||||
"destination_ips": 12,
|
||||
"domain": Array [
|
||||
"test.domain.com",
|
||||
],
|
||||
"flows": 12345,
|
||||
"ip": "8.8.8.8",
|
||||
"location": Object {
|
||||
"flowTarget": "source",
|
||||
"geo": Object {
|
||||
"city_name": Array [
|
||||
"Mountain View",
|
||||
],
|
||||
"continent_name": Array [
|
||||
"North America",
|
||||
],
|
||||
"country_iso_code": Array [
|
||||
"US",
|
||||
],
|
||||
"country_name": null,
|
||||
"region_iso_code": Array [
|
||||
"US-CA",
|
||||
],
|
||||
"region_name": Array [
|
||||
"California",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -33,26 +56,50 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
|
|||
"node": Object {
|
||||
"destination": null,
|
||||
"network": Object {
|
||||
"bytes": 325909849,
|
||||
"direction": Array [
|
||||
"inbound",
|
||||
"outbound",
|
||||
],
|
||||
"packets": 221494,
|
||||
"bytes_in": 3826633497,
|
||||
"bytes_out": 1083495734,
|
||||
},
|
||||
"source": Object {
|
||||
"autonomous_system": Object {
|
||||
"name": "TM Net, Internet Service Provider",
|
||||
"number": 4788,
|
||||
},
|
||||
"destination_ips": 12,
|
||||
"domain": Array [
|
||||
"test.domain.net",
|
||||
"test.old.domain.net",
|
||||
],
|
||||
"flows": 12345,
|
||||
"ip": "9.9.9.9",
|
||||
"location": Object {
|
||||
"flowTarget": "source",
|
||||
"geo": Object {
|
||||
"city_name": Array [
|
||||
"Petaling Jaya",
|
||||
],
|
||||
"continent_name": Array [
|
||||
"Asia",
|
||||
],
|
||||
"country_iso_code": Array [
|
||||
"MY",
|
||||
],
|
||||
"country_name": null,
|
||||
"region_iso_code": Array [
|
||||
"MY-10",
|
||||
],
|
||||
"region_name": Array [
|
||||
"Selangor",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
fakeTotalCount={50}
|
||||
id="topNFlow"
|
||||
flowTargeted="source"
|
||||
id="topNFlowSource"
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
|
|
|
@ -4,91 +4,96 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash/fp';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { get, isEmpty } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { CountryFlag } from '../../../source_destination/country_flag';
|
||||
import {
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
TopNFlowNetworkEcsField,
|
||||
AutonomousSystemItem,
|
||||
FlowTargetNew,
|
||||
NetworkTopNFlowEdges,
|
||||
TopNFlowItem,
|
||||
TopNFlowNetworkEcsField,
|
||||
} from '../../../../graphql/types';
|
||||
import { assertUnreachable } from '../../../../lib/helpers';
|
||||
import { escapeQueryValue } from '../../../../lib/keury';
|
||||
import { networkModel } from '../../../../store';
|
||||
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
|
||||
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
|
||||
import { defaultToEmptyTag, getEmptyTagValue } from '../../../empty_value';
|
||||
import { getEmptyTagValue } from '../../../empty_value';
|
||||
import { IPDetailsLink } from '../../../links';
|
||||
import { Columns } from '../../../load_more_table';
|
||||
import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider';
|
||||
import { Provider } from '../../../timeline/data_providers/provider';
|
||||
import { AddToKql } from '../../add_to_kql';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { getRowItemDraggables } from '../../../tables/helpers';
|
||||
import { getRowItemDraggable, getRowItemDraggables } from '../../../tables/helpers';
|
||||
import { PreferenceFormattedBytes } from '../../../formatted_bytes';
|
||||
|
||||
export type NetworkTopNFlowColumns = [
|
||||
Columns<NetworkTopNFlowEdges>,
|
||||
Columns<NetworkTopNFlowEdges>,
|
||||
Columns<TopNFlowNetworkEcsField['direction']>,
|
||||
Columns<TopNFlowNetworkEcsField['bytes']>,
|
||||
Columns<TopNFlowNetworkEcsField['packets']>,
|
||||
Columns<TopNFlowItem['count']>
|
||||
Columns<NetworkTopNFlowEdges>,
|
||||
Columns<TopNFlowNetworkEcsField['bytes_in']>,
|
||||
Columns<TopNFlowNetworkEcsField['bytes_out']>,
|
||||
Columns<NetworkTopNFlowEdges>,
|
||||
Columns<NetworkTopNFlowEdges>
|
||||
];
|
||||
|
||||
export const getNetworkTopNFlowColumns = (
|
||||
indexPattern: StaticIndexPattern,
|
||||
flowDirection: FlowDirection,
|
||||
flowTarget: FlowTarget,
|
||||
flowTarget: FlowTargetNew,
|
||||
type: networkModel.NetworkType,
|
||||
tableId: string
|
||||
): NetworkTopNFlowColumns => [
|
||||
{
|
||||
name: getIpTitle(flowTarget),
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
name: i18n.IP_TITLE,
|
||||
render: ({ node }) => {
|
||||
const ipAttr = `${flowTarget}.ip`;
|
||||
const ip: string | null = get(ipAttr, node);
|
||||
const id = escapeDataProviderId(`${tableId}-table-${flowTarget}-${flowDirection}-ip-${ip}`);
|
||||
const geoAttr = `${flowTarget}.location.geo.country_iso_code[0]`;
|
||||
const geo: string | null = get(geoAttr, node);
|
||||
const id = escapeDataProviderId(`${tableId}-table-${flowTarget}-ip-${ip}`);
|
||||
|
||||
if (ip != null) {
|
||||
return (
|
||||
<DraggableWrapper
|
||||
key={id}
|
||||
dataProvider={{
|
||||
and: [],
|
||||
enabled: true,
|
||||
id,
|
||||
name: ip,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: { field: ipAttr, value: ip, operator: IS_OPERATOR },
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<IPDetailsLink ip={ip} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<DraggableWrapper
|
||||
key={id}
|
||||
dataProvider={{
|
||||
and: [],
|
||||
enabled: true,
|
||||
id,
|
||||
name: ip,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: { field: ipAttr, value: ip, operator: IS_OPERATOR },
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<IPDetailsLink ip={ip} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{geo && (
|
||||
<>
|
||||
{' '}
|
||||
<CountryFlag countryCode={geo} /> {geo}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return getEmptyTagValue();
|
||||
}
|
||||
},
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
name: i18n.DOMAIN,
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
render: ({ node }) => {
|
||||
const domainAttr = `${flowTarget}.domain`;
|
||||
const ipAttr = `${flowTarget}.ip`;
|
||||
|
@ -96,7 +101,7 @@ export const getNetworkTopNFlowColumns = (
|
|||
const ip: string | null = get(ipAttr, node);
|
||||
|
||||
if (Array.isArray(domains) && domains.length > 0) {
|
||||
const id = escapeDataProviderId(`${tableId}-table-${ip}-${flowDirection}`);
|
||||
const id = escapeDataProviderId(`${tableId}-table-${ip}`);
|
||||
return getRowItemDraggables({
|
||||
rowItems: domains,
|
||||
attrName: domainAttr,
|
||||
|
@ -107,38 +112,41 @@ export const getNetworkTopNFlowColumns = (
|
|||
return getEmptyTagValue();
|
||||
}
|
||||
},
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
field: 'node.network.direction',
|
||||
name: i18n.DIRECTION,
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
render: directions =>
|
||||
isEmpty(directions)
|
||||
? getEmptyTagValue()
|
||||
: directions &&
|
||||
directions.map((direction, index) => (
|
||||
<AddToKql
|
||||
indexPattern={indexPattern}
|
||||
key={escapeDataProviderId(
|
||||
`${tableId}-table-${flowTarget}-${flowDirection}-direction-${direction}`
|
||||
)}
|
||||
expression={`network.direction: "${escapeQueryValue(direction)}"`}
|
||||
componentFilterType="network"
|
||||
type={type}
|
||||
>
|
||||
<>
|
||||
{defaultToEmptyTag(direction)}
|
||||
{index < directions.length - 1 ? '\u00A0' : null}
|
||||
</>
|
||||
</AddToKql>
|
||||
)),
|
||||
name: i18n.AUTONOMOUS_SYSTEM,
|
||||
render: ({ node, cursor: { value: ipAddress } }) => {
|
||||
const asAttr = `${flowTarget}.autonomous_system`;
|
||||
const as: AutonomousSystemItem | null = get(asAttr, node);
|
||||
if (as != null) {
|
||||
const id = escapeDataProviderId(`${tableId}-table-${flowTarget}-ip-${ipAddress}`);
|
||||
return (
|
||||
<>
|
||||
{as.name &&
|
||||
getRowItemDraggable({
|
||||
rowItem: as.name,
|
||||
attrName: `${flowTarget}.as.organization.name`,
|
||||
idPrefix: `${id}-name`,
|
||||
})}
|
||||
{as.number &&
|
||||
getRowItemDraggable({
|
||||
rowItem: `${as.number}`,
|
||||
attrName: `${flowTarget}.as.number`,
|
||||
idPrefix: `${id}-number`,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return getEmptyTagValue();
|
||||
}
|
||||
},
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
field: 'node.network.bytes',
|
||||
name: i18n.BYTES,
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
align: 'right',
|
||||
field: 'node.network.bytes_in',
|
||||
name: i18n.BYTES_IN,
|
||||
sortable: true,
|
||||
render: bytes => {
|
||||
if (bytes != null) {
|
||||
|
@ -149,28 +157,39 @@ export const getNetworkTopNFlowColumns = (
|
|||
},
|
||||
},
|
||||
{
|
||||
field: 'node.network.packets',
|
||||
name: i18n.PACKETS,
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
align: 'right',
|
||||
field: 'node.network.bytes_out',
|
||||
name: i18n.BYTES_OUT,
|
||||
sortable: true,
|
||||
render: packets => {
|
||||
if (packets != null) {
|
||||
return numeral(packets).format('0,000');
|
||||
render: bytes => {
|
||||
if (bytes != null) {
|
||||
return <PreferenceFormattedBytes value={bytes} />;
|
||||
} else {
|
||||
return getEmptyTagValue();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
field: `node.${flowTarget}.count`,
|
||||
name: getUniqueTitle(flowTarget),
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
align: 'right',
|
||||
field: `node.${flowTarget}.flows`,
|
||||
name: i18n.FLOWS,
|
||||
sortable: true,
|
||||
render: ipCount => {
|
||||
if (ipCount != null) {
|
||||
return numeral(ipCount).format('0,000');
|
||||
render: flows => {
|
||||
if (flows != null) {
|
||||
return numeral(flows).format('0,000');
|
||||
} else {
|
||||
return getEmptyTagValue();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: `node.${flowTarget}.${getOppositeField(flowTarget)}_ips`,
|
||||
name: flowTarget === FlowTargetNew.source ? i18n.DESTINATION_IPS : i18n.SOURCE_IPS,
|
||||
sortable: true,
|
||||
render: ips => {
|
||||
if (ips != null) {
|
||||
return numeral(ips).format('0,000');
|
||||
} else {
|
||||
return getEmptyTagValue();
|
||||
}
|
||||
|
@ -178,30 +197,5 @@ export const getNetworkTopNFlowColumns = (
|
|||
},
|
||||
];
|
||||
|
||||
const getIpTitle = (flowTarget: FlowTarget) => {
|
||||
switch (flowTarget) {
|
||||
case FlowTarget.source:
|
||||
return i18n.SOURCE_IP;
|
||||
case FlowTarget.destination:
|
||||
return i18n.DESTINATION_IP;
|
||||
case FlowTarget.client:
|
||||
return i18n.CLIENT_IP;
|
||||
case FlowTarget.server:
|
||||
return i18n.SERVER_IP;
|
||||
}
|
||||
assertUnreachable(flowTarget);
|
||||
};
|
||||
|
||||
const getUniqueTitle = (flowTarget: FlowTarget) => {
|
||||
switch (flowTarget) {
|
||||
case FlowTarget.source:
|
||||
return i18n.UNIQUE_DESTINATION_IP;
|
||||
case FlowTarget.destination:
|
||||
return i18n.UNIQUE_SOURCE_IP;
|
||||
case FlowTarget.client:
|
||||
return i18n.UNIQUE_SERVER_IP;
|
||||
case FlowTarget.server:
|
||||
return i18n.UNIQUE_CLIENT_IP;
|
||||
}
|
||||
assertUnreachable(flowTarget);
|
||||
};
|
||||
const getOppositeField = (flowTarget: FlowTargetNew): FlowTargetNew =>
|
||||
flowTarget === FlowTargetNew.source ? FlowTargetNew.destination : FlowTargetNew.source;
|
||||
|
|
|
@ -11,20 +11,18 @@ import * as React from 'react';
|
|||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { FlowDirection } from '../../../../graphql/types';
|
||||
import { FlowTargetNew } from '../../../../graphql/types';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockIndexPattern,
|
||||
mockGlobalState,
|
||||
mockIndexPattern,
|
||||
TestProviders,
|
||||
} from '../../../../mock';
|
||||
import { createStore, networkModel, State } from '../../../../store';
|
||||
|
||||
import { NetworkTopNFlowTable, NetworkTopNFlowTableId } from '.';
|
||||
import { NetworkTopNFlowTable } from '.';
|
||||
import { mockData } from './mock';
|
||||
|
||||
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('NetworkTopNFlow Table Component', () => {
|
||||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
@ -42,7 +40,8 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
<NetworkTopNFlowTable
|
||||
data={mockData.NetworkTopNFlow.edges}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
id="topNFlow"
|
||||
flowTargeted={FlowTargetNew.source}
|
||||
id="topNFlowSource"
|
||||
indexPattern={mockIndexPattern}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
|
@ -61,97 +60,6 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Direction', () => {
|
||||
test('when you click on the bi-directional button, it get selected', () => {
|
||||
const event = {
|
||||
target: { name: 'direction', value: FlowDirection.biDirectional },
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<MockedProvider>
|
||||
<TestProviders store={store}>
|
||||
<NetworkTopNFlowTable
|
||||
data={mockData.NetworkTopNFlow.edges}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
id="topNFlow"
|
||||
indexPattern={mockIndexPattern}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
mockData.NetworkTopNFlow.pageInfo
|
||||
)}
|
||||
totalCount={mockData.NetworkTopNFlow.totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
</TestProviders>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(`[data-test-subj="${FlowDirection.biDirectional}"]`)
|
||||
.first()
|
||||
.simulate('click', event);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="${FlowDirection.biDirectional}"]`)
|
||||
.first()
|
||||
.render()
|
||||
.hasClass('euiFilterButton-hasActiveFilters')
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sorting by type', () => {
|
||||
test('when you click on the sorting dropdown, and picked destination', () => {
|
||||
const wrapper = mount(
|
||||
<MockedProvider>
|
||||
<TestProviders store={store}>
|
||||
<NetworkTopNFlowTable
|
||||
data={mockData.NetworkTopNFlow.edges}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
id="topNFlow"
|
||||
indexPattern={mockIndexPattern}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
mockData.NetworkTopNFlow.pageInfo
|
||||
)}
|
||||
totalCount={mockData.NetworkTopNFlow.totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
</TestProviders>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(`[data-test-subj="${NetworkTopNFlowTableId}-select-flow-target"] button`)
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find(`button#${NetworkTopNFlowTableId}-select-flow-target-destination`)
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="${NetworkTopNFlowTableId}-select-flow-target"] button`)
|
||||
.first()
|
||||
.text()
|
||||
.toLocaleLowerCase()
|
||||
).toEqual('by destination ip');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sorting on Table', () => {
|
||||
test('when you click on the column header, you should show the sorting icon', () => {
|
||||
const wrapper = mount(
|
||||
|
@ -160,7 +68,8 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
<NetworkTopNFlowTable
|
||||
data={mockData.NetworkTopNFlow.edges}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
id="topNFlow"
|
||||
flowTargeted={FlowTargetNew.source}
|
||||
id="topNFlowSource"
|
||||
indexPattern={mockIndexPattern}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
|
@ -175,9 +84,9 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
</TestProviders>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(store.getState().network.page.queries!.topNFlow.topNFlowSort).toEqual({
|
||||
expect(store.getState().network.page.queries.topNFlowSource.topNFlowSort).toEqual({
|
||||
direction: 'desc',
|
||||
field: 'bytes',
|
||||
field: 'bytes_out',
|
||||
});
|
||||
|
||||
wrapper
|
||||
|
@ -187,22 +96,22 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
|
||||
wrapper.update();
|
||||
|
||||
expect(store.getState().network.page.queries!.topNFlow.topNFlowSort).toEqual({
|
||||
expect(store.getState().network.page.queries.topNFlowSource.topNFlowSort).toEqual({
|
||||
direction: 'asc',
|
||||
field: 'packets',
|
||||
field: 'bytes_out',
|
||||
});
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiTable thead tr th button')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('BytesClick to sort in ascending order');
|
||||
).toEqual('Bytes inClick to sort in ascending order');
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiTable thead tr th button')
|
||||
.at(1)
|
||||
.text()
|
||||
).toEqual('PacketsClick to sort in descending order');
|
||||
).toEqual('Bytes outClick to sort in descending order');
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiTable thead tr th button')
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { isEqual, last } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -13,25 +13,22 @@ import { StaticIndexPattern } from 'ui/index_patterns';
|
|||
|
||||
import { networkActions } from '../../../../store/actions';
|
||||
import {
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
Direction,
|
||||
FlowTargetNew,
|
||||
NetworkTopNFlowEdges,
|
||||
NetworkTopNFlowFields,
|
||||
NetworkTopNFlowSortField,
|
||||
} from '../../../../graphql/types';
|
||||
import { networkModel, networkSelectors, State } from '../../../../store';
|
||||
import { FlowDirectionSelect } from '../../../flow_controls/flow_direction_select';
|
||||
import { FlowTargetSelect } from '../../../flow_controls/flow_target_select';
|
||||
import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table';
|
||||
|
||||
import { getNetworkTopNFlowColumns } from './columns';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const tableType = networkModel.NetworkTableType.topNFlow;
|
||||
|
||||
interface OwnProps {
|
||||
data: NetworkTopNFlowEdges[];
|
||||
fakeTotalCount: number;
|
||||
flowTargeted: FlowTargetNew;
|
||||
id: string;
|
||||
indexPattern: StaticIndexPattern;
|
||||
loading: boolean;
|
||||
|
@ -43,9 +40,7 @@ interface OwnProps {
|
|||
|
||||
interface NetworkTopNFlowTableReduxProps {
|
||||
limit: number;
|
||||
flowDirection: FlowDirection;
|
||||
topNFlowSort: NetworkTopNFlowSortField;
|
||||
flowTarget: FlowTarget;
|
||||
}
|
||||
|
||||
interface NetworkTopNFlowTableDispatchProps {
|
||||
|
@ -53,20 +48,15 @@ interface NetworkTopNFlowTableDispatchProps {
|
|||
activePage: number;
|
||||
tableType: networkModel.NetworkTableType;
|
||||
}>;
|
||||
updateTopNFlowDirection: ActionCreator<{
|
||||
flowDirection: FlowDirection;
|
||||
networkType: networkModel.NetworkType;
|
||||
}>;
|
||||
updateTopNFlowLimit: ActionCreator<{
|
||||
limit: number;
|
||||
networkType: networkModel.NetworkType;
|
||||
tableType: networkModel.TopNTableType;
|
||||
}>;
|
||||
updateTopNFlowSort: ActionCreator<{
|
||||
topNFlowSort: NetworkTopNFlowSortField;
|
||||
networkType: networkModel.NetworkType;
|
||||
}>;
|
||||
updateTopNFlowTarget: ActionCreator<{
|
||||
flowTarget: FlowTarget;
|
||||
tableType: networkModel.TopNTableType;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
@ -85,15 +75,14 @@ const rowItems: ItemsPerRow[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const NetworkTopNFlowTableId = 'networkTopNFlow-top-talkers';
|
||||
export const NetworkTopNFlowTableId = 'networkTopSourceFlow-top-talkers';
|
||||
|
||||
class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowTableProps> {
|
||||
public render() {
|
||||
const {
|
||||
data,
|
||||
flowDirection,
|
||||
flowTarget,
|
||||
fakeTotalCount,
|
||||
flowTargeted,
|
||||
id,
|
||||
indexPattern,
|
||||
limit,
|
||||
|
@ -104,64 +93,43 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
|
|||
totalCount,
|
||||
type,
|
||||
updateTopNFlowLimit,
|
||||
updateTopNFlowTarget,
|
||||
updateTableActivePage,
|
||||
} = this.props;
|
||||
|
||||
let tableType: networkModel.TopNTableType;
|
||||
let headerTitle: string;
|
||||
|
||||
if (flowTargeted === FlowTargetNew.source) {
|
||||
headerTitle = i18n.SOURCE_IP;
|
||||
tableType = networkModel.NetworkTableType.topNFlowSource;
|
||||
} else {
|
||||
headerTitle = i18n.DESTINATION_IP;
|
||||
tableType = networkModel.NetworkTableType.topNFlowDestination;
|
||||
}
|
||||
|
||||
const field =
|
||||
topNFlowSort.field === NetworkTopNFlowFields.ipCount
|
||||
? `node.${flowTarget}.count`
|
||||
: `node.network.${topNFlowSort.field}`;
|
||||
topNFlowSort.field === NetworkTopNFlowFields.bytes_out ||
|
||||
topNFlowSort.field === NetworkTopNFlowFields.bytes_in
|
||||
? `node.network.${topNFlowSort.field}`
|
||||
: `node.${flowTargeted}.${topNFlowSort.field}`;
|
||||
|
||||
return (
|
||||
<PaginatedTable
|
||||
columns={getNetworkTopNFlowColumns(
|
||||
indexPattern,
|
||||
flowDirection,
|
||||
flowTarget,
|
||||
flowTargeted,
|
||||
type,
|
||||
NetworkTopNFlowTableId
|
||||
)}
|
||||
headerCount={totalCount}
|
||||
headerSupplement={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectTypeItem
|
||||
grow={false}
|
||||
data-test-subj={`${NetworkTopNFlowTableId}-select-flow-target`}
|
||||
>
|
||||
<FlowTargetSelect
|
||||
id={NetworkTopNFlowTableId}
|
||||
isLoading={loading}
|
||||
selectedDirection={flowDirection}
|
||||
selectedTarget={flowTarget}
|
||||
displayTextOverride={[
|
||||
i18n.BY_SOURCE_IP,
|
||||
i18n.BY_DESTINATION_IP,
|
||||
i18n.BY_CLIENT_IP,
|
||||
i18n.BY_SERVER_IP,
|
||||
]}
|
||||
updateFlowTargetAction={updateTopNFlowTarget}
|
||||
/>
|
||||
</SelectTypeItem>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<FlowDirectionSelect
|
||||
selectedDirection={flowDirection}
|
||||
onChangeDirection={this.onChangeTopNFlowDirection}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
headerTitle={i18n.TOP_TALKERS}
|
||||
headerTitle={headerTitle}
|
||||
headerUnit={i18n.UNIT(totalCount)}
|
||||
id={id}
|
||||
itemsPerRow={rowItems}
|
||||
limit={limit}
|
||||
loading={loading}
|
||||
loadPage={newActivePage => loadPage(newActivePage)}
|
||||
onChange={this.onChange}
|
||||
onChange={criteria => this.onChange(criteria, tableType)}
|
||||
pageOfItems={data}
|
||||
showMorePagesIndicator={showMorePagesIndicator}
|
||||
sorting={{ field, direction: topNFlowSort.direction }}
|
||||
|
@ -173,47 +141,42 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
|
|||
})
|
||||
}
|
||||
updateLimitPagination={newLimit =>
|
||||
updateTopNFlowLimit({ limit: newLimit, networkType: type })
|
||||
updateTopNFlowLimit({ limit: newLimit, networkType: type, tableType })
|
||||
}
|
||||
updateProps={{ flowDirection, flowTarget, totalCount, topNFlowSort, field }}
|
||||
updateProps={{ totalCount, topNFlowSort, field }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private onChange = (criteria: Criteria) => {
|
||||
private onChange = (criteria: Criteria, tableType: networkModel.TopNTableType) => {
|
||||
if (criteria.sort != null) {
|
||||
const splitField = criteria.sort.field.split('.');
|
||||
const field = last(splitField) === 'count' ? NetworkTopNFlowFields.ipCount : last(splitField);
|
||||
const field = last(splitField);
|
||||
const newSortDirection =
|
||||
field !== this.props.topNFlowSort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click
|
||||
const newTopNFlowSort: NetworkTopNFlowSortField = {
|
||||
field: field as NetworkTopNFlowFields,
|
||||
direction: criteria.sort.direction,
|
||||
direction: newSortDirection,
|
||||
};
|
||||
if (!isEqual(newTopNFlowSort, this.props.topNFlowSort)) {
|
||||
this.props.updateTopNFlowSort({
|
||||
topNFlowSort: newTopNFlowSort,
|
||||
networkType: this.props.type,
|
||||
tableType,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onChangeTopNFlowDirection = (flowDirection: FlowDirection) =>
|
||||
this.props.updateTopNFlowDirection({ flowDirection, networkType: this.props.type });
|
||||
}
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getNetworkTopNFlowSelector = networkSelectors.topNFlowSelector();
|
||||
const mapStateToProps = (state: State) => getNetworkTopNFlowSelector(state);
|
||||
return mapStateToProps;
|
||||
};
|
||||
const mapStateToProps = (state: State, ownProps: OwnProps) =>
|
||||
networkSelectors.topNFlowSelector(ownProps.flowTargeted);
|
||||
|
||||
export const NetworkTopNFlowTable = connect(
|
||||
makeMapStateToProps,
|
||||
mapStateToProps,
|
||||
{
|
||||
updateTopNFlowLimit: networkActions.updateTopNFlowLimit,
|
||||
updateTopNFlowSort: networkActions.updateTopNFlowSort,
|
||||
updateTopNFlowTarget: networkActions.updateTopNFlowTarget,
|
||||
updateTopNFlowDirection: networkActions.updateTopNFlowDirection,
|
||||
updateTableActivePage: networkActions.updateNetworkPageTableActivePage,
|
||||
}
|
||||
)(NetworkTopNFlowTableComponent);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { NetworkDirectionEcs, NetworkTopNFlowData } from '../../../../graphql/types';
|
||||
import { NetworkTopNFlowData, FlowTarget } from '../../../../graphql/types';
|
||||
|
||||
export const mockData: { NetworkTopNFlow: NetworkTopNFlowData } = {
|
||||
NetworkTopNFlow: {
|
||||
|
@ -13,15 +13,30 @@ export const mockData: { NetworkTopNFlow: NetworkTopNFlowData } = {
|
|||
{
|
||||
node: {
|
||||
source: {
|
||||
ip: '8.8.8.8',
|
||||
autonomous_system: {
|
||||
name: 'Google, Inc',
|
||||
number: 15169,
|
||||
},
|
||||
domain: ['test.domain.com'],
|
||||
count: 1,
|
||||
flows: 12345,
|
||||
destination_ips: 12,
|
||||
ip: '8.8.8.8',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: ['North America'],
|
||||
country_name: null,
|
||||
country_iso_code: ['US'],
|
||||
city_name: ['Mountain View'],
|
||||
region_iso_code: ['US-CA'],
|
||||
region_name: ['California'],
|
||||
},
|
||||
flowTarget: FlowTarget.source,
|
||||
},
|
||||
},
|
||||
destination: null,
|
||||
network: {
|
||||
bytes: 3826633497,
|
||||
packets: 4185805,
|
||||
direction: [NetworkDirectionEcs.inbound],
|
||||
bytes_in: 3826633497,
|
||||
bytes_out: 1083495734,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
|
@ -31,14 +46,30 @@ export const mockData: { NetworkTopNFlow: NetworkTopNFlowData } = {
|
|||
{
|
||||
node: {
|
||||
source: {
|
||||
ip: '9.9.9.9',
|
||||
autonomous_system: {
|
||||
name: 'TM Net, Internet Service Provider',
|
||||
number: 4788,
|
||||
},
|
||||
domain: ['test.domain.net', 'test.old.domain.net'],
|
||||
flows: 12345,
|
||||
destination_ips: 12,
|
||||
ip: '9.9.9.9',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: ['Asia'],
|
||||
country_name: null,
|
||||
country_iso_code: ['MY'],
|
||||
city_name: ['Petaling Jaya'],
|
||||
region_iso_code: ['MY-10'],
|
||||
region_name: ['Selangor'],
|
||||
},
|
||||
flowTarget: FlowTarget.source,
|
||||
},
|
||||
},
|
||||
destination: null,
|
||||
network: {
|
||||
bytes: 325909849,
|
||||
packets: 221494,
|
||||
direction: [NetworkDirectionEcs.inbound, NetworkDirectionEcs.outbound],
|
||||
bytes_in: 3826633497,
|
||||
bytes_out: 1083495734,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TOP_TALKERS = i18n.translate('xpack.siem.networkTopNFlowTable.title', {
|
||||
defaultMessage: 'Top talkers',
|
||||
});
|
||||
|
||||
export const UNIT = (totalCount: number) =>
|
||||
i18n.translate('xpack.siem.networkTopNFlowTable.unit', {
|
||||
values: { totalCount },
|
||||
|
@ -17,95 +13,47 @@ export const UNIT = (totalCount: number) =>
|
|||
});
|
||||
|
||||
export const SOURCE_IP = i18n.translate('xpack.siem.networkTopNFlowTable.column.sourceIpTitle', {
|
||||
defaultMessage: 'Source IP',
|
||||
defaultMessage: 'Source IPs',
|
||||
});
|
||||
|
||||
export const DESTINATION_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.column.destinationIpTitle',
|
||||
{
|
||||
defaultMessage: 'Destination IP',
|
||||
defaultMessage: 'Destination IPs',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLIENT_IP = i18n.translate('xpack.siem.networkTopNFlowTable.column.clientIpTitle', {
|
||||
defaultMessage: 'Client IP',
|
||||
export const IP_TITLE = i18n.translate('xpack.siem.networkTopNFlowTable.column.IpTitle', {
|
||||
defaultMessage: 'IP',
|
||||
});
|
||||
|
||||
export const SERVER_IP = i18n.translate('xpack.siem.networkTopNFlowTable.column.serverIpTitle', {
|
||||
defaultMessage: 'Server IP',
|
||||
export const DOMAIN = i18n.translate('xpack.siem.networkTopNFlowTable.column.domainTitle', {
|
||||
defaultMessage: 'Domain',
|
||||
});
|
||||
|
||||
export const DOMAIN = i18n.translate('xpack.siem.networkTopNFlowTable.column.lastDomainTitle', {
|
||||
defaultMessage: 'Last domain',
|
||||
export const BYTES_IN = i18n.translate('xpack.siem.networkTopNFlowTable.column.bytesInTitle', {
|
||||
defaultMessage: 'Bytes in',
|
||||
});
|
||||
|
||||
export const BYTES = i18n.translate('xpack.siem.networkTopNFlowTable.column.bytesTitle', {
|
||||
defaultMessage: 'Bytes',
|
||||
export const BYTES_OUT = i18n.translate('xpack.siem.networkTopNFlowTable.column.bytesOutTitle', {
|
||||
defaultMessage: 'Bytes out',
|
||||
});
|
||||
|
||||
export const PACKETS = i18n.translate('xpack.siem.networkTopNFlowTable.column.packetsTitle', {
|
||||
defaultMessage: 'Packets',
|
||||
export const AUTONOMOUS_SYSTEM = i18n.translate('xpack.siem.networkTopNFlowTable.column.asTitle', {
|
||||
defaultMessage: 'Autonomous system',
|
||||
});
|
||||
|
||||
export const DIRECTION = i18n.translate('xpack.siem.networkTopNFlowTable.column.directionTitle', {
|
||||
defaultMessage: 'Direction',
|
||||
export const FLOWS = i18n.translate('xpack.siem.networkTopNFlowTable.flows', {
|
||||
defaultMessage: 'Flows',
|
||||
});
|
||||
|
||||
export const UNIQUE_SOURCE_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.column.uniqueSourceIpsTitle',
|
||||
{
|
||||
defaultMessage: 'Unique source IPs',
|
||||
}
|
||||
);
|
||||
export const DESTINATION_IPS = i18n.translate('xpack.siem.networkTopNFlowTable.destinationIps', {
|
||||
defaultMessage: 'Destination IPs',
|
||||
});
|
||||
|
||||
export const UNIQUE_DESTINATION_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.column.uniqueDestinationIpsTitle',
|
||||
{
|
||||
defaultMessage: 'Unique destination IPs',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNIQUE_CLIENT_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.column.uniqueClientIpsTitle',
|
||||
{
|
||||
defaultMessage: 'Unique client IPs',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNIQUE_SERVER_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.column.uniqueServerIpsTitle',
|
||||
{
|
||||
defaultMessage: 'Unique server IPs',
|
||||
}
|
||||
);
|
||||
|
||||
export const BY_SOURCE_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.select.bySourceIpDropDownOptionLabel',
|
||||
{
|
||||
defaultMessage: 'By source IP',
|
||||
}
|
||||
);
|
||||
|
||||
export const BY_DESTINATION_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.select.byDestinationIpDropDownOptionLabel',
|
||||
{
|
||||
defaultMessage: 'By destination IP',
|
||||
}
|
||||
);
|
||||
|
||||
export const BY_CLIENT_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.select.byClientIpDropDownOptionLabel',
|
||||
{
|
||||
defaultMessage: 'By client IP',
|
||||
}
|
||||
);
|
||||
|
||||
export const BY_SERVER_IP = i18n.translate(
|
||||
'xpack.siem.networkTopNFlowTable.select.byServerIpDropDownOptionLabel',
|
||||
{
|
||||
defaultMessage: 'By server IP',
|
||||
}
|
||||
);
|
||||
export const SOURCE_IPS = i18n.translate('xpack.siem.networkTopNFlowTable.sourceIps', {
|
||||
defaultMessage: 'Source IPs',
|
||||
});
|
||||
|
||||
export const ROWS_5 = i18n.translate('xpack.siem.networkTopNFlowTable.rows', {
|
||||
values: { numRows: 5 },
|
||||
|
|
|
@ -237,8 +237,9 @@ export const PaginatedTable = memo<SiemTables>(
|
|||
) : (
|
||||
<>
|
||||
<BasicTable
|
||||
items={pageOfItems}
|
||||
columns={columns}
|
||||
compressed
|
||||
items={pageOfItems}
|
||||
onChange={onChange}
|
||||
sorting={
|
||||
sorting
|
||||
|
@ -306,6 +307,10 @@ const BasicTable = styled(EuiBasicTable)`
|
|||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.euiTableCellContent {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import countries from 'i18n-iso-countries';
|
||||
import countryJson from 'i18n-iso-countries/langs/en.json';
|
||||
|
||||
/**
|
||||
* Returns the flag for the specified country code, or null if the specified
|
||||
|
@ -20,12 +23,27 @@ export const getFlag = (countryCode: string): string | null =>
|
|||
: null;
|
||||
|
||||
/** Renders an emjoi flag for the specified country code */
|
||||
export const CountryFlag = pure<{
|
||||
export const CountryFlag = memo<{
|
||||
countryCode: string;
|
||||
}>(({ countryCode }) => {
|
||||
displayCountryNameOnHover?: boolean;
|
||||
}>(({ countryCode, displayCountryNameOnHover = false }) => {
|
||||
useEffect(() => {
|
||||
if (displayCountryNameOnHover && isEmpty(countries.getNames('en'))) {
|
||||
countries.registerLocale(countryJson);
|
||||
}
|
||||
}, []);
|
||||
const flag = getFlag(countryCode);
|
||||
|
||||
return flag !== null ? <span data-test-subj="country-flag">{flag}</span> : null;
|
||||
if (flag !== null) {
|
||||
return displayCountryNameOnHover ? (
|
||||
<EuiToolTip position="top" content={countries.getName(countryCode, 'en')}>
|
||||
<span data-test-subj="country-flag">{flag}</span>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<span data-test-subj="country-flag">{flag}</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
CountryFlag.displayName = 'CountryFlag';
|
||||
|
|
|
@ -1,102 +1,92 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Stat Items Component disable charts it renders the default widget 1`] = `
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
<ThemeProvider
|
||||
theme={[Function]}
|
||||
>
|
||||
<StatItemsComponent
|
||||
description="HOSTS"
|
||||
fields={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3185FC",
|
||||
"icon": "cross",
|
||||
"key": "hosts",
|
||||
"value": null,
|
||||
},
|
||||
]
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
from={1560578400000}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
narrowDateRange={[MockFunction]}
|
||||
to={1560837600000}
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
<StatItemsComponent
|
||||
description="HOSTS"
|
||||
fields={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3185FC",
|
||||
"icon": "cross",
|
||||
"key": "hosts",
|
||||
"value": null,
|
||||
},
|
||||
]
|
||||
}
|
||||
from={1560578400000}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
narrowDateRange={[MockFunction]}
|
||||
to={1560837600000}
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
paddingSize="m"
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium"
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
paddingSize="m"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
>
|
||||
<h6
|
||||
className="euiTitle euiTitle--xxxsmall"
|
||||
>
|
||||
HOSTS
|
||||
</h6>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<Connect(InspectButtonComponent)
|
||||
inspectIndex={0}
|
||||
queryId="statItems"
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<InspectButtonComponent
|
||||
id=""
|
||||
inspect={null}
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
>
|
||||
<h6
|
||||
className="euiTitle euiTitle--xxxsmall"
|
||||
>
|
||||
HOSTS
|
||||
</h6>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<Connect(InspectButtonComponent)
|
||||
inspectIndex={0}
|
||||
isInspected={false}
|
||||
loading={false}
|
||||
queryId="statItems"
|
||||
refetch={null}
|
||||
selectedInspectIndex={0}
|
||||
setIsInspected={[Function]}
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<Component
|
||||
<InspectButtonComponent
|
||||
id=""
|
||||
inspect={null}
|
||||
inspectIndex={0}
|
||||
|
@ -109,238 +99,248 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
|
|||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<InspectContainer
|
||||
showInspect={false}
|
||||
<Component
|
||||
id=""
|
||||
inspect={null}
|
||||
inspectIndex={0}
|
||||
isInspected={false}
|
||||
loading={false}
|
||||
queryId="statItems"
|
||||
refetch={null}
|
||||
selectedInspectIndex={0}
|
||||
setIsInspected={[Function]}
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<div
|
||||
className="sc-EHOje dUUqHB"
|
||||
<InspectContainer
|
||||
showInspect={false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Inspect"
|
||||
className=""
|
||||
color="primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
<div
|
||||
className="sc-EHOje wlqEL"
|
||||
>
|
||||
<button
|
||||
<EuiButtonIcon
|
||||
aria-label="Inspect"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
className=""
|
||||
color="primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
size="m"
|
||||
type="inspect"
|
||||
<button
|
||||
aria-label="Inspect"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
style={null}
|
||||
className="euiButtonIcon__icon"
|
||||
size="m"
|
||||
type="inspect"
|
||||
>
|
||||
<svg
|
||||
<EuiIconEmpty
|
||||
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>
|
||||
</InspectContainer>
|
||||
</Component>
|
||||
</InspectButtonComponent>
|
||||
</Connect(InspectButtonComponent)>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<FlexItem
|
||||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-gzVnrw dJnFxB"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-gzVnrw dJnFxB"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
--
|
||||
|
||||
</p>
|
||||
</EuiTitle>
|
||||
</StatValue>
|
||||
>
|
||||
<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>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</InspectContainer>
|
||||
</Component>
|
||||
</InspectButtonComponent>
|
||||
</Connect(InspectButtonComponent)>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</StatItemsComponent>
|
||||
</Provider>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<FlexItem
|
||||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-gzVnrw dJnFxB"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-gzVnrw dJnFxB"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
<EmptyWrapper>
|
||||
<span
|
||||
className="sc-htpNat bijuWJ"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</EmptyWrapper>
|
||||
|
||||
</p>
|
||||
</EuiTitle>
|
||||
</StatValue>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</StatItemsComponent>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
`;
|
||||
|
||||
exports[`Stat Items Component disable charts it renders the default widget 2`] = `
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
<ThemeProvider
|
||||
theme={[Function]}
|
||||
>
|
||||
<StatItemsComponent
|
||||
areaChart={Array []}
|
||||
barChart={Array []}
|
||||
description="HOSTS"
|
||||
fields={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3185FC",
|
||||
"icon": "cross",
|
||||
"key": "hosts",
|
||||
"value": null,
|
||||
},
|
||||
]
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
from={1560578400000}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
narrowDateRange={[MockFunction]}
|
||||
to={1560837600000}
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
<StatItemsComponent
|
||||
areaChart={Array []}
|
||||
barChart={Array []}
|
||||
description="HOSTS"
|
||||
fields={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3185FC",
|
||||
"icon": "cross",
|
||||
"key": "hosts",
|
||||
"value": null,
|
||||
},
|
||||
]
|
||||
}
|
||||
from={1560578400000}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
narrowDateRange={[MockFunction]}
|
||||
to={1560837600000}
|
||||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
paddingSize="m"
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium"
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
paddingSize="m"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
>
|
||||
<h6
|
||||
className="euiTitle euiTitle--xxxsmall"
|
||||
>
|
||||
HOSTS
|
||||
</h6>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<Connect(InspectButtonComponent)
|
||||
inspectIndex={0}
|
||||
queryId="statItems"
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<InspectButtonComponent
|
||||
id=""
|
||||
inspect={null}
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
>
|
||||
<h6
|
||||
className="euiTitle euiTitle--xxxsmall"
|
||||
>
|
||||
HOSTS
|
||||
</h6>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<Connect(InspectButtonComponent)
|
||||
inspectIndex={0}
|
||||
isInspected={false}
|
||||
loading={false}
|
||||
queryId="statItems"
|
||||
refetch={null}
|
||||
selectedInspectIndex={0}
|
||||
setIsInspected={[Function]}
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<Component
|
||||
<InspectButtonComponent
|
||||
id=""
|
||||
inspect={null}
|
||||
inspectIndex={0}
|
||||
|
@ -353,138 +353,158 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
|
|||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<InspectContainer
|
||||
showInspect={false}
|
||||
<Component
|
||||
id=""
|
||||
inspect={null}
|
||||
inspectIndex={0}
|
||||
isInspected={false}
|
||||
loading={false}
|
||||
queryId="statItems"
|
||||
refetch={null}
|
||||
selectedInspectIndex={0}
|
||||
setIsInspected={[Function]}
|
||||
show={false}
|
||||
title="KPI HOSTS"
|
||||
>
|
||||
<div
|
||||
className="sc-EHOje dUUqHB"
|
||||
<InspectContainer
|
||||
showInspect={false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Inspect"
|
||||
className=""
|
||||
color="primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
<div
|
||||
className="sc-EHOje wlqEL"
|
||||
>
|
||||
<button
|
||||
<EuiButtonIcon
|
||||
aria-label="Inspect"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
className=""
|
||||
color="primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
size="m"
|
||||
type="inspect"
|
||||
<button
|
||||
aria-label="Inspect"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
data-test-subj="inspect-icon-button"
|
||||
onClick={[Function]}
|
||||
title="Inspect"
|
||||
type="button"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
style={null}
|
||||
className="euiButtonIcon__icon"
|
||||
size="m"
|
||||
type="inspect"
|
||||
>
|
||||
<svg
|
||||
<EuiIconEmpty
|
||||
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>
|
||||
</InspectContainer>
|
||||
</Component>
|
||||
</InspectButtonComponent>
|
||||
</Connect(InspectButtonComponent)>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<FlexItem
|
||||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
0
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-gzVnrw dJnFxB"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-gzVnrw dJnFxB"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
--
|
||||
|
||||
</p>
|
||||
</EuiTitle>
|
||||
</StatValue>
|
||||
>
|
||||
<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>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</InspectContainer>
|
||||
</Component>
|
||||
</InspectButtonComponent>
|
||||
</Connect(InspectButtonComponent)>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</StatItemsComponent>
|
||||
</Provider>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<FlexItem
|
||||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
0
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-bZQynM kpuYFd"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-bZQynM kpuYFd"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-gzVnrw dJnFxB"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-gzVnrw dJnFxB"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
<EmptyWrapper>
|
||||
<span
|
||||
className="sc-htpNat bijuWJ"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</EmptyWrapper>
|
||||
|
||||
</p>
|
||||
</EuiTitle>
|
||||
</StatValue>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</FlexItem>
|
||||
</StatItemsComponent>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
`;
|
||||
|
||||
exports[`Stat Items Component rendering kpis with charts it renders the default widget 1`] = `
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import {
|
||||
StatItemsComponent,
|
||||
|
@ -36,42 +38,47 @@ 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 theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
const state: State = mockGlobalState;
|
||||
const store = createStore(state, apolloClientObservable);
|
||||
|
||||
describe.each([
|
||||
[
|
||||
mount(
|
||||
<ReduxStoreProvider store={store}>
|
||||
<StatItemsComponent
|
||||
description="HOSTS"
|
||||
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
|
||||
from={from}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
to={to}
|
||||
narrowDateRange={mockNarrowDateRange}
|
||||
/>
|
||||
</ReduxStoreProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<StatItemsComponent
|
||||
description="HOSTS"
|
||||
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
|
||||
from={from}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
to={to}
|
||||
narrowDateRange={mockNarrowDateRange}
|
||||
/>
|
||||
</ReduxStoreProvider>
|
||||
</ThemeProvider>
|
||||
),
|
||||
],
|
||||
[
|
||||
mount(
|
||||
<ReduxStoreProvider store={store}>
|
||||
<StatItemsComponent
|
||||
areaChart={[]}
|
||||
barChart={[]}
|
||||
description="HOSTS"
|
||||
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
|
||||
from={from}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
to={to}
|
||||
narrowDateRange={mockNarrowDateRange}
|
||||
/>
|
||||
</ReduxStoreProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<StatItemsComponent
|
||||
areaChart={[]}
|
||||
barChart={[]}
|
||||
description="HOSTS"
|
||||
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
|
||||
from={from}
|
||||
id="statItems"
|
||||
index={0}
|
||||
key="mock-keys"
|
||||
to={to}
|
||||
narrowDateRange={mockNarrowDateRange}
|
||||
/>
|
||||
</ReduxStoreProvider>
|
||||
</ThemeProvider>
|
||||
),
|
||||
],
|
||||
])('disable charts', wrapper => {
|
||||
|
|
|
@ -97,32 +97,37 @@ exports[`Table Helpers #getRowItemDraggables it returns correctly against snapsh
|
|||
|
||||
exports[`Table Helpers #getRowItemOverflow it returns correctly against snapshot 1`] = `
|
||||
<div>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<React.Fragment>
|
||||
<span>
|
||||
<React.Fragment>
|
||||
item2
|
||||
</React.Fragment>
|
||||
<br />
|
||||
</span>
|
||||
<b>
|
||||
<br />
|
||||
<Popover
|
||||
count={2}
|
||||
idPrefix="attrName"
|
||||
>
|
||||
<EuiText
|
||||
size="xs"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
key="attrName-item2"
|
||||
>
|
||||
item2
|
||||
</li>
|
||||
</ul>
|
||||
<p
|
||||
data-test-subj="popover-additional-overflow"
|
||||
>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
>
|
||||
1
|
||||
|
||||
<FormattedMessage
|
||||
defaultMessage="More..."
|
||||
defaultMessage="more not shown"
|
||||
id="xpack.siem.tables.rowItemHelper.moreDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</b>
|
||||
</React.Fragment>
|
||||
}
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<MoreRowItems
|
||||
type="boxesHorizontal"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
</Popover>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
displayCount: 0,
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItem}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItem}</TestProviders>);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it returns empty string value when rowItem is empty', () => {
|
||||
|
@ -64,8 +64,9 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
displayCount: 0,
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItem}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItem}</TestProviders>);
|
||||
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it uses custom renderer', () => {
|
||||
|
@ -104,8 +105,8 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
displayCount: 0,
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it returns empty string value when rowItem is empty', () => {
|
||||
|
@ -130,8 +131,8 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
displayCount: 0,
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it returns no items when provided a 0 displayCount', () => {
|
||||
|
@ -141,8 +142,8 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
displayCount: 0,
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
test('it returns no items when provided an empty array', () => {
|
||||
|
@ -151,10 +152,12 @@ describe('Table Helpers', () => {
|
|||
attrName: 'attrName',
|
||||
idPrefix: 'idPrefix',
|
||||
});
|
||||
const wrapper = shallow(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.html()).toBe(getEmptyValue());
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.text()).toBe(getEmptyValue());
|
||||
});
|
||||
|
||||
// Using hostNodes due to this issue: https://github.com/airbnb/enzyme/issues/836
|
||||
|
||||
test('it returns 2 items then overflows', () => {
|
||||
const rowItems = getRowItemDraggables({
|
||||
rowItems: items,
|
||||
|
@ -163,7 +166,7 @@ describe('Table Helpers', () => {
|
|||
displayCount: 2,
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').length).toBe(2);
|
||||
expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').hostNodes().length).toBe(2);
|
||||
});
|
||||
|
||||
test('it uses custom renderer', () => {
|
||||
|
@ -191,20 +194,16 @@ describe('Table Helpers', () => {
|
|||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it does not show "More..." when maxOverflowItems are not exceeded', () => {
|
||||
test('it does not show "more not shown" when maxOverflowItems are not exceeded', () => {
|
||||
const rowItemOverflow = getRowItemOverflow(items, 'attrName', 1, 5);
|
||||
const wrapper = shallow(<div>{rowItemOverflow}</div>);
|
||||
expect(JSON.stringify(wrapper.find('EuiToolTip').prop('content'))).not.toContain(
|
||||
'defaultMessage'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="popover-additional-overflow"]').length).toBe(0);
|
||||
});
|
||||
|
||||
test('it shows "More..." when maxOverflowItems are exceeded', () => {
|
||||
test('it shows "more not shown" when maxOverflowItems are exceeded', () => {
|
||||
const rowItemOverflow = getRowItemOverflow(items, 'attrName', 1, 1);
|
||||
const wrapper = shallow(<div>{rowItemOverflow}</div>);
|
||||
expect(JSON.stringify(wrapper.find('EuiToolTip').prop('content'))).toContain(
|
||||
'defaultMessage'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="popover-additional-overflow"]').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { EuiLink, EuiPopover, EuiToolTip, EuiText, EuiTextColor } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { escapeDataProviderId } from '../drag_and_drop/helpers';
|
||||
import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper';
|
||||
|
@ -15,6 +16,10 @@ import { Provider } from '../timeline/data_providers/provider';
|
|||
import { defaultToEmptyTag, getEmptyTagValue } from '../empty_value';
|
||||
import { MoreRowItems, Spacer } from '../page';
|
||||
|
||||
const Subtext = styled.div`
|
||||
font-size: ${props => props.theme.eui.euiFontSizeXS};
|
||||
`;
|
||||
|
||||
export const getRowItemDraggable = ({
|
||||
rowItem,
|
||||
attrName,
|
||||
|
@ -144,36 +149,57 @@ export const getRowItemOverflow = (
|
|||
return (
|
||||
<>
|
||||
{rowItems.length > overflowIndexStart && (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<>
|
||||
<Popover count={rowItems.length - overflowIndexStart} idPrefix={idPrefix}>
|
||||
<EuiText size="xs">
|
||||
<ul>
|
||||
{rowItems
|
||||
.slice(overflowIndexStart, overflowIndexStart + maxOverflowItems)
|
||||
.map(rowItem => (
|
||||
<span key={`${idPrefix}-${rowItem}`}>
|
||||
{defaultToEmptyTag(rowItem)}
|
||||
<br />
|
||||
</span>
|
||||
<li key={`${idPrefix}-${rowItem}`}>{defaultToEmptyTag(rowItem)}</li>
|
||||
))}
|
||||
{rowItems.length > overflowIndexStart + maxOverflowItems && (
|
||||
<b>
|
||||
<br />
|
||||
</ul>
|
||||
|
||||
{rowItems.length > overflowIndexStart + maxOverflowItems && (
|
||||
<p data-test-subj="popover-additional-overflow">
|
||||
<EuiTextColor color="subdued">
|
||||
{rowItems.length - overflowIndexStart - maxOverflowItems}{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.siem.tables.rowItemHelper.moreDescription"
|
||||
defaultMessage="More..."
|
||||
defaultMessage="more not shown"
|
||||
/>
|
||||
</b>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MoreRowItems type="boxesHorizontal" />
|
||||
</EuiToolTip>
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
</Popover>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Popover = React.memo<{
|
||||
children: React.ReactNode;
|
||||
count: number;
|
||||
idPrefix: string;
|
||||
}>(({ children, count, idPrefix }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Subtext>
|
||||
<EuiPopover
|
||||
button={<EuiLink onClick={() => setIsOpen(!isOpen)}>{`+${count} More`}</EuiLink>}
|
||||
closePopover={() => setIsOpen(!isOpen)}
|
||||
id={`${idPrefix}-popover`}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
{children}
|
||||
</EuiPopover>
|
||||
</Subtext>
|
||||
);
|
||||
});
|
||||
|
||||
Popover.displayName = 'Popover';
|
||||
|
||||
export const OverflowField = React.memo<{
|
||||
value: string;
|
||||
showToolTip?: boolean;
|
||||
|
|
|
@ -12,7 +12,7 @@ exports[`empty_column_renderer renders correctly against snapshot 1`] = `
|
|||
"kqlQuery": "",
|
||||
"name": "source.ip: ",
|
||||
"queryMatch": Object {
|
||||
"displayValue": "--",
|
||||
"displayValue": "—",
|
||||
"field": "source.ip",
|
||||
"operator": ":*",
|
||||
"value": "",
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
exports[`unknown_column_renderer renders correctly against snapshot 1`] = `
|
||||
<span>
|
||||
--
|
||||
<EmptyWrapper>
|
||||
—
|
||||
</EmptyWrapper>
|
||||
</span>
|
||||
`;
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import { get } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { mockTimelineData, TestProviders } from '../../../../mock';
|
||||
import { getEmptyValue } from '../../../empty_value';
|
||||
|
@ -16,6 +18,8 @@ import { FormattedFieldValue } from './formatted_field';
|
|||
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('Events', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
test('renders correctly against snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<FormattedFieldValue
|
||||
|
@ -92,13 +96,15 @@ describe('Events', () => {
|
|||
|
||||
test('it renders placeholder text for a non-date field when the field is NOT populated', () => {
|
||||
const wrapper = mount(
|
||||
<FormattedFieldValue
|
||||
eventId={mockTimelineData[0].ecs._id}
|
||||
contextId="test"
|
||||
fieldName="fake.field"
|
||||
fieldType="text"
|
||||
value={get('fake.field', mockTimelineData[0].ecs)}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<FormattedFieldValue
|
||||
eventId={mockTimelineData[0].ecs._id}
|
||||
contextId="test"
|
||||
fieldName="fake.field"
|
||||
fieldType="text"
|
||||
value={get('fake.field', mockTimelineData[0].ecs)}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toEqual(getEmptyValue());
|
||||
|
|
|
@ -95,7 +95,7 @@ describe('plain_column_renderer', () => {
|
|||
<span>{column}</span>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.text()).toEqual('120.563KB');
|
||||
expect(wrapper.text()).toEqual('120.6KB');
|
||||
});
|
||||
|
||||
test('should return the value of event.action if event has a valid value', () => {
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { TimelineNonEcsData } from '../../../../graphql/types';
|
||||
import { defaultHeaders, mockTimelineData } from '../../../../mock';
|
||||
|
@ -16,6 +18,7 @@ import { unknownColumnRenderer } from './unknown_column_renderer';
|
|||
import { getValues } from './helpers';
|
||||
|
||||
describe('unknown_column_renderer', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
let mockDatum: TimelineNonEcsData[];
|
||||
const _id = mockTimelineData[0]._id;
|
||||
beforeEach(() => {
|
||||
|
@ -44,7 +47,11 @@ describe('unknown_column_renderer', () => {
|
|||
values: getValues('a made up column name', mockDatum),
|
||||
field: defaultHeaders.find(h => h.id === 'a made up column name')!,
|
||||
});
|
||||
const wrapper = mount(<span>{emptyColumn}</span>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<span>{emptyColumn}</span>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toEqual(getEmptyValue());
|
||||
});
|
||||
|
||||
|
@ -55,7 +62,11 @@ describe('unknown_column_renderer', () => {
|
|||
values: getValues('@timestamp', mockDatum),
|
||||
field: defaultHeaders.find(h => h.id === '@timestamp')!,
|
||||
});
|
||||
const wrapper = mount(<span>{emptyColumn}</span>);
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<span>{emptyColumn}</span>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(wrapper.text()).toEqual(getEmptyValue());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,6 +33,7 @@ interface State {
|
|||
}
|
||||
|
||||
const HoverActionsPanelContainer = styled.div`
|
||||
color: ${props => props.theme.eui.textColors.default}
|
||||
height: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
|
|
@ -9,11 +9,10 @@ import gql from 'graphql-tag';
|
|||
export const networkTopNFlowQuery = gql`
|
||||
query GetNetworkTopNFlowQuery(
|
||||
$sourceId: ID!
|
||||
$flowDirection: FlowDirection!
|
||||
$filterQuery: String
|
||||
$pagination: PaginationInputPaginated!
|
||||
$sort: NetworkTopNFlowSortField!
|
||||
$flowTarget: FlowTarget!
|
||||
$flowTarget: FlowTargetNew!
|
||||
$timerange: TimerangeInput!
|
||||
$defaultIndex: [String!]!
|
||||
$inspect: Boolean!
|
||||
|
@ -22,7 +21,6 @@ export const networkTopNFlowQuery = gql`
|
|||
id
|
||||
NetworkTopNFlow(
|
||||
filterQuery: $filterQuery
|
||||
flowDirection: $flowDirection
|
||||
flowTarget: $flowTarget
|
||||
pagination: $pagination
|
||||
sort: $sort
|
||||
|
@ -33,29 +31,50 @@ export const networkTopNFlowQuery = gql`
|
|||
edges {
|
||||
node {
|
||||
source {
|
||||
count
|
||||
ip
|
||||
autonomous_system {
|
||||
name
|
||||
number
|
||||
}
|
||||
domain
|
||||
ip
|
||||
location {
|
||||
geo {
|
||||
continent_name
|
||||
country_name
|
||||
country_iso_code
|
||||
city_name
|
||||
region_iso_code
|
||||
region_name
|
||||
}
|
||||
flowTarget
|
||||
}
|
||||
flows
|
||||
destination_ips
|
||||
}
|
||||
destination {
|
||||
count
|
||||
ip
|
||||
autonomous_system {
|
||||
name
|
||||
number
|
||||
}
|
||||
domain
|
||||
}
|
||||
client {
|
||||
count
|
||||
ip
|
||||
domain
|
||||
}
|
||||
server {
|
||||
count
|
||||
ip
|
||||
domain
|
||||
location {
|
||||
geo {
|
||||
continent_name
|
||||
country_name
|
||||
country_iso_code
|
||||
city_name
|
||||
region_iso_code
|
||||
region_name
|
||||
}
|
||||
flowTarget
|
||||
}
|
||||
flows
|
||||
source_ips
|
||||
}
|
||||
network {
|
||||
bytes
|
||||
direction
|
||||
packets
|
||||
bytes_in
|
||||
bytes_out
|
||||
}
|
||||
}
|
||||
cursor {
|
||||
|
|
|
@ -12,14 +12,13 @@ import { connect } from 'react-redux';
|
|||
import chrome from 'ui/chrome';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
|
||||
import {
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
FlowTargetNew,
|
||||
GetNetworkTopNFlowQuery,
|
||||
NetworkTopNFlowEdges,
|
||||
NetworkTopNFlowSortField,
|
||||
PageInfoPaginated,
|
||||
} from '../../graphql/types';
|
||||
import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store';
|
||||
import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store';
|
||||
import { generateTablePaginationOptions } from '../../components/paginated_table/helpers';
|
||||
import { createFilter } from '../helpers';
|
||||
import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated';
|
||||
|
@ -40,13 +39,12 @@ export interface NetworkTopNFlowArgs {
|
|||
|
||||
export interface OwnProps extends QueryTemplatePaginatedProps {
|
||||
children: (args: NetworkTopNFlowArgs) => React.ReactNode;
|
||||
flowTarget: FlowTargetNew;
|
||||
type: networkModel.NetworkType;
|
||||
}
|
||||
|
||||
export interface NetworkTopNFlowComponentReduxProps {
|
||||
activePage: number;
|
||||
flowDirection: FlowDirection;
|
||||
flowTarget: FlowTarget;
|
||||
isInspected: boolean;
|
||||
limit: number;
|
||||
topNFlowSort: NetworkTopNFlowSortField;
|
||||
|
@ -64,9 +62,8 @@ class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated<
|
|||
activePage,
|
||||
children,
|
||||
endDate,
|
||||
filterQuery,
|
||||
flowDirection,
|
||||
flowTarget,
|
||||
filterQuery,
|
||||
id = ID,
|
||||
isInspected,
|
||||
limit,
|
||||
|
@ -84,7 +81,6 @@ class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated<
|
|||
variables={{
|
||||
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
|
||||
filterQuery: createFilter(filterQuery),
|
||||
flowDirection,
|
||||
flowTarget,
|
||||
inspect: isInspected,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
|
@ -136,17 +132,14 @@ class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated<
|
|||
}
|
||||
}
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getNetworkTopNFlowSelector = networkSelectors.topNFlowSelector();
|
||||
const mapStateToProps = (state: State, { flowTarget, id = ID }: OwnProps) => {
|
||||
const getNetworkTopNFlowSelector = networkSelectors.topNFlowSelector(flowTarget);
|
||||
const getQuery = inputsSelectors.globalQueryByIdSelector();
|
||||
const mapStateToProps = (state: State, { id = ID }: OwnProps) => {
|
||||
const { isInspected } = getQuery(state, id);
|
||||
return {
|
||||
...getNetworkTopNFlowSelector(state),
|
||||
isInspected,
|
||||
};
|
||||
const { isInspected } = getQuery(state, id);
|
||||
return {
|
||||
...getNetworkTopNFlowSelector(state),
|
||||
isInspected,
|
||||
};
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export const NetworkTopNFlowQuery = connect(makeMapStateToProps)(NetworkTopNFlowComponentQuery);
|
||||
export const NetworkTopNFlowQuery = connect(mapStateToProps)(NetworkTopNFlowComponentQuery);
|
||||
|
|
|
@ -1685,23 +1685,13 @@
|
|||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "flowDirection",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "ENUM", "name": "FlowDirection", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "flowTarget",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "ENUM", "name": "FlowTarget", "ofType": null }
|
||||
"ofType": { "kind": "ENUM", "name": "FlowTargetNew", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
|
@ -7416,6 +7406,24 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "FlowTargetNew",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "destination",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "source", "description": "", "isDeprecated": false, "deprecationReason": null }
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "NetworkTopNFlowSortField",
|
||||
|
@ -7455,14 +7463,31 @@
|
|||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{ "name": "bytes", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "packets",
|
||||
"name": "bytes_in",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "ipCount", "description": "", "isDeprecated": false, "deprecationReason": null }
|
||||
{
|
||||
"name": "bytes_out",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "flows", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "destination_ips",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "source_ips",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
|
@ -7581,7 +7606,7 @@
|
|||
"name": "source",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItem", "ofType": null },
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItemSource", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -7589,23 +7614,7 @@
|
|||
"name": "destination",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "client",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "server",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItem", "ofType": null },
|
||||
"type": { "kind": "OBJECT", "name": "TopNFlowItemDestination", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -7625,14 +7634,14 @@
|
|||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TopNFlowItem",
|
||||
"name": "TopNFlowItemSource",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "count",
|
||||
"name": "autonomous_system",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -7659,6 +7668,151 @@
|
|||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "location",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "flows",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "destination_ips",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AutonomousSystemItem",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "number",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GeoItem",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "geo",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "flowTarget",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "ENUM", "name": "FlowTarget", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TopNFlowItemDestination",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "autonomous_system",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "domain",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "ip",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "location",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "flows",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "source_ips",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
@ -7672,7 +7826,7 @@
|
|||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "bytes",
|
||||
"name": "bytes_in",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
|
@ -7680,36 +7834,12 @@
|
|||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "packets",
|
||||
"name": "bytes_out",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "transport",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "direction",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "ENUM", "name": "NetworkDirectionEcs", "ofType": null }
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
|
|
@ -1166,33 +1166,57 @@ export interface NetworkTopNFlowEdges {
|
|||
export interface NetworkTopNFlowItem {
|
||||
_id?: string | null;
|
||||
|
||||
source?: TopNFlowItem | null;
|
||||
source?: TopNFlowItemSource | null;
|
||||
|
||||
destination?: TopNFlowItem | null;
|
||||
|
||||
client?: TopNFlowItem | null;
|
||||
|
||||
server?: TopNFlowItem | null;
|
||||
destination?: TopNFlowItemDestination | null;
|
||||
|
||||
network?: TopNFlowNetworkEcsField | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowItem {
|
||||
count?: number | null;
|
||||
export interface TopNFlowItemSource {
|
||||
autonomous_system?: AutonomousSystemItem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
location?: GeoItem | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
destination_ips?: number | null;
|
||||
}
|
||||
|
||||
export interface AutonomousSystemItem {
|
||||
name?: string | null;
|
||||
|
||||
number?: number | null;
|
||||
}
|
||||
|
||||
export interface GeoItem {
|
||||
geo?: GeoEcsFields | null;
|
||||
|
||||
flowTarget?: FlowTarget | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowItemDestination {
|
||||
autonomous_system?: AutonomousSystemItem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
location?: GeoItem | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
source_ips?: number | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowNetworkEcsField {
|
||||
bytes?: number | null;
|
||||
bytes_in?: number | null;
|
||||
|
||||
packets?: number | null;
|
||||
|
||||
transport?: string | null;
|
||||
|
||||
direction?: NetworkDirectionEcs[] | null;
|
||||
bytes_out?: number | null;
|
||||
}
|
||||
|
||||
export interface NetworkDnsData {
|
||||
|
@ -1955,9 +1979,7 @@ export interface NetworkTopNFlowSourceArgs {
|
|||
|
||||
filterQuery?: string | null;
|
||||
|
||||
flowDirection: FlowDirection;
|
||||
|
||||
flowTarget: FlowTarget;
|
||||
flowTarget: FlowTargetNew;
|
||||
|
||||
pagination: PaginationInputPaginated;
|
||||
|
||||
|
@ -2123,10 +2145,17 @@ export enum UsersFields {
|
|||
count = 'count',
|
||||
}
|
||||
|
||||
export enum FlowTargetNew {
|
||||
destination = 'destination',
|
||||
source = 'source',
|
||||
}
|
||||
|
||||
export enum NetworkTopNFlowFields {
|
||||
bytes = 'bytes',
|
||||
packets = 'packets',
|
||||
ipCount = 'ipCount',
|
||||
bytes_in = 'bytes_in',
|
||||
bytes_out = 'bytes_out',
|
||||
flows = 'flows',
|
||||
destination_ips = 'destination_ips',
|
||||
source_ips = 'source_ips',
|
||||
}
|
||||
|
||||
export enum NetworkDnsFields {
|
||||
|
@ -3277,11 +3306,10 @@ export namespace GetNetworkDnsQuery {
|
|||
export namespace GetNetworkTopNFlowQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
flowDirection: FlowDirection;
|
||||
filterQuery?: string | null;
|
||||
pagination: PaginationInputPaginated;
|
||||
sort: NetworkTopNFlowSortField;
|
||||
flowTarget: FlowTarget;
|
||||
flowTarget: FlowTargetNew;
|
||||
timerange: TimerangeInput;
|
||||
defaultIndex: string[];
|
||||
inspect: boolean;
|
||||
|
@ -3328,61 +3356,111 @@ export namespace GetNetworkTopNFlowQuery {
|
|||
|
||||
destination?: Destination | null;
|
||||
|
||||
client?: Client | null;
|
||||
|
||||
server?: Server | null;
|
||||
|
||||
network?: Network | null;
|
||||
};
|
||||
|
||||
export type _Source = {
|
||||
__typename?: 'TopNFlowItem';
|
||||
__typename?: 'TopNFlowItemSource';
|
||||
|
||||
count?: number | null;
|
||||
autonomous_system?: AutonomousSystem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
location?: Location | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
destination_ips?: number | null;
|
||||
};
|
||||
|
||||
export type AutonomousSystem = {
|
||||
__typename?: 'AutonomousSystemItem';
|
||||
|
||||
name?: string | null;
|
||||
|
||||
number?: number | null;
|
||||
};
|
||||
|
||||
export type Location = {
|
||||
__typename?: 'GeoItem';
|
||||
|
||||
geo?: Geo | null;
|
||||
|
||||
flowTarget?: FlowTarget | null;
|
||||
};
|
||||
|
||||
export type Geo = {
|
||||
__typename?: 'GeoEcsFields';
|
||||
|
||||
continent_name?: ToStringArray | null;
|
||||
|
||||
country_name?: ToStringArray | null;
|
||||
|
||||
country_iso_code?: ToStringArray | null;
|
||||
|
||||
city_name?: ToStringArray | null;
|
||||
|
||||
region_iso_code?: ToStringArray | null;
|
||||
|
||||
region_name?: ToStringArray | null;
|
||||
};
|
||||
|
||||
export type Destination = {
|
||||
__typename?: 'TopNFlowItem';
|
||||
__typename?: 'TopNFlowItemDestination';
|
||||
|
||||
count?: number | null;
|
||||
autonomous_system?: _AutonomousSystem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
location?: _Location | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
source_ips?: number | null;
|
||||
};
|
||||
|
||||
export type Client = {
|
||||
__typename?: 'TopNFlowItem';
|
||||
export type _AutonomousSystem = {
|
||||
__typename?: 'AutonomousSystemItem';
|
||||
|
||||
count?: number | null;
|
||||
name?: string | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
number?: number | null;
|
||||
};
|
||||
|
||||
export type Server = {
|
||||
__typename?: 'TopNFlowItem';
|
||||
export type _Location = {
|
||||
__typename?: 'GeoItem';
|
||||
|
||||
count?: number | null;
|
||||
geo?: _Geo | null;
|
||||
|
||||
ip?: string | null;
|
||||
flowTarget?: FlowTarget | null;
|
||||
};
|
||||
|
||||
domain?: string[] | null;
|
||||
export type _Geo = {
|
||||
__typename?: 'GeoEcsFields';
|
||||
|
||||
continent_name?: ToStringArray | null;
|
||||
|
||||
country_name?: ToStringArray | null;
|
||||
|
||||
country_iso_code?: ToStringArray | null;
|
||||
|
||||
city_name?: ToStringArray | null;
|
||||
|
||||
region_iso_code?: ToStringArray | null;
|
||||
|
||||
region_name?: ToStringArray | null;
|
||||
};
|
||||
|
||||
export type Network = {
|
||||
__typename?: 'TopNFlowNetworkEcsField';
|
||||
|
||||
bytes?: number | null;
|
||||
bytes_in?: number | null;
|
||||
|
||||
direction?: NetworkDirectionEcs[] | null;
|
||||
|
||||
packets?: number | null;
|
||||
bytes_out?: number | null;
|
||||
};
|
||||
|
||||
export type Cursor = {
|
||||
|
|
|
@ -65,12 +65,15 @@ export const mockGlobalState: State = {
|
|||
network: {
|
||||
page: {
|
||||
queries: {
|
||||
topNFlow: {
|
||||
topNFlowSource: {
|
||||
activePage: 0,
|
||||
limit: 10,
|
||||
flowTarget: FlowTarget.source,
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
topNFlowSort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc },
|
||||
},
|
||||
topNFlowDestination: {
|
||||
activePage: 0,
|
||||
limit: 10,
|
||||
topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc },
|
||||
},
|
||||
dns: {
|
||||
activePage: 0,
|
||||
|
|
|
@ -41,25 +41,25 @@ export const mockFrameworks: Readonly<Record<string, MockFrameworks>> = {
|
|||
timezone: 'America/Denver',
|
||||
},
|
||||
default_browser: {
|
||||
bytesFormat: '0,0.[000]b',
|
||||
bytesFormat: '0,0.[0]b',
|
||||
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
|
||||
dateFormatTz: 'Browser',
|
||||
timezone: 'America/Denver',
|
||||
},
|
||||
default_ET: {
|
||||
bytesFormat: '0,0.[000]b',
|
||||
bytesFormat: '0,0.[0]b',
|
||||
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
|
||||
dateFormatTz: 'America/New_York',
|
||||
timezone: 'America/New_York',
|
||||
},
|
||||
default_MT: {
|
||||
bytesFormat: '0,0.[000]b',
|
||||
bytesFormat: '0,0.[0]b',
|
||||
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
|
||||
dateFormatTz: 'America/Denver',
|
||||
timezone: 'America/Denver',
|
||||
},
|
||||
default_UTC: {
|
||||
bytesFormat: '0,0.[000]b',
|
||||
bytesFormat: '0,0.[0]b',
|
||||
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
|
||||
dateFormatTz: 'UTC',
|
||||
timezone: 'UTC',
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
|
@ -24,7 +24,7 @@ import { KpiNetworkQuery } from '../../containers/kpi_network';
|
|||
import { NetworkDnsQuery } from '../../containers/network_dns';
|
||||
import { NetworkTopNFlowQuery } from '../../containers/network_top_n_flow';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { LastEventIndexKey } from '../../graphql/types';
|
||||
import { FlowTargetNew, LastEventIndexKey } from '../../graphql/types';
|
||||
import { networkModel, networkSelectors, State } from '../../store';
|
||||
|
||||
import { NetworkKql } from './kql';
|
||||
|
@ -48,165 +48,238 @@ interface NetworkComponentReduxProps {
|
|||
}
|
||||
|
||||
type NetworkComponentProps = NetworkComponentReduxProps;
|
||||
const NetworkComponent = pure<NetworkComponentProps>(
|
||||
({ filterQuery, setAbsoluteRangeDatePicker }) => (
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.page} />
|
||||
</FiltersGlobal>
|
||||
const mediaMatch = window.matchMedia(
|
||||
'screen and (min-width: ' + euiLightVars.euiBreakpoints.xl + ')'
|
||||
);
|
||||
const getFlexDirectionByMediaMatch = (): 'row' | 'column' => {
|
||||
const { matches } = mediaMatch;
|
||||
return matches ? 'row' : 'column';
|
||||
};
|
||||
export const getFlexDirection = () => {
|
||||
const [display, setDisplay] = useState(getFlexDirectionByMediaMatch());
|
||||
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
useEffect(() => {
|
||||
const setFromEvent = () => setDisplay(getFlexDirectionByMediaMatch());
|
||||
window.addEventListener('resize', setFromEvent);
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
<UseUrlState indexPattern={indexPattern}>
|
||||
{({ isInitializing }) => (
|
||||
<>
|
||||
<KpiNetworkQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiNetwork, loading, id, inspect, refetch }) => (
|
||||
<KpiNetworkComponentManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
data={kpiNetwork}
|
||||
loading={loading}
|
||||
from={from}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setTimeout(() => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}, 500);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KpiNetworkQuery>
|
||||
return () => {
|
||||
window.removeEventListener('resize', setFromEvent);
|
||||
};
|
||||
}, []);
|
||||
|
||||
<EuiSpacer />
|
||||
return display;
|
||||
};
|
||||
|
||||
<NetworkTopNFlowQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
>
|
||||
{({
|
||||
totalCount,
|
||||
loading,
|
||||
networkTopNFlow,
|
||||
pageInfo,
|
||||
loadPage,
|
||||
id,
|
||||
inspect,
|
||||
refetch,
|
||||
}) => (
|
||||
<NetworkTopNFlowTableManage
|
||||
data={networkTopNFlow}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
pageInfo
|
||||
)}
|
||||
totalCount={totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
)}
|
||||
</NetworkTopNFlowQuery>
|
||||
const NetworkComponent = React.memo<NetworkComponentProps>(
|
||||
({ filterQuery, setAbsoluteRangeDatePicker }) => {
|
||||
return (
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.page} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<EuiSpacer />
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
|
||||
<NetworkDnsQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
>
|
||||
{({
|
||||
totalCount,
|
||||
loading,
|
||||
networkDns,
|
||||
pageInfo,
|
||||
loadPage,
|
||||
id,
|
||||
inspect,
|
||||
refetch,
|
||||
}) => (
|
||||
<NetworkDnsTableManage
|
||||
data={networkDns}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
pageInfo
|
||||
)}
|
||||
totalCount={totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
)}
|
||||
</NetworkDnsQuery>
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
<UseUrlState indexPattern={indexPattern}>
|
||||
{({ isInitializing }) => (
|
||||
<>
|
||||
<KpiNetworkQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiNetwork, loading, id, inspect, refetch }) => (
|
||||
<KpiNetworkComponentManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
data={kpiNetwork}
|
||||
loading={loading}
|
||||
from={from}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setTimeout(() => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}, 500);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KpiNetworkQuery>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<AnomaliesNetworkTable
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
type={networkModel.NetworkType.page}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
id: 'global',
|
||||
from: fromTo.from,
|
||||
to: fromTo.to,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</UseUrlState>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
<EuiFlexGroup direction={getFlexDirection()}>
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQuery
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetNew.source}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
>
|
||||
{({
|
||||
id,
|
||||
inspect,
|
||||
loading,
|
||||
loadPage,
|
||||
networkTopNFlow,
|
||||
pageInfo,
|
||||
refetch,
|
||||
totalCount,
|
||||
}) => (
|
||||
<NetworkTopNFlowTableManage
|
||||
data={networkTopNFlow}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
flowTargeted={FlowTargetNew.source}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
pageInfo
|
||||
)}
|
||||
totalCount={totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
)}
|
||||
</NetworkTopNFlowQuery>
|
||||
</EuiFlexItem>
|
||||
|
||||
<NetworkEmptyPage />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
)
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQuery
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetNew.destination}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
>
|
||||
{({
|
||||
id,
|
||||
inspect,
|
||||
loading,
|
||||
loadPage,
|
||||
networkTopNFlow,
|
||||
pageInfo,
|
||||
refetch,
|
||||
totalCount,
|
||||
}) => (
|
||||
<NetworkTopNFlowTableManage
|
||||
data={networkTopNFlow}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
flowTargeted={FlowTargetNew.destination}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
pageInfo
|
||||
)}
|
||||
totalCount={totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
)}
|
||||
</NetworkTopNFlowQuery>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<NetworkDnsQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
>
|
||||
{({
|
||||
totalCount,
|
||||
loading,
|
||||
networkDns,
|
||||
pageInfo,
|
||||
loadPage,
|
||||
id,
|
||||
inspect,
|
||||
refetch,
|
||||
}) => (
|
||||
<NetworkDnsTableManage
|
||||
data={networkDns}
|
||||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
showMorePagesIndicator={getOr(
|
||||
false,
|
||||
'showMorePagesIndicator',
|
||||
pageInfo
|
||||
)}
|
||||
totalCount={totalCount}
|
||||
type={networkModel.NetworkType.page}
|
||||
/>
|
||||
)}
|
||||
</NetworkDnsQuery>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<AnomaliesNetworkTable
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
type={networkModel.NetworkType.page}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
id: 'global',
|
||||
from: fromTo.from,
|
||||
to: fromTo.to,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</UseUrlState>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
|
||||
<NetworkEmptyPage />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
NetworkComponent.displayName = 'NetworkComponent';
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
TlsSortField,
|
||||
UsersSortField,
|
||||
} from '../../graphql/types';
|
||||
import { KueryFilterQuery, SerializedFilterQuery } from '../model';
|
||||
import { KueryFilterQuery, networkModel, SerializedFilterQuery } from '../model';
|
||||
|
||||
import { IpDetailsTableType, NetworkTableType, NetworkType } from './model';
|
||||
|
||||
|
@ -49,22 +49,15 @@ export const updateIsPtrIncluded = actionCreator<{
|
|||
export const updateTopNFlowLimit = actionCreator<{
|
||||
limit: number;
|
||||
networkType: NetworkType;
|
||||
tableType: networkModel.TopNTableType;
|
||||
}>('UPDATE_TOP_N_FLOW_LIMIT');
|
||||
|
||||
export const updateTopNFlowSort = actionCreator<{
|
||||
topNFlowSort: NetworkTopNFlowSortField;
|
||||
networkType: NetworkType;
|
||||
tableType: networkModel.NetworkTableType;
|
||||
}>('UPDATE_TOP_N_FLOW_SORT');
|
||||
|
||||
export const updateTopNFlowTarget = actionCreator<{
|
||||
flowTarget: FlowTarget;
|
||||
}>('UPDATE_TOP_N_FLOW_TARGET');
|
||||
|
||||
export const updateTopNFlowDirection = actionCreator<{
|
||||
flowDirection: FlowDirection;
|
||||
networkType: NetworkType;
|
||||
}>('UPDATE_TOP_N_FLOW_DIRECTION');
|
||||
|
||||
export const setNetworkFilterQueryDraft = actionCreator<{
|
||||
filterQueryDraft: KueryFilterQuery;
|
||||
networkType: NetworkType;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Direction,
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
NetworkTopNFlowFields,
|
||||
NetworkTopNFlowSortField,
|
||||
} from '../../graphql/types';
|
||||
|
||||
export const helperUpdateTopNFlowDirection = (
|
||||
flowTarget: FlowTarget,
|
||||
flowDirection: FlowDirection
|
||||
) => {
|
||||
const topNFlowSort: NetworkTopNFlowSortField = {
|
||||
field: NetworkTopNFlowFields.bytes,
|
||||
direction: Direction.desc,
|
||||
};
|
||||
if (
|
||||
flowDirection === FlowDirection.uniDirectional &&
|
||||
[FlowTarget.client, FlowTarget.server].includes(flowTarget)
|
||||
) {
|
||||
return { flowDirection, flowTarget: FlowTarget.source, topNFlowSort };
|
||||
}
|
||||
return { flowDirection, topNFlowSort };
|
||||
};
|
|
@ -13,7 +13,7 @@ import {
|
|||
TlsSortField,
|
||||
UsersSortField,
|
||||
} from '../../graphql/types';
|
||||
import { KueryFilterQuery, SerializedFilterQuery } from '../model';
|
||||
import { KueryFilterQuery, networkModel, SerializedFilterQuery } from '../model';
|
||||
|
||||
export enum NetworkType {
|
||||
page = 'page',
|
||||
|
@ -22,9 +22,14 @@ export enum NetworkType {
|
|||
|
||||
export enum NetworkTableType {
|
||||
dns = 'dns',
|
||||
topNFlow = 'topNFlow',
|
||||
topNFlowSource = 'topNFlowSource',
|
||||
topNFlowDestination = 'topNFlowDestination',
|
||||
}
|
||||
|
||||
export type TopNTableType =
|
||||
| networkModel.NetworkTableType.topNFlowDestination
|
||||
| networkModel.NetworkTableType.topNFlowSource;
|
||||
|
||||
export enum IpDetailsTableType {
|
||||
domains = 'domains',
|
||||
tls = 'tls',
|
||||
|
@ -38,9 +43,7 @@ export interface BasicQueryPaginated {
|
|||
|
||||
// Network Page Models
|
||||
export interface TopNFlowQuery extends BasicQueryPaginated {
|
||||
flowTarget: FlowTarget;
|
||||
topNFlowSort: NetworkTopNFlowSortField;
|
||||
flowDirection: FlowDirection;
|
||||
}
|
||||
|
||||
export interface DnsQuery extends BasicQueryPaginated {
|
||||
|
@ -50,7 +53,8 @@ export interface DnsQuery extends BasicQueryPaginated {
|
|||
|
||||
interface NetworkQueries {
|
||||
[NetworkTableType.dns]: DnsQuery;
|
||||
[NetworkTableType.topNFlow]: TopNFlowQuery;
|
||||
[NetworkTableType.topNFlowSource]: TopNFlowQuery;
|
||||
[NetworkTableType.topNFlowDestination]: TopNFlowQuery;
|
||||
}
|
||||
|
||||
export interface NetworkPageModel {
|
||||
|
|
|
@ -31,15 +31,12 @@ import {
|
|||
updateIsPtrIncluded,
|
||||
updateIpDetailsTableActivePage,
|
||||
updateNetworkPageTableActivePage,
|
||||
updateTopNFlowDirection,
|
||||
updateTopNFlowLimit,
|
||||
updateTopNFlowSort,
|
||||
updateTopNFlowTarget,
|
||||
updateTlsSort,
|
||||
updateUsersLimit,
|
||||
updateUsersSort,
|
||||
} from './actions';
|
||||
import { helperUpdateTopNFlowDirection } from './helper';
|
||||
import { IpDetailsTableType, NetworkModel, NetworkTableType, NetworkType } from './model';
|
||||
|
||||
export type NetworkState = NetworkModel;
|
||||
|
@ -47,15 +44,21 @@ export type NetworkState = NetworkModel;
|
|||
export const initialNetworkState: NetworkState = {
|
||||
page: {
|
||||
queries: {
|
||||
[NetworkTableType.topNFlow]: {
|
||||
[NetworkTableType.topNFlowSource]: {
|
||||
activePage: DEFAULT_TABLE_ACTIVE_PAGE,
|
||||
limit: DEFAULT_TABLE_LIMIT,
|
||||
topNFlowSort: {
|
||||
field: NetworkTopNFlowFields.bytes,
|
||||
field: NetworkTopNFlowFields.bytes_out,
|
||||
direction: Direction.desc,
|
||||
},
|
||||
},
|
||||
[NetworkTableType.topNFlowDestination]: {
|
||||
activePage: DEFAULT_TABLE_ACTIVE_PAGE,
|
||||
limit: DEFAULT_TABLE_LIMIT,
|
||||
topNFlowSort: {
|
||||
field: NetworkTopNFlowFields.bytes_out,
|
||||
direction: Direction.desc,
|
||||
},
|
||||
flowTarget: FlowTarget.source,
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
},
|
||||
[NetworkTableType.dns]: {
|
||||
activePage: DEFAULT_TABLE_ACTIVE_PAGE,
|
||||
|
@ -170,65 +173,32 @@ export const networkReducer = reducerWithInitialState(initialNetworkState)
|
|||
},
|
||||
},
|
||||
}))
|
||||
.case(updateTopNFlowLimit, (state, { limit, networkType }) => ({
|
||||
.case(updateTopNFlowLimit, (state, { limit, networkType, tableType }) => ({
|
||||
...state,
|
||||
[networkType]: {
|
||||
...state[networkType],
|
||||
queries: {
|
||||
...state[networkType].queries,
|
||||
[NetworkTableType.topNFlow]: {
|
||||
...state[NetworkType.page].queries.topNFlow,
|
||||
[tableType]: {
|
||||
...state[NetworkType.page].queries[tableType],
|
||||
limit,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
.case(updateTopNFlowDirection, (state, { flowDirection, networkType }) => ({
|
||||
.case(updateTopNFlowSort, (state, { topNFlowSort, networkType, tableType }) => ({
|
||||
...state,
|
||||
[networkType]: {
|
||||
...state[networkType],
|
||||
queries: {
|
||||
...state[networkType].queries,
|
||||
[NetworkTableType.topNFlow]: {
|
||||
...state[NetworkType.page].queries.topNFlow,
|
||||
...helperUpdateTopNFlowDirection(
|
||||
state[NetworkType.page].queries.topNFlow.flowTarget,
|
||||
flowDirection
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
.case(updateTopNFlowSort, (state, { topNFlowSort, networkType }) => ({
|
||||
...state,
|
||||
[networkType]: {
|
||||
...state[networkType],
|
||||
queries: {
|
||||
...state[networkType].queries,
|
||||
[NetworkTableType.topNFlow]: {
|
||||
...state[NetworkType.page].queries.topNFlow,
|
||||
[tableType]: {
|
||||
...state[NetworkType.page].queries[tableType],
|
||||
topNFlowSort,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
.case(updateTopNFlowTarget, (state, { flowTarget }) => ({
|
||||
...state,
|
||||
[NetworkType.page]: {
|
||||
...state[NetworkType.page],
|
||||
queries: {
|
||||
...state[NetworkType.page].queries,
|
||||
[NetworkTableType.topNFlow]: {
|
||||
...state[NetworkType.page].queries.topNFlow,
|
||||
flowTarget,
|
||||
topNFlowSort: {
|
||||
field: NetworkTopNFlowFields.bytes,
|
||||
direction: Direction.desc,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
.case(setNetworkFilterQueryDraft, (state, { filterQueryDraft, networkType }) => ({
|
||||
...state,
|
||||
[networkType]: {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { isFromKueryExpressionValid } from '../../lib/keury';
|
|||
import { State } from '../reducer';
|
||||
|
||||
import { NetworkDetailsModel, NetworkPageModel, NetworkType } from './model';
|
||||
import { FlowTargetNew } from '../../graphql/types';
|
||||
|
||||
const selectNetworkPage = (state: State): NetworkPageModel => state.network.page;
|
||||
|
||||
|
@ -25,11 +26,18 @@ export const dnsSelector = () =>
|
|||
selectNetworkPage,
|
||||
network => network.queries.dns
|
||||
);
|
||||
|
||||
export const topNFlowSelector = () =>
|
||||
export enum NetworkTableType {
|
||||
dns = 'dns',
|
||||
topNFlowSource = 'topNFlowSource',
|
||||
topNFlowDestination = 'topNFlowDestination',
|
||||
}
|
||||
export const topNFlowSelector = (flowTarget: FlowTargetNew) =>
|
||||
createSelector(
|
||||
selectNetworkPage,
|
||||
network => network.queries.topNFlow
|
||||
network =>
|
||||
flowTarget === FlowTargetNew.source
|
||||
? network.queries[NetworkTableType.topNFlowSource]
|
||||
: network.queries[NetworkTableType.topNFlowDestination]
|
||||
);
|
||||
|
||||
// Filter Query Selectors
|
||||
|
|
|
@ -38,7 +38,6 @@ export const createNetworkResolvers = (
|
|||
...createOptionsPaginated(source, args, info),
|
||||
flowTarget: args.flowTarget,
|
||||
networkTopNFlowSort: args.sort,
|
||||
flowDirection: args.flowDirection,
|
||||
};
|
||||
return libs.network.getNetworkTopNFlow(req, options);
|
||||
},
|
||||
|
|
|
@ -19,22 +19,44 @@ export const networkSchema = gql`
|
|||
}
|
||||
|
||||
type TopNFlowNetworkEcsField {
|
||||
bytes: Float
|
||||
packets: Float
|
||||
transport: String
|
||||
direction: [NetworkDirectionEcs!]
|
||||
bytes_in: Float
|
||||
bytes_out: Float
|
||||
}
|
||||
|
||||
type TopNFlowItem {
|
||||
count: Float
|
||||
type GeoItem {
|
||||
geo: GeoEcsFields
|
||||
flowTarget: FlowTarget
|
||||
}
|
||||
|
||||
type AutonomousSystemItem {
|
||||
name: String
|
||||
number: Float
|
||||
}
|
||||
|
||||
type TopNFlowItemSource {
|
||||
autonomous_system: AutonomousSystemItem
|
||||
domain: [String!]
|
||||
ip: String
|
||||
location: GeoItem
|
||||
flows: Float
|
||||
destination_ips: Float
|
||||
}
|
||||
|
||||
type TopNFlowItemDestination {
|
||||
autonomous_system: AutonomousSystemItem
|
||||
domain: [String!]
|
||||
ip: String
|
||||
location: GeoItem
|
||||
flows: Float
|
||||
source_ips: Float
|
||||
}
|
||||
|
||||
enum NetworkTopNFlowFields {
|
||||
bytes
|
||||
packets
|
||||
ipCount
|
||||
bytes_in
|
||||
bytes_out
|
||||
flows
|
||||
destination_ips
|
||||
source_ips
|
||||
}
|
||||
|
||||
input NetworkTopNFlowSortField {
|
||||
|
@ -44,10 +66,8 @@ export const networkSchema = gql`
|
|||
|
||||
type NetworkTopNFlowItem {
|
||||
_id: String
|
||||
source: TopNFlowItem
|
||||
destination: TopNFlowItem
|
||||
client: TopNFlowItem
|
||||
server: TopNFlowItem
|
||||
source: TopNFlowItemSource
|
||||
destination: TopNFlowItemDestination
|
||||
network: TopNFlowNetworkEcsField
|
||||
}
|
||||
|
||||
|
@ -102,8 +122,7 @@ export const networkSchema = gql`
|
|||
NetworkTopNFlow(
|
||||
id: String
|
||||
filterQuery: String
|
||||
flowDirection: FlowDirection!
|
||||
flowTarget: FlowTarget!
|
||||
flowTarget: FlowTargetNew!
|
||||
pagination: PaginationInputPaginated!
|
||||
sort: NetworkTopNFlowSortField!
|
||||
timerange: TimerangeInput!
|
||||
|
|
|
@ -1195,33 +1195,57 @@ export interface NetworkTopNFlowEdges {
|
|||
export interface NetworkTopNFlowItem {
|
||||
_id?: string | null;
|
||||
|
||||
source?: TopNFlowItem | null;
|
||||
source?: TopNFlowItemSource | null;
|
||||
|
||||
destination?: TopNFlowItem | null;
|
||||
|
||||
client?: TopNFlowItem | null;
|
||||
|
||||
server?: TopNFlowItem | null;
|
||||
destination?: TopNFlowItemDestination | null;
|
||||
|
||||
network?: TopNFlowNetworkEcsField | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowItem {
|
||||
count?: number | null;
|
||||
export interface TopNFlowItemSource {
|
||||
autonomous_system?: AutonomousSystemItem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
location?: GeoItem | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
destination_ips?: number | null;
|
||||
}
|
||||
|
||||
export interface AutonomousSystemItem {
|
||||
name?: string | null;
|
||||
|
||||
number?: number | null;
|
||||
}
|
||||
|
||||
export interface GeoItem {
|
||||
geo?: GeoEcsFields | null;
|
||||
|
||||
flowTarget?: FlowTarget | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowItemDestination {
|
||||
autonomous_system?: AutonomousSystemItem | null;
|
||||
|
||||
domain?: string[] | null;
|
||||
|
||||
ip?: string | null;
|
||||
|
||||
location?: GeoItem | null;
|
||||
|
||||
flows?: number | null;
|
||||
|
||||
source_ips?: number | null;
|
||||
}
|
||||
|
||||
export interface TopNFlowNetworkEcsField {
|
||||
bytes?: number | null;
|
||||
bytes_in?: number | null;
|
||||
|
||||
packets?: number | null;
|
||||
|
||||
transport?: string | null;
|
||||
|
||||
direction?: NetworkDirectionEcs[] | null;
|
||||
bytes_out?: number | null;
|
||||
}
|
||||
|
||||
export interface NetworkDnsData {
|
||||
|
@ -1984,9 +2008,7 @@ export interface NetworkTopNFlowSourceArgs {
|
|||
|
||||
filterQuery?: string | null;
|
||||
|
||||
flowDirection: FlowDirection;
|
||||
|
||||
flowTarget: FlowTarget;
|
||||
flowTarget: FlowTargetNew;
|
||||
|
||||
pagination: PaginationInputPaginated;
|
||||
|
||||
|
@ -2152,10 +2174,17 @@ export enum UsersFields {
|
|||
count = 'count',
|
||||
}
|
||||
|
||||
export enum FlowTargetNew {
|
||||
destination = 'destination',
|
||||
source = 'source',
|
||||
}
|
||||
|
||||
export enum NetworkTopNFlowFields {
|
||||
bytes = 'bytes',
|
||||
packets = 'packets',
|
||||
ipCount = 'ipCount',
|
||||
bytes_in = 'bytes_in',
|
||||
bytes_out = 'bytes_out',
|
||||
flows = 'flows',
|
||||
destination_ips = 'destination_ips',
|
||||
source_ips = 'source_ips',
|
||||
}
|
||||
|
||||
export enum NetworkDnsFields {
|
||||
|
@ -2815,9 +2844,7 @@ export namespace SourceResolvers {
|
|||
|
||||
filterQuery?: string | null;
|
||||
|
||||
flowDirection: FlowDirection;
|
||||
|
||||
flowTarget: FlowTarget;
|
||||
flowTarget: FlowTargetNew;
|
||||
|
||||
pagination: PaginationInputPaginated;
|
||||
|
||||
|
@ -6303,13 +6330,9 @@ export namespace NetworkTopNFlowItemResolvers {
|
|||
export interface Resolvers<Context = SiemContext, TypeParent = NetworkTopNFlowItem> {
|
||||
_id?: IdResolver<string | null, TypeParent, Context>;
|
||||
|
||||
source?: SourceResolver<TopNFlowItem | null, TypeParent, Context>;
|
||||
source?: SourceResolver<TopNFlowItemSource | null, TypeParent, Context>;
|
||||
|
||||
destination?: DestinationResolver<TopNFlowItem | null, TypeParent, Context>;
|
||||
|
||||
client?: ClientResolver<TopNFlowItem | null, TypeParent, Context>;
|
||||
|
||||
server?: ServerResolver<TopNFlowItem | null, TypeParent, Context>;
|
||||
destination?: DestinationResolver<TopNFlowItemDestination | null, TypeParent, Context>;
|
||||
|
||||
network?: NetworkResolver<TopNFlowNetworkEcsField | null, TypeParent, Context>;
|
||||
}
|
||||
|
@ -6320,22 +6343,12 @@ export namespace NetworkTopNFlowItemResolvers {
|
|||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type SourceResolver<
|
||||
R = TopNFlowItem | null,
|
||||
R = TopNFlowItemSource | null,
|
||||
Parent = NetworkTopNFlowItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type DestinationResolver<
|
||||
R = TopNFlowItem | null,
|
||||
Parent = NetworkTopNFlowItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type ClientResolver<
|
||||
R = TopNFlowItem | null,
|
||||
Parent = NetworkTopNFlowItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type ServerResolver<
|
||||
R = TopNFlowItem | null,
|
||||
R = TopNFlowItemDestination | null,
|
||||
Parent = NetworkTopNFlowItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
|
@ -6346,63 +6359,155 @@ export namespace NetworkTopNFlowItemResolvers {
|
|||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace TopNFlowItemResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = TopNFlowItem> {
|
||||
count?: CountResolver<number | null, TypeParent, Context>;
|
||||
export namespace TopNFlowItemSourceResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = TopNFlowItemSource> {
|
||||
autonomous_system?: AutonomousSystemResolver<AutonomousSystemItem | null, TypeParent, Context>;
|
||||
|
||||
domain?: DomainResolver<string[] | null, TypeParent, Context>;
|
||||
|
||||
ip?: IpResolver<string | null, TypeParent, Context>;
|
||||
|
||||
location?: LocationResolver<GeoItem | null, TypeParent, Context>;
|
||||
|
||||
flows?: FlowsResolver<number | null, TypeParent, Context>;
|
||||
|
||||
destination_ips?: DestinationIpsResolver<number | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type CountResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowItem,
|
||||
export type AutonomousSystemResolver<
|
||||
R = AutonomousSystemItem | null,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type DomainResolver<
|
||||
R = string[] | null,
|
||||
Parent = TopNFlowItem,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type IpResolver<
|
||||
R = string | null,
|
||||
Parent = TopNFlowItem,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type LocationResolver<
|
||||
R = GeoItem | null,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type FlowsResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type DestinationIpsResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowItemSource,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace AutonomousSystemItemResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = AutonomousSystemItem> {
|
||||
name?: NameResolver<string | null, TypeParent, Context>;
|
||||
|
||||
number?: NumberResolver<number | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type NameResolver<
|
||||
R = string | null,
|
||||
Parent = AutonomousSystemItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type NumberResolver<
|
||||
R = number | null,
|
||||
Parent = AutonomousSystemItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace GeoItemResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = GeoItem> {
|
||||
geo?: GeoResolver<GeoEcsFields | null, TypeParent, Context>;
|
||||
|
||||
flowTarget?: FlowTargetResolver<FlowTarget | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type GeoResolver<
|
||||
R = GeoEcsFields | null,
|
||||
Parent = GeoItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type FlowTargetResolver<
|
||||
R = FlowTarget | null,
|
||||
Parent = GeoItem,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace TopNFlowItemDestinationResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = TopNFlowItemDestination> {
|
||||
autonomous_system?: AutonomousSystemResolver<AutonomousSystemItem | null, TypeParent, Context>;
|
||||
|
||||
domain?: DomainResolver<string[] | null, TypeParent, Context>;
|
||||
|
||||
ip?: IpResolver<string | null, TypeParent, Context>;
|
||||
|
||||
location?: LocationResolver<GeoItem | null, TypeParent, Context>;
|
||||
|
||||
flows?: FlowsResolver<number | null, TypeParent, Context>;
|
||||
|
||||
source_ips?: SourceIpsResolver<number | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type AutonomousSystemResolver<
|
||||
R = AutonomousSystemItem | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type DomainResolver<
|
||||
R = string[] | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type IpResolver<
|
||||
R = string | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type LocationResolver<
|
||||
R = GeoItem | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type FlowsResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type SourceIpsResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowItemDestination,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace TopNFlowNetworkEcsFieldResolvers {
|
||||
export interface Resolvers<Context = SiemContext, TypeParent = TopNFlowNetworkEcsField> {
|
||||
bytes?: BytesResolver<number | null, TypeParent, Context>;
|
||||
bytes_in?: BytesInResolver<number | null, TypeParent, Context>;
|
||||
|
||||
packets?: PacketsResolver<number | null, TypeParent, Context>;
|
||||
|
||||
transport?: TransportResolver<string | null, TypeParent, Context>;
|
||||
|
||||
direction?: DirectionResolver<NetworkDirectionEcs[] | null, TypeParent, Context>;
|
||||
bytes_out?: BytesOutResolver<number | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type BytesResolver<
|
||||
export type BytesInResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowNetworkEcsField,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type PacketsResolver<
|
||||
export type BytesOutResolver<
|
||||
R = number | null,
|
||||
Parent = TopNFlowNetworkEcsField,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type TransportResolver<
|
||||
R = string | null,
|
||||
Parent = TopNFlowNetworkEcsField,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type DirectionResolver<
|
||||
R = NetworkDirectionEcs[] | null,
|
||||
Parent = TopNFlowNetworkEcsField,
|
||||
Context = SiemContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace NetworkDnsDataResolvers {
|
||||
|
|
|
@ -6,19 +6,21 @@
|
|||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
import { NetworkTopNFlowData } from '../../graphql/types';
|
||||
import { FlowTargetNew, NetworkTopNFlowData } from '../../graphql/types';
|
||||
import { FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
|
||||
import { ElasticsearchNetworkAdapter } from './elasticsearch_adapter';
|
||||
import { mockOptions, mockRequest, mockResponse, mockResult, mockTopNFlowQueryDsl } from './mock';
|
||||
|
||||
jest.mock('./query_top_n_flow.dsl', () => {
|
||||
const r = jest.requireActual('./query_top_n_flow.dsl');
|
||||
return {
|
||||
...r,
|
||||
buildTopNFlowQuery: jest.fn(() => mockTopNFlowQueryDsl),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Network Top N flow elasticsearch_adapter with FlowTarget=source and FlowDirection=uniDirectional', () => {
|
||||
describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () => {
|
||||
describe('Happy Path - get Data', () => {
|
||||
const mockCallWithRequest = jest.fn();
|
||||
mockCallWithRequest.mockResolvedValue(mockResponse);
|
||||
|
@ -47,7 +49,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source and Fl
|
|||
describe('Unhappy Path - No data', () => {
|
||||
const mockNoDataResponse = cloneDeep(mockResponse);
|
||||
mockNoDataResponse.aggregations.top_n_flow_count.value = 0;
|
||||
mockNoDataResponse.aggregations.top_uni_flow.buckets = [];
|
||||
mockNoDataResponse.aggregations[FlowTargetNew.source].buckets = [];
|
||||
const mockCallWithRequest = jest.fn();
|
||||
mockCallWithRequest.mockResolvedValue(mockNoDataResponse);
|
||||
const mockFramework: FrameworkAdapter = {
|
||||
|
@ -87,10 +89,9 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source and Fl
|
|||
describe('No pagination', () => {
|
||||
const mockNoPaginationResponse = cloneDeep(mockResponse);
|
||||
mockNoPaginationResponse.aggregations.top_n_flow_count.value = 10;
|
||||
mockNoPaginationResponse.aggregations.top_uni_flow.buckets = mockNoPaginationResponse.aggregations.top_uni_flow.buckets.slice(
|
||||
0,
|
||||
-1
|
||||
);
|
||||
mockNoPaginationResponse.aggregations[
|
||||
FlowTargetNew.source
|
||||
].buckets = mockNoPaginationResponse.aggregations[FlowTargetNew.source].buckets.slice(0, -1);
|
||||
const mockCallWithRequest = jest.fn();
|
||||
mockCallWithRequest.mockResolvedValue(mockNoPaginationResponse);
|
||||
const mockFramework: FrameworkAdapter = {
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
import { get, getOr } from 'lodash/fp';
|
||||
|
||||
import {
|
||||
FlowDirection,
|
||||
FlowTargetNew,
|
||||
AutonomousSystemItem,
|
||||
FlowTarget,
|
||||
GeoItem,
|
||||
NetworkDnsData,
|
||||
NetworkDnsEdges,
|
||||
NetworkTopNFlowData,
|
||||
|
@ -21,7 +23,7 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants';
|
|||
|
||||
import { NetworkDnsRequestOptions, NetworkTopNFlowRequestOptions } from './index';
|
||||
import { buildDnsQuery } from './query_dns.dsl';
|
||||
import { buildTopNFlowQuery } from './query_top_n_flow.dsl';
|
||||
import { buildTopNFlowQuery, getOppositeField } from './query_top_n_flow.dsl';
|
||||
import { NetworkAdapter, NetworkDnsBuckets, NetworkTopNFlowBuckets } from './types';
|
||||
|
||||
export class ElasticsearchNetworkAdapter implements NetworkAdapter {
|
||||
|
@ -31,6 +33,9 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter {
|
|||
request: FrameworkRequest,
|
||||
options: NetworkTopNFlowRequestOptions
|
||||
): Promise<NetworkTopNFlowData> {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
const dsl = buildTopNFlowQuery(options);
|
||||
const response = await this.framework.callWithRequest<NetworkTopNFlowData, TermAggregation>(
|
||||
request,
|
||||
|
@ -64,6 +69,9 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter {
|
|||
request: FrameworkRequest,
|
||||
options: NetworkDnsRequestOptions
|
||||
): Promise<NetworkDnsData> {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
const dsl = buildDnsQuery(options);
|
||||
const response = await this.framework.callWithRequest<NetworkDnsData, TermAggregation>(
|
||||
request,
|
||||
|
@ -99,37 +107,73 @@ const getTopNFlowEdges = (
|
|||
response: DatabaseSearchResponse<NetworkTopNFlowData, TermAggregation>,
|
||||
options: NetworkTopNFlowRequestOptions
|
||||
): NetworkTopNFlowEdges[] => {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
if (options.flowDirection === FlowDirection.uniDirectional) {
|
||||
return formatTopNFlowEdges(
|
||||
getOr([], 'aggregations.top_uni_flow.buckets', response),
|
||||
options.flowTarget
|
||||
);
|
||||
}
|
||||
return formatTopNFlowEdges(
|
||||
getOr([], 'aggregations.top_bi_flow.buckets', response),
|
||||
getOr([], `aggregations.${options.flowTarget}.buckets`, response),
|
||||
options.flowTarget
|
||||
);
|
||||
};
|
||||
|
||||
const getFlowTargetFromString = (flowAsString: string) =>
|
||||
flowAsString === 'source' ? FlowTarget.source : FlowTarget.destination;
|
||||
|
||||
const getGeoItem = (result: NetworkTopNFlowBuckets): GeoItem | null =>
|
||||
result.location.top_geo.hits.hits.length > 0
|
||||
? {
|
||||
geo: getOr(
|
||||
'',
|
||||
`location.top_geo.hits.hits[0]._source.${
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
}.geo`,
|
||||
result
|
||||
),
|
||||
flowTarget: getFlowTargetFromString(
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
),
|
||||
}
|
||||
: null;
|
||||
|
||||
const getAsItem = (result: NetworkTopNFlowBuckets): AutonomousSystemItem | null =>
|
||||
result.autonomous_system.top_as.hits.hits.length > 0
|
||||
? {
|
||||
number: getOr(
|
||||
null,
|
||||
`autonomous_system.top_as.hits.hits[0]._source.${
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
}.as.number`,
|
||||
result
|
||||
),
|
||||
name: getOr(
|
||||
'',
|
||||
`autonomous_system.top_as.hits.hits[0]._source.${
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
}.as.organization.name`,
|
||||
result
|
||||
),
|
||||
}
|
||||
: null;
|
||||
|
||||
const formatTopNFlowEdges = (
|
||||
buckets: NetworkTopNFlowBuckets[],
|
||||
flowTarget: FlowTarget
|
||||
flowTarget: FlowTargetNew
|
||||
): NetworkTopNFlowEdges[] =>
|
||||
buckets.map((bucket: NetworkTopNFlowBuckets) => ({
|
||||
node: {
|
||||
_id: bucket.key,
|
||||
[flowTarget]: {
|
||||
count: getOrNumber('ip_count.value', bucket),
|
||||
domain: bucket.domain.buckets.map(bucketDomain => bucketDomain.key),
|
||||
ip: bucket.key,
|
||||
location: getGeoItem(bucket),
|
||||
autonomous_system: getAsItem(bucket),
|
||||
flows: getOr(0, 'flows.value', bucket),
|
||||
[`${getOppositeField(flowTarget)}_ips`]: getOr(
|
||||
0,
|
||||
`${getOppositeField(flowTarget)}_ips.value`,
|
||||
bucket
|
||||
),
|
||||
},
|
||||
network: {
|
||||
bytes: getOrNumber('bytes.value', bucket),
|
||||
packets: getOrNumber('packets.value', bucket),
|
||||
direction: bucket.direction.buckets.map(bucketDir => bucketDir.key),
|
||||
bytes_in: getOr(0, 'bytes_in.value', bucket),
|
||||
bytes_out: getOr(0, 'bytes_out.value', bucket),
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
FlowTargetNew,
|
||||
NetworkDnsSortField,
|
||||
NetworkTopNFlowData,
|
||||
NetworkTopNFlowSortField,
|
||||
|
@ -19,8 +18,7 @@ export * from './types';
|
|||
|
||||
export interface NetworkTopNFlowRequestOptions extends RequestOptionsPaginated {
|
||||
networkTopNFlowSort: NetworkTopNFlowSortField;
|
||||
flowTarget: FlowTarget;
|
||||
flowDirection: FlowDirection;
|
||||
flowTarget: FlowTargetNew;
|
||||
}
|
||||
|
||||
export interface NetworkDnsRequestOptions extends RequestOptionsPaginated {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,66 +6,15 @@
|
|||
|
||||
import {
|
||||
Direction,
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
NetworkTopNFlowFields,
|
||||
FlowTargetNew,
|
||||
NetworkTopNFlowSortField,
|
||||
NetworkTopNFlowFields,
|
||||
} from '../../graphql/types';
|
||||
import { assertUnreachable, createQueryFilterClauses } from '../../utils/build_query';
|
||||
|
||||
import { NetworkTopNFlowRequestOptions } from './index';
|
||||
|
||||
const getUniDirectionalFilter = (flowDirection: FlowDirection) =>
|
||||
flowDirection === FlowDirection.uniDirectional
|
||||
? {
|
||||
must_not: [
|
||||
{
|
||||
exists: {
|
||||
field: 'destination.bytes',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
const getBiDirectionalFilter = (flowDirection: FlowDirection, flowTarget: FlowTarget) => {
|
||||
if (
|
||||
flowDirection === FlowDirection.biDirectional &&
|
||||
[FlowTarget.source, FlowTarget.destination].includes(flowTarget)
|
||||
) {
|
||||
return [
|
||||
{
|
||||
exists: {
|
||||
field: 'source.bytes',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'destination.bytes',
|
||||
},
|
||||
},
|
||||
];
|
||||
} else if (
|
||||
flowDirection === FlowDirection.biDirectional &&
|
||||
[FlowTarget.client, FlowTarget.server].includes(flowTarget)
|
||||
) {
|
||||
return [
|
||||
{
|
||||
exists: {
|
||||
field: 'client.bytes',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'server.bytes',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const getCountAgg = (flowTarget: FlowTarget) => ({
|
||||
const getCountAgg = (flowTarget: FlowTargetNew) => ({
|
||||
top_n_flow_count: {
|
||||
cardinality: {
|
||||
field: `${flowTarget}.ip`,
|
||||
|
@ -76,7 +25,6 @@ const getCountAgg = (flowTarget: FlowTarget) => ({
|
|||
export const buildTopNFlowQuery = ({
|
||||
defaultIndex,
|
||||
filterQuery,
|
||||
flowDirection,
|
||||
flowTarget,
|
||||
networkTopNFlowSort,
|
||||
pagination: { querySize },
|
||||
|
@ -88,7 +36,6 @@ export const buildTopNFlowQuery = ({
|
|||
const filter = [
|
||||
...createQueryFilterClauses(filterQuery),
|
||||
{ range: { [timestamp]: { gte: from, lte: to } } },
|
||||
...getBiDirectionalFilter(flowDirection, flowTarget),
|
||||
];
|
||||
|
||||
const dslQuery = {
|
||||
|
@ -98,13 +45,11 @@ export const buildTopNFlowQuery = ({
|
|||
body: {
|
||||
aggregations: {
|
||||
...getCountAgg(flowTarget),
|
||||
...getUniDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, querySize),
|
||||
...getBiDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, querySize),
|
||||
...getFlowTargetAggs(networkTopNFlowSort, flowTarget, querySize),
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter,
|
||||
...getUniDirectionalFilter(flowDirection),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -114,146 +59,118 @@ export const buildTopNFlowQuery = ({
|
|||
return dslQuery;
|
||||
};
|
||||
|
||||
const getUniDirectionAggs = (
|
||||
flowDirection: FlowDirection,
|
||||
const getFlowTargetAggs = (
|
||||
networkTopNFlowSortField: NetworkTopNFlowSortField,
|
||||
flowTarget: FlowTarget,
|
||||
flowTarget: FlowTargetNew,
|
||||
querySize: number
|
||||
) =>
|
||||
flowDirection === FlowDirection.uniDirectional
|
||||
? {
|
||||
top_uni_flow: {
|
||||
terms: {
|
||||
field: `${flowTarget}.ip`,
|
||||
size: querySize,
|
||||
order: {
|
||||
...getQueryOrder(networkTopNFlowSortField),
|
||||
},
|
||||
) => ({
|
||||
[flowTarget]: {
|
||||
terms: {
|
||||
field: `${flowTarget}.ip`,
|
||||
size: querySize,
|
||||
order: {
|
||||
...getQueryOrder(networkTopNFlowSortField),
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
bytes_in: {
|
||||
sum: {
|
||||
field: `${getOppositeField(flowTarget)}.bytes`,
|
||||
},
|
||||
},
|
||||
bytes_out: {
|
||||
sum: {
|
||||
field: `${flowTarget}.bytes`,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
terms: {
|
||||
field: `${flowTarget}.domain`,
|
||||
order: {
|
||||
timestamp: 'desc',
|
||||
},
|
||||
aggs: {
|
||||
bytes: {
|
||||
sum: {
|
||||
field: 'network.bytes',
|
||||
},
|
||||
},
|
||||
direction: {
|
||||
terms: {
|
||||
field: 'network.direction',
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
terms: {
|
||||
field: `${flowTarget}.domain`,
|
||||
order: {
|
||||
timestamp: 'desc',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
timestamp: {
|
||||
max: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ip_count: {
|
||||
cardinality: {
|
||||
field: `${
|
||||
flowTarget === FlowTarget.source ? FlowTarget.destination : FlowTarget.source
|
||||
}.ip`,
|
||||
},
|
||||
},
|
||||
packets: {
|
||||
sum: {
|
||||
field: 'network.packets',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
timestamp: {
|
||||
max: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
const getBiDirectionAggs = (
|
||||
flowDirection: FlowDirection,
|
||||
networkTopNFlowSortField: NetworkTopNFlowSortField,
|
||||
flowTarget: FlowTarget,
|
||||
querySize: number
|
||||
) =>
|
||||
flowDirection === FlowDirection.biDirectional
|
||||
? {
|
||||
top_bi_flow: {
|
||||
terms: {
|
||||
field: `${flowTarget}.ip`,
|
||||
size: querySize,
|
||||
order: {
|
||||
...getQueryOrder(networkTopNFlowSortField),
|
||||
},
|
||||
},
|
||||
location: {
|
||||
filter: {
|
||||
exists: {
|
||||
field: `${flowTarget}.geo`,
|
||||
},
|
||||
aggs: {
|
||||
bytes: {
|
||||
sum: {
|
||||
field: `${flowTarget}.bytes`,
|
||||
},
|
||||
},
|
||||
direction: {
|
||||
terms: {
|
||||
field: 'network.direction',
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
terms: {
|
||||
field: `${flowTarget}.domain`,
|
||||
order: {
|
||||
timestamp: 'desc',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
timestamp: {
|
||||
max: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ip_count: {
|
||||
cardinality: {
|
||||
field: `${getOppositeField(flowTarget)}.ip`,
|
||||
},
|
||||
},
|
||||
packets: {
|
||||
sum: {
|
||||
field: `${flowTarget}.packets`,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
top_geo: {
|
||||
top_hits: {
|
||||
_source: `${flowTarget}.geo.*`,
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
},
|
||||
autonomous_system: {
|
||||
filter: {
|
||||
exists: {
|
||||
field: `${flowTarget}.as`,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
top_as: {
|
||||
top_hits: {
|
||||
_source: `${flowTarget}.as.*`,
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
flows: {
|
||||
cardinality: {
|
||||
field: 'network.community_id',
|
||||
},
|
||||
},
|
||||
[`${getOppositeField(flowTarget)}_ips`]: {
|
||||
cardinality: {
|
||||
field: `${getOppositeField(flowTarget)}.ip`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const getOppositeField = (flowTarget: FlowTarget): FlowTarget => {
|
||||
export const getOppositeField = (flowTarget: FlowTargetNew): FlowTargetNew => {
|
||||
switch (flowTarget) {
|
||||
case FlowTarget.source:
|
||||
return FlowTarget.destination;
|
||||
case FlowTarget.destination:
|
||||
return FlowTarget.source;
|
||||
case FlowTarget.server:
|
||||
return FlowTarget.client;
|
||||
case FlowTarget.client:
|
||||
return FlowTarget.server;
|
||||
case FlowTargetNew.source:
|
||||
return FlowTargetNew.destination;
|
||||
case FlowTargetNew.destination:
|
||||
return FlowTargetNew.source;
|
||||
}
|
||||
assertUnreachable(flowTarget);
|
||||
};
|
||||
|
||||
type QueryOrder = { bytes: Direction } | { packets: Direction } | { ip_count: Direction };
|
||||
type QueryOrder =
|
||||
| { bytes_in: Direction }
|
||||
| { bytes_out: Direction }
|
||||
| { flows: Direction }
|
||||
| { destination_ips: Direction }
|
||||
| { source_ips: Direction };
|
||||
|
||||
const getQueryOrder = (networkTopNFlowSortField: NetworkTopNFlowSortField): QueryOrder => {
|
||||
switch (networkTopNFlowSortField.field) {
|
||||
case NetworkTopNFlowFields.bytes:
|
||||
return { bytes: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.packets:
|
||||
return { packets: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.ipCount:
|
||||
return { ip_count: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.bytes_in:
|
||||
return { bytes_in: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.bytes_out:
|
||||
return { bytes_out: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.flows:
|
||||
return { flows: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.destination_ips:
|
||||
return { destination_ips: networkTopNFlowSortField.direction };
|
||||
case NetworkTopNFlowFields.source_ips:
|
||||
return { source_ips: networkTopNFlowSortField.direction };
|
||||
}
|
||||
assertUnreachable(networkTopNFlowSortField.field);
|
||||
};
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { NetworkDirectionEcs, NetworkDnsData, NetworkTopNFlowData } from '../../graphql/types';
|
||||
import { NetworkDnsData, NetworkTopNFlowData } from '../../graphql/types';
|
||||
import { FrameworkRequest, RequestOptionsPaginated } from '../framework';
|
||||
import { SearchHit } from '../types';
|
||||
import { SearchHit, TotalValue } from '../types';
|
||||
|
||||
export interface NetworkAdapter {
|
||||
getNetworkTopNFlow(
|
||||
|
@ -21,27 +21,58 @@ export interface GenericBuckets {
|
|||
doc_count: number;
|
||||
}
|
||||
|
||||
export interface DirectionBuckets {
|
||||
key: NetworkDirectionEcs;
|
||||
interface LocationHit<T> {
|
||||
doc_count: number;
|
||||
top_geo: {
|
||||
hits: {
|
||||
total: TotalValue | number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: T;
|
||||
sort?: [number];
|
||||
_index?: string;
|
||||
_type?: string;
|
||||
_id?: string;
|
||||
_score?: number | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface AutonomousSystemHit<T> {
|
||||
doc_count: number;
|
||||
top_as: {
|
||||
hits: {
|
||||
total: TotalValue | number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: T;
|
||||
sort?: [number];
|
||||
_index?: string;
|
||||
_type?: string;
|
||||
_id?: string;
|
||||
_score?: number | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface NetworkTopNFlowBuckets {
|
||||
key: string;
|
||||
bytes: {
|
||||
autonomous_system: AutonomousSystemHit<object>;
|
||||
bytes_in: {
|
||||
value: number;
|
||||
};
|
||||
packets: {
|
||||
value: number;
|
||||
};
|
||||
ip_count: {
|
||||
bytes_out: {
|
||||
value: number;
|
||||
};
|
||||
domain: {
|
||||
buckets: GenericBuckets[];
|
||||
};
|
||||
direction: {
|
||||
buckets: DirectionBuckets[];
|
||||
};
|
||||
location: LocationHit<object>;
|
||||
flows: number;
|
||||
destination_ips?: number;
|
||||
source_ips?: number;
|
||||
}
|
||||
|
||||
export interface NetworkTopNFlowData extends SearchHit {
|
||||
|
@ -49,10 +80,10 @@ export interface NetworkTopNFlowData extends SearchHit {
|
|||
top_n_flow_count?: {
|
||||
value: number;
|
||||
};
|
||||
top_uni_flow?: {
|
||||
destination?: {
|
||||
buckets: NetworkTopNFlowBuckets[];
|
||||
};
|
||||
top_bi_flow?: {
|
||||
source?: {
|
||||
buckets: NetworkTopNFlowBuckets[];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -255,6 +255,7 @@
|
|||
"history": "4.9.0",
|
||||
"history-extra": "^5.0.1",
|
||||
"humps": "2.0.1",
|
||||
"i18n-iso-countries": "^4.3.1",
|
||||
"icalendar": "0.7.1",
|
||||
"idx": "^2.5.2",
|
||||
"immer": "^1.5.0",
|
||||
|
|
|
@ -9409,24 +9409,9 @@
|
|||
"xpack.siem.networkDnsTable.select.includePtrRecords": "PTR記録を含める",
|
||||
"xpack.siem.networkDnsTable.title": "トップDNSドメイン",
|
||||
"xpack.siem.networkDnsTable.unit": "{totalCount, plural, =1 {Domain} other {Domains}}",
|
||||
"xpack.siem.networkTopNFlowTable.column.bytesTitle": "バイト",
|
||||
"xpack.siem.networkTopNFlowTable.column.clientIpTitle": "クライアント IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.destinationIpTitle": "送信先 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.directionTitle": "方向",
|
||||
"xpack.siem.networkTopNFlowTable.column.lastDomainTitle": "最後のドメイン",
|
||||
"xpack.siem.networkTopNFlowTable.column.packetsTitle": "パケット",
|
||||
"xpack.siem.networkTopNFlowTable.column.serverIpTitle": "サーバーIP",
|
||||
"xpack.siem.networkTopNFlowTable.column.sourceIpTitle": "送信元IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueClientIpsTitle": "固有のクライアントIP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueDestinationIpsTitle": "固有の送信先IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueServerIpsTitle": "固有のサーバーIP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueSourceIpsTitle": "固有の送信元 IP",
|
||||
"xpack.siem.networkTopNFlowTable.rows": "{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}",
|
||||
"xpack.siem.networkTopNFlowTable.select.byClientIpDropDownOptionLabel": "クライアントIP",
|
||||
"xpack.siem.networkTopNFlowTable.select.byDestinationIpDropDownOptionLabel": "送信先 IP 別",
|
||||
"xpack.siem.networkTopNFlowTable.select.byServerIpDropDownOptionLabel": "サーバー IP 別",
|
||||
"xpack.siem.networkTopNFlowTable.select.bySourceIpDropDownOptionLabel": "送信元 IP 別",
|
||||
"xpack.siem.networkTopNFlowTable.title": "トップトーカー",
|
||||
"xpack.siem.networkTopNFlowTable.unit": "{totalCount, plural, =1 {IP} other {IPs}}",
|
||||
"xpack.siem.notes.addANotePlaceholder": "メモを追加",
|
||||
"xpack.siem.notes.addedANoteLabel": "メモを追加しました",
|
||||
|
|
|
@ -9551,24 +9551,9 @@
|
|||
"xpack.siem.networkDnsTable.select.includePtrRecords": "包括 PTR 记录",
|
||||
"xpack.siem.networkDnsTable.title": "排名靠前的 DNS 域",
|
||||
"xpack.siem.networkDnsTable.unit": "{totalCount, plural, =1 {Domain} other {Domains}}",
|
||||
"xpack.siem.networkTopNFlowTable.column.bytesTitle": "字节",
|
||||
"xpack.siem.networkTopNFlowTable.column.clientIpTitle": "客户端 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.destinationIpTitle": "目标 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.directionTitle": "方向",
|
||||
"xpack.siem.networkTopNFlowTable.column.lastDomainTitle": "最后域",
|
||||
"xpack.siem.networkTopNFlowTable.column.packetsTitle": "数据包",
|
||||
"xpack.siem.networkTopNFlowTable.column.serverIpTitle": "服务器 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.sourceIpTitle": "源 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueClientIpsTitle": "唯一客户端 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueDestinationIpsTitle": "唯一目标 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueServerIpsTitle": "唯一服务器 IP",
|
||||
"xpack.siem.networkTopNFlowTable.column.uniqueSourceIpsTitle": "唯一源 IP",
|
||||
"xpack.siem.networkTopNFlowTable.rows": "{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}",
|
||||
"xpack.siem.networkTopNFlowTable.select.byClientIpDropDownOptionLabel": "按客户端 IP",
|
||||
"xpack.siem.networkTopNFlowTable.select.byDestinationIpDropDownOptionLabel": "按目标 IP",
|
||||
"xpack.siem.networkTopNFlowTable.select.byServerIpDropDownOptionLabel": "按服务器 IP",
|
||||
"xpack.siem.networkTopNFlowTable.select.bySourceIpDropDownOptionLabel": "按源 IP",
|
||||
"xpack.siem.networkTopNFlowTable.title": "排名靠前的网络流量生成者",
|
||||
"xpack.siem.networkTopNFlowTable.unit": "{totalCount, plural, =1 {IP} other {IPs}}",
|
||||
"xpack.siem.notes.addANotePlaceholder": "添加备注",
|
||||
"xpack.siem.notes.addedANoteLabel": "已添加备注",
|
||||
|
|
|
@ -8,8 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { networkTopNFlowQuery } from '../../../../legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query';
|
||||
import {
|
||||
Direction,
|
||||
FlowDirection,
|
||||
FlowTarget,
|
||||
FlowTargetNew,
|
||||
GetNetworkTopNFlowQuery,
|
||||
NetworkTopNFlowFields,
|
||||
} from '../../../../legacy/plugins/siem/public/graphql/types';
|
||||
|
@ -28,7 +27,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
const FROM = new Date('2019-02-09T01:57:24.870Z').valueOf();
|
||||
const TO = new Date('2019-02-12T01:57:24.870Z').valueOf();
|
||||
|
||||
it('Make sure that we get unidirectional Source NetworkTopNFlow data with bytes descending sort', () => {
|
||||
it('Make sure that we get Source NetworkTopNFlow data with bytes_in descending sort', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
|
@ -39,9 +38,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
flowTarget: FlowTarget.source,
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
flowTarget: FlowTargetNew.source,
|
||||
sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc },
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
|
@ -57,14 +55,16 @@ export default function({ getService }: FtrProviderContext) {
|
|||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(121);
|
||||
expect(networkTopNFlow.edges.map(i => i.node.source!.ip).join(',')).to.be(
|
||||
'8.250.107.245,10.100.7.198,8.248.211.247,8.253.157.240,151.205.0.21,8.254.254.117,54.239.220.40,151.205.0.23,8.248.223.246,151.205.0.17'
|
||||
'10.100.7.196,10.100.7.199,10.100.7.197,10.100.7.198,3.82.33.170,17.249.172.100,10.100.4.1,8.248.209.244,8.248.211.247,8.248.213.244'
|
||||
);
|
||||
expect(networkTopNFlow.edges[0].node.destination).to.be(null);
|
||||
expect(networkTopNFlow.edges[0].node.source!.flows).to.be(498);
|
||||
expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(132);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50);
|
||||
});
|
||||
});
|
||||
|
||||
it('Make sure that we get unidirectional Source NetworkTopNFlow data with bytes ascending sort ', () => {
|
||||
it('Make sure that we get Source NetworkTopNFlow data with bytes_in ascending sort ', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
|
@ -75,9 +75,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
flowTarget: FlowTarget.source,
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.asc },
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
flowTarget: FlowTargetNew.source,
|
||||
sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.asc },
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
|
@ -93,14 +92,16 @@ export default function({ getService }: FtrProviderContext) {
|
|||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(121);
|
||||
expect(networkTopNFlow.edges.map(i => i.node.source!.ip).join(',')).to.be(
|
||||
'10.100.4.1,54.239.219.220,54.239.219.228,54.239.220.94,54.239.220.138,54.239.220.184,54.239.220.186,54.239.221.253,35.167.45.163,52.5.171.20'
|
||||
'8.248.209.244,8.248.211.247,8.248.213.244,8.248.223.246,8.250.107.245,8.250.121.236,8.250.125.244,8.253.38.231,8.253.157.112,8.253.157.240'
|
||||
);
|
||||
expect(networkTopNFlow.edges[0].node.destination).to.be(null);
|
||||
expect(networkTopNFlow.edges[0].node.source!.flows).to.be(12);
|
||||
expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(1);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50);
|
||||
});
|
||||
});
|
||||
|
||||
it('Make sure that we get bidirectional Source NetworkTopNFlow data', () => {
|
||||
it('Make sure that we get Destination NetworkTopNFlow data', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
|
@ -111,42 +112,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.source,
|
||||
flowDirection: FlowDirection.biDirectional,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
fakePossibleCount: 10,
|
||||
querySize: 10,
|
||||
},
|
||||
defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
inspect: false,
|
||||
},
|
||||
})
|
||||
.then(resp => {
|
||||
const networkTopNFlow = resp.data.source.NetworkTopNFlow;
|
||||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(10);
|
||||
expect(networkTopNFlow.edges[0].node.destination).to.be(null);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('Make sure that we get unidirectional Destination NetworkTopNFlow data', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.destination,
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc },
|
||||
flowTarget: FlowTargetNew.destination,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
|
@ -160,40 +127,9 @@ export default function({ getService }: FtrProviderContext) {
|
|||
.then(resp => {
|
||||
const networkTopNFlow = resp.data.source.NetworkTopNFlow;
|
||||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(144);
|
||||
expect(networkTopNFlow.edges[0].node.source).to.be(null);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50);
|
||||
});
|
||||
});
|
||||
|
||||
it('Make sure that we get bidirectional Destination NetworkTopNFlow data', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.destination,
|
||||
flowDirection: FlowDirection.biDirectional,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
fakePossibleCount: 50,
|
||||
querySize: 10,
|
||||
},
|
||||
defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
inspect: false,
|
||||
},
|
||||
})
|
||||
.then(resp => {
|
||||
const networkTopNFlow = resp.data.source.NetworkTopNFlow;
|
||||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(89);
|
||||
expect(networkTopNFlow.totalCount).to.be(154);
|
||||
expect(networkTopNFlow.edges[0].node.destination!.flows).to.be(19);
|
||||
expect(networkTopNFlow.edges[0].node.destination!.source_ips).to.be(1);
|
||||
expect(networkTopNFlow.edges[0].node.source).to.be(null);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50);
|
||||
});
|
||||
|
@ -210,9 +146,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.source,
|
||||
flowDirection: FlowDirection.uniDirectional,
|
||||
sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc },
|
||||
flowTarget: FlowTargetNew.source,
|
||||
pagination: {
|
||||
activePage: 1,
|
||||
cursorStart: 10,
|
||||
|
@ -228,81 +163,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(networkTopNFlow.totalCount).to.be(121);
|
||||
expect(networkTopNFlow.edges[0].node.source!.ip).to.be('151.205.0.19');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('With packetbeat', () => {
|
||||
before(() => esArchiver.load('packetbeat/default'));
|
||||
after(() => esArchiver.unload('packetbeat/default'));
|
||||
|
||||
const FROM = new Date('2019-02-19T23:22:09.675Z').valueOf();
|
||||
const TO = new Date('2019-02-19T23:26:50.001Z').valueOf();
|
||||
|
||||
it('Make sure that we get bidirectional Client NetworkTopNFlow data', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.client,
|
||||
flowDirection: FlowDirection.biDirectional,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
fakePossibleCount: 50,
|
||||
querySize: 10,
|
||||
},
|
||||
defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
inspect: false,
|
||||
},
|
||||
})
|
||||
.then(resp => {
|
||||
const networkTopNFlow = resp.data.source.NetworkTopNFlow;
|
||||
expect(networkTopNFlow.edges.length).to.be(1);
|
||||
expect(networkTopNFlow.totalCount).to.be(1);
|
||||
expect(networkTopNFlow.edges[0].node.server).to.be(null);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('Make sure that we get bidirectional Server NetworkTopNFlow data', () => {
|
||||
return client
|
||||
.query<GetNetworkTopNFlowQuery.Query>({
|
||||
query: networkTopNFlowQuery,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
to: TO,
|
||||
from: FROM,
|
||||
},
|
||||
sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc },
|
||||
flowTarget: FlowTarget.server,
|
||||
flowDirection: FlowDirection.biDirectional,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
cursorStart: 0,
|
||||
fakePossibleCount: 50,
|
||||
querySize: 10,
|
||||
},
|
||||
defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
inspect: false,
|
||||
},
|
||||
})
|
||||
.then(resp => {
|
||||
const networkTopNFlow = resp.data.source.NetworkTopNFlow;
|
||||
expect(networkTopNFlow.edges.length).to.be(1);
|
||||
expect(networkTopNFlow.totalCount).to.be(1);
|
||||
expect(networkTopNFlow.edges[0].node.client).to.be(null);
|
||||
expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(1);
|
||||
expect(networkTopNFlow.edges[0].node.source!.ip).to.be('8.248.223.246');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -10096,6 +10096,11 @@ di@^0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
|
||||
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
|
||||
|
||||
diacritics@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1"
|
||||
integrity sha1-PvqHMj67hj5mls67AILUj/PW96E=
|
||||
|
||||
diagnostics@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a"
|
||||
|
@ -14974,6 +14979,13 @@ hyperlinker@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
|
||||
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==
|
||||
|
||||
i18n-iso-countries@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-4.3.1.tgz#f110a8824ce14edbb0eb8f3b0bd817ff950af37c"
|
||||
integrity sha512-yxeCvmT8yO1p/epv93c1OHnnYNNMOX6NUNpNfuvzSIcDyripS7OGeKXgzYGd5QI31UK+GBrMG0nPFNv0jrHggw==
|
||||
dependencies:
|
||||
diacritics "^1.3.0"
|
||||
|
||||
icalendar@0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue