[SIEM] Update Scale types for charts (#38512) (#39202)

* change Scale types for charts

* update mock data

* add unit test for rendering charts with configs

* add timezone and formatter for charts

* update mock implementation for ui/chrome

* update snapshot
This commit is contained in:
Angela Chuang 2019-06-19 21:50:25 +08:00 committed by GitHub
parent da706ab1df
commit 7c1ed64c03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1229 additions and 958 deletions

View file

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AreaChartBaseComponent render with customized configs should 2 render AreaSeries 1`] = `[Function]`;
exports[`AreaChartBaseComponent render with default configs if no customized configs given should 2 render AreaSeries 1`] = `[Function]`;

View file

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BarChartBaseComponent render with customized configs should 2 render BarSeries 1`] = `[Function]`;
exports[`BarChartBaseComponent render with default configs if no customized configs given should 2 render BarSeries 1`] = `[Function]`;

View file

@ -4,30 +4,33 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, ReactWrapper } from 'enzyme';
import { ShallowWrapper, shallow } from 'enzyme';
import * as React from 'react';
import { AreaChartBaseComponent, AreaChartWithCustomPrompt } from './areachart';
import { ChartConfigsData } from './common';
import { ChartConfigsData, ChartHolder } from './common';
import { ScaleType, AreaSeries, Axis } from '@elastic/charts';
jest.mock('@elastic/charts');
describe('AreaChartBaseComponent', () => {
let wrapper: ReactWrapper;
let shallowWrapper: ShallowWrapper;
const mockAreaChartData: ChartConfigsData[] = [
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: 1556686800000, y: 580213 },
{ x: 1556730000000, y: 1096175 },
{ x: 1556773200000, y: 12382 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1096175 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -35,47 +38,165 @@ describe('AreaChartBaseComponent', () => {
describe('render', () => {
beforeAll(() => {
wrapper = mount(<AreaChartBaseComponent height={100} width={120} data={mockAreaChartData} />);
shallowWrapper = shallow(
<AreaChartBaseComponent height={100} width={120} data={mockAreaChartData} />
);
});
it('should render Chart', () => {
expect(wrapper.find('Chart')).toHaveLength(1);
expect(shallowWrapper.find('Chart')).toHaveLength(1);
});
});
describe('render with customized configs', () => {
const mockTimeFormatter = jest.fn();
const mockNumberFormatter = jest.fn();
const configs = {
series: {
xScaleType: ScaleType.Time,
yScaleType: ScaleType.Linear,
},
axis: {
xTickFormatter: mockTimeFormatter,
yTickFormatter: mockNumberFormatter,
},
};
beforeAll(() => {
shallowWrapper = shallow(
<AreaChartBaseComponent
height={100}
width={120}
data={mockAreaChartData}
configs={configs}
/>
);
});
it(`should ${mockAreaChartData.length} render AreaSeries`, () => {
expect(shallow).toMatchSnapshot();
expect(shallowWrapper.find(AreaSeries)).toHaveLength(mockAreaChartData.length);
});
it('should render AreaSeries with given xScaleType', () => {
expect(
shallowWrapper
.find(AreaSeries)
.first()
.prop('xScaleType')
).toEqual(configs.series.xScaleType);
});
it('should render AreaSeries with given yScaleType', () => {
expect(
shallowWrapper
.find(AreaSeries)
.first()
.prop('yScaleType')
).toEqual(configs.series.yScaleType);
});
it('should render xAxis with given tick formatter', () => {
expect(
shallowWrapper
.find(Axis)
.first()
.prop('tickFormat')
).toEqual(mockTimeFormatter);
});
it('should render yAxis with given tick formatter', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toEqual(mockNumberFormatter);
});
});
describe('render with default configs if no customized configs given', () => {
beforeAll(() => {
shallowWrapper = shallow(
<AreaChartBaseComponent height={100} width={120} data={mockAreaChartData} />
);
});
it(`should ${mockAreaChartData.length} render AreaSeries`, () => {
expect(shallow).toMatchSnapshot();
expect(shallowWrapper.find(AreaSeries)).toHaveLength(mockAreaChartData.length);
});
it('should render AreaSeries with default xScaleType: Linear', () => {
expect(
shallowWrapper
.find(AreaSeries)
.first()
.prop('xScaleType')
).toEqual(ScaleType.Linear);
});
it('should render AreaSeries with default yScaleType: Linear', () => {
expect(
shallowWrapper
.find(AreaSeries)
.first()
.prop('yScaleType')
).toEqual(ScaleType.Linear);
});
it('should not format xTicks value', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toBeUndefined();
});
it('should not format yTicks value', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toBeUndefined();
});
});
describe('no render', () => {
beforeAll(() => {
wrapper = mount(
shallowWrapper = shallow(
<AreaChartBaseComponent height={null} width={null} data={mockAreaChartData} />
);
});
it('should not render without height and width', () => {
expect(wrapper.find('Chart')).toHaveLength(0);
expect(shallowWrapper.find('Chart')).toHaveLength(0);
});
});
});
describe('AreaChartWithCustomPrompt', () => {
let wrapper: ReactWrapper;
let shallowWrapper: ShallowWrapper;
describe.each([
[
[
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: 1556686800000, y: 580213 },
{ x: 1556730000000, y: 1096175 },
{ x: 1556773200000, y: 12382 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1096175 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -90,9 +211,9 @@ describe('AreaChartWithCustomPrompt', () => {
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -101,12 +222,12 @@ describe('AreaChartWithCustomPrompt', () => {
],
])('renders areachart', (data: ChartConfigsData[] | [] | null | undefined) => {
beforeAll(() => {
wrapper = mount(<AreaChartWithCustomPrompt height={100} width={120} data={data} />);
shallowWrapper = shallow(<AreaChartWithCustomPrompt height={100} width={120} data={data} />);
});
it('render AreaChartBaseComponent', () => {
expect(wrapper.find('Chart')).toHaveLength(1);
expect(wrapper.find('ChartHolder')).toHaveLength(0);
expect(shallowWrapper.find(AreaChartBaseComponent)).toHaveLength(1);
expect(shallowWrapper.find(ChartHolder)).toHaveLength(0);
});
});
@ -128,12 +249,20 @@ describe('AreaChartWithCustomPrompt', () => {
[
{
key: 'uniqueSourceIpsHistogram',
value: [{ x: 1556686800000 }, { x: 1556730000000 }, { x: 1556773200000 }],
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf() },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf() },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf() },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [{ x: 1556686800000 }, { x: 1556730000000 }, { x: 1556773200000 }],
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf() },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf() },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf() },
],
color: '#490092',
},
],
@ -142,18 +271,18 @@ describe('AreaChartWithCustomPrompt', () => {
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: 1556686800000, y: 580213 },
{ x: 1556730000000, y: null },
{ x: 1556773200000, y: 12382 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: null },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -164,18 +293,18 @@ describe('AreaChartWithCustomPrompt', () => {
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: 1556686800000, y: 580213 },
{ x: 1556730000000, y: {} },
{ x: 1556773200000, y: 12382 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: {} },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -183,12 +312,12 @@ describe('AreaChartWithCustomPrompt', () => {
],
])('renders prompt', (data: ChartConfigsData[] | [] | null | undefined) => {
beforeAll(() => {
wrapper = mount(<AreaChartWithCustomPrompt height={100} width={120} data={data} />);
shallowWrapper = shallow(<AreaChartWithCustomPrompt height={100} width={120} data={data} />);
});
it('render Chart Holder', () => {
expect(wrapper.find('Chart')).toHaveLength(0);
expect(wrapper.find('ChartHolder')).toHaveLength(1);
expect(shallowWrapper.find(AreaChartBaseComponent)).toHaveLength(0);
expect(shallowWrapper.find(ChartHolder)).toHaveLength(1);
});
});
});

View file

@ -15,20 +15,18 @@ import {
ScaleType,
Settings,
} from '@elastic/charts';
import { getOr, get } from 'lodash/fp';
import {
ChartConfigsData,
ChartHolder,
getSeriesStyle,
numberFormatter,
WrappedByAutoSizer,
getTheme,
ChartSeriesConfigs,
browserTimezone,
} from './common';
import { AutoSizer } from '../auto_sizer';
const dateFormatter = (d: string) => {
return d.toLocaleString().split('T')[0];
};
// custom series styles: https://ela.st/areachart-styling
const getSeriesLineStyle = (color: string | undefined) => {
return color
@ -64,7 +62,13 @@ export const AreaChartBaseComponent = React.memo<{
data: ChartConfigsData[];
width: number | null | undefined;
height: number | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}>(({ data, ...chartConfigs }) => {
const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs);
const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs);
const xAxisId = getAxisId(`group-${data[0].key}-x`);
const yAxisId = getAxisId(`group-${data[0].key}-y`);
return chartConfigs.width && chartConfigs.height ? (
<div style={{ height: chartConfigs.height, width: chartConfigs.width, position: 'relative' }}>
<Chart>
@ -78,8 +82,9 @@ export const AreaChartBaseComponent = React.memo<{
key={seriesKey}
name={series.key.replace('Histogram', '')}
data={series.value}
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xScaleType={getOr(ScaleType.Linear, 'configs.series.xScaleType', chartConfigs)}
yScaleType={getOr(ScaleType.Linear, 'configs.series.yScaleType', chartConfigs)}
timeZone={browserTimezone}
xAccessor="x"
yAccessors={['y']}
areaSeriesStyle={getSeriesLineStyle(series.color)}
@ -88,19 +93,23 @@ export const AreaChartBaseComponent = React.memo<{
) : null;
})}
<Axis
id={getAxisId(`group-${data[0].key}-x`)}
position={Position.Bottom}
showOverlappingTicks={false}
tickFormat={dateFormatter}
tickSize={0}
/>
<Axis
id={getAxisId(`group-${data[0].key}-y`)}
position={Position.Left}
tickSize={0}
tickFormat={numberFormatter}
/>
{xTickFormatter ? (
<Axis
id={xAxisId}
position={Position.Bottom}
showOverlappingTicks={false}
tickFormat={xTickFormatter}
tickSize={0}
/>
) : (
<Axis id={xAxisId} position={Position.Bottom} showOverlappingTicks={false} tickSize={0} />
)}
{yTickFormatter ? (
<Axis id={yAxisId} position={Position.Left} tickSize={0} tickFormat={yTickFormatter} />
) : (
<Axis id={yAxisId} position={Position.Left} tickSize={0} />
)}
</Chart>
</div>
) : null;
@ -110,7 +119,8 @@ export const AreaChartWithCustomPrompt = React.memo<{
data: ChartConfigsData[] | null | undefined;
height: number | null | undefined;
width: number | null | undefined;
}>(({ data, height, width }) => {
configs?: ChartSeriesConfigs | undefined;
}>(({ data, height, width, configs }) => {
return data != null &&
data.length &&
data.every(
@ -119,20 +129,26 @@ export const AreaChartWithCustomPrompt = React.memo<{
value.length > 0 &&
value.every(chart => chart.x != null && chart.y != null)
) ? (
<AreaChartBaseComponent height={height} width={width} data={data} />
<AreaChartBaseComponent height={height} width={width} data={data} configs={configs} />
) : (
<ChartHolder />
);
});
export const AreaChart = React.memo<{ areaChart: ChartConfigsData[] | null | undefined }>(
({ areaChart }) => (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer innerRef={measureRef}>
<AreaChartWithCustomPrompt data={areaChart} height={height} width={width} />
</WrappedByAutoSizer>
)}
</AutoSizer>
)
);
export const AreaChart = React.memo<{
areaChart: ChartConfigsData[] | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}>(({ areaChart, configs }) => (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer innerRef={measureRef}>
<AreaChartWithCustomPrompt
data={areaChart}
height={height}
width={width}
configs={configs}
/>
</WrappedByAutoSizer>
)}
</AutoSizer>
));

View file

@ -4,14 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, ReactWrapper } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { BarChartBaseComponent, BarChartWithCustomPrompt } from './barchart';
import { ChartConfigsData } from './common';
import { ChartConfigsData, ChartHolder } from './common';
import { BarSeries, ScaleType, Axis } from '@elastic/charts';
jest.mock('@elastic/charts');
describe('BarChartBaseComponent', () => {
let wrapper: ReactWrapper;
let shallowWrapper: ShallowWrapper;
const mockBarChartData: ChartConfigsData[] = [
{
key: 'uniqueSourceIps',
@ -27,21 +30,134 @@ describe('BarChartBaseComponent', () => {
describe('render', () => {
beforeAll(() => {
wrapper = mount(<BarChartBaseComponent height={100} width={120} data={mockBarChartData} />);
shallowWrapper = shallow(
<BarChartBaseComponent height={100} width={120} data={mockBarChartData} />
);
});
it('should render two bar series', () => {
expect(wrapper.find('Chart')).toHaveLength(1);
expect(shallowWrapper.find('Chart')).toHaveLength(1);
});
});
describe('render with customized configs', () => {
const mockNumberFormatter = jest.fn();
const configs = {
series: {
xScaleType: ScaleType.Ordinal,
yScaleType: ScaleType.Linear,
},
axis: {
yTickFormatter: mockNumberFormatter,
},
};
beforeAll(() => {
shallowWrapper = shallow(
<BarChartBaseComponent height={100} width={120} data={mockBarChartData} configs={configs} />
);
});
it(`should ${mockBarChartData.length} render BarSeries`, () => {
expect(shallow).toMatchSnapshot();
expect(shallowWrapper.find(BarSeries)).toHaveLength(mockBarChartData.length);
});
it('should render BarSeries with given xScaleType', () => {
expect(
shallowWrapper
.find(BarSeries)
.first()
.prop('xScaleType')
).toEqual(configs.series.xScaleType);
});
it('should render BarSeries with given yScaleType', () => {
expect(
shallowWrapper
.find(BarSeries)
.first()
.prop('yScaleType')
).toEqual(configs.series.yScaleType);
});
it('should render xAxis with given tick formatter', () => {
expect(
shallowWrapper
.find(Axis)
.first()
.prop('tickFormat')
).toBeUndefined();
});
it('should render yAxis with given tick formatter', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toEqual(mockNumberFormatter);
});
});
describe('render with default configs if no customized configs given', () => {
beforeAll(() => {
shallowWrapper = shallow(
<BarChartBaseComponent height={100} width={120} data={mockBarChartData} />
);
});
it(`should ${mockBarChartData.length} render BarSeries`, () => {
expect(shallow).toMatchSnapshot();
expect(shallowWrapper.find(BarSeries)).toHaveLength(mockBarChartData.length);
});
it('should render BarSeries with default xScaleType: Linear', () => {
expect(
shallowWrapper
.find(BarSeries)
.first()
.prop('xScaleType')
).toEqual(ScaleType.Linear);
});
it('should render BarSeries with default yScaleType: Linear', () => {
expect(
shallowWrapper
.find(BarSeries)
.first()
.prop('yScaleType')
).toEqual(ScaleType.Linear);
});
it('should not format xTicks value', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toBeUndefined();
});
it('should not format yTicks value', () => {
expect(
shallowWrapper
.find(Axis)
.last()
.prop('tickFormat')
).toBeUndefined();
});
});
describe('no render', () => {
beforeAll(() => {
wrapper = mount(<BarChartBaseComponent height={null} width={null} data={mockBarChartData} />);
shallowWrapper = shallow(
<BarChartBaseComponent height={null} width={null} data={mockBarChartData} />
);
});
it('should not render without height and width', () => {
expect(wrapper.find('Chart')).toHaveLength(0);
expect(shallowWrapper.find('Chart')).toHaveLength(0);
});
});
});
@ -78,17 +194,17 @@ describe.each([
],
],
])('BarChartWithCustomPrompt', mockBarChartData => {
let wrapper: ReactWrapper;
let shallowWrapper: ShallowWrapper;
describe('renders barchart', () => {
beforeAll(() => {
wrapper = mount(
shallowWrapper = shallow(
<BarChartWithCustomPrompt height={100} width={120} data={mockBarChartData} />
);
});
it('render BarChartBaseComponent', () => {
expect(wrapper.find('Chart')).toHaveLength(1);
expect(wrapper.find('ChartHolder')).toHaveLength(0);
expect(shallowWrapper.find(BarChartBaseComponent)).toHaveLength(1);
expect(shallowWrapper.find(ChartHolder)).toHaveLength(0);
});
});
});
@ -156,13 +272,13 @@ describe.each([
],
],
])('renders prompt', (data: ChartConfigsData[] | [] | null | undefined) => {
let wrapper: ReactWrapper;
let shallowWrapper: ShallowWrapper;
beforeAll(() => {
wrapper = mount(<BarChartWithCustomPrompt height={100} width={120} data={data} />);
shallowWrapper = shallow(<BarChartWithCustomPrompt height={100} width={120} data={data} />);
});
it('render Chart Holder', () => {
expect(wrapper.find('Chart')).toHaveLength(0);
expect(wrapper.find('ChartHolder')).toHaveLength(1);
expect(shallowWrapper.find(BarChartBaseComponent)).toHaveLength(0);
expect(shallowWrapper.find(ChartHolder)).toHaveLength(1);
});
});

View file

@ -8,14 +8,16 @@ import React from 'react';
import { Chart, BarSeries, Axis, Position, getSpecId, ScaleType, Settings } from '@elastic/charts';
import { getAxisId } from '@elastic/charts';
import { getOr, get } from 'lodash/fp';
import {
ChartConfigsData,
WrappedByAutoSizer,
ChartHolder,
numberFormatter,
SeriesType,
getSeriesStyle,
getTheme,
ChartSeriesConfigs,
browserTimezone,
} from './common';
import { AutoSizer } from '../auto_sizer';
@ -24,7 +26,13 @@ export const BarChartBaseComponent = React.memo<{
data: ChartConfigsData[];
width: number | null | undefined;
height: number | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}>(({ data, ...chartConfigs }) => {
const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs);
const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs);
const xAxisId = getAxisId(`stat-items-barchart-${data[0].key}-x`);
const yAxisId = getAxisId(`stat-items-barchart-${data[0].key}-y`);
return chartConfigs.width && chartConfigs.height ? (
<Chart>
<Settings rotation={90} theme={getTheme()} />
@ -37,10 +45,11 @@ export const BarChartBaseComponent = React.memo<{
id={barSeriesSpecId}
key={barSeriesKey}
name={series.key}
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xScaleType={getOr(ScaleType.Linear, 'configs.series.xScaleType', chartConfigs)}
yScaleType={getOr(ScaleType.Linear, 'configs.series.yScaleType', chartConfigs)}
xAccessor="x"
yAccessors={['y']}
timeZone={browserTimezone}
splitSeriesAccessors={['g']}
data={series.value!}
stackAccessors={['y']}
@ -49,17 +58,23 @@ export const BarChartBaseComponent = React.memo<{
);
})}
<Axis
id={getAxisId(`stat-items-barchart-${data[0].key}-x`)}
position={Position.Bottom}
tickSize={0}
tickFormat={numberFormatter}
/>
<Axis
id={getAxisId(`stat-items-barchart-${data[0].key}-y`)}
position={Position.Left}
tickSize={0}
/>
{xTickFormatter ? (
<Axis
id={xAxisId}
position={Position.Bottom}
showOverlappingTicks={false}
tickSize={0}
tickFormat={xTickFormatter}
/>
) : (
<Axis id={xAxisId} position={Position.Bottom} showOverlappingTicks={false} tickSize={0} />
)}
{yTickFormatter ? (
<Axis id={yAxisId} position={Position.Left} tickSize={0} tickFormat={yTickFormatter} />
) : (
<Axis id={yAxisId} position={Position.Left} tickSize={0} />
)}
</Chart>
) : null;
});
@ -68,27 +83,29 @@ export const BarChartWithCustomPrompt = React.memo<{
data: ChartConfigsData[] | null | undefined;
height: number | null | undefined;
width: number | null | undefined;
}>(({ data, height, width }) => {
configs?: ChartSeriesConfigs | undefined;
}>(({ data, height, width, configs }) => {
return data &&
data.length &&
data.some(
({ value }) =>
value != null && value.length > 0 && value.every(chart => chart.y != null && chart.y > 0)
) ? (
<BarChartBaseComponent height={height} width={width} data={data} />
<BarChartBaseComponent height={height} width={width} data={data} configs={configs} />
) : (
<ChartHolder />
);
});
export const BarChart = React.memo<{ barChart: ChartConfigsData[] | null | undefined }>(
({ barChart }) => (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer innerRef={measureRef}>
<BarChartWithCustomPrompt height={height} width={width} data={barChart} />
</WrappedByAutoSizer>
)}
</AutoSizer>
)
);
export const BarChart = React.memo<{
barChart: ChartConfigsData[] | null | undefined;
configs?: ChartSeriesConfigs | undefined;
}>(({ barChart, configs }) => (
<AutoSizer detectAnyWindowResize={false} content>
{({ measureRef, content: { height, width } }) => (
<WrappedByAutoSizer innerRef={measureRef}>
<BarChartWithCustomPrompt height={height} width={width} data={barChart} configs={configs} />
</WrappedByAutoSizer>
)}
</AutoSizer>
));

View file

@ -14,9 +14,12 @@ import {
PartialTheme,
LIGHT_THEME,
DARK_THEME,
ScaleType,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { TickFormatter } from '@elastic/charts/dist/lib/series/specs';
import chrome from 'ui/chrome';
import moment from 'moment-timezone';
const chartHeight = 74;
const FlexGroup = styled(EuiFlexGroup)`
@ -42,10 +45,23 @@ export interface ChartData {
g?: number | string;
}
export interface ChartSeriesConfigs {
series?: {
xScaleType?: ScaleType | undefined;
yScaleType?: ScaleType | undefined;
};
axis?: {
xTickFormatter?: TickFormatter | undefined;
yTickFormatter?: TickFormatter | undefined;
};
}
export interface ChartConfigsData {
key: string;
value: ChartData[] | [] | null;
color?: string | undefined;
areachartConfigs?: ChartSeriesConfigs | undefined;
barchartConfigs?: ChartSeriesConfigs | undefined;
}
export const WrappedByAutoSizer = styled.div`
@ -57,10 +73,6 @@ export const WrappedByAutoSizer = styled.div`
}
`;
export const numberFormatter = (value: string | number) => {
return value.toLocaleString && value.toLocaleString();
};
export enum SeriesType {
BAR = 'bar',
AREA = 'area',
@ -109,3 +121,6 @@ export const getTheme = () => {
const defaultTheme = isDarkMode ? DARK_THEME : LIGHT_THEME;
return mergeWithDefaultTheme(theme, defaultTheme);
};
const kibanaTimezone = chrome.getUiSettingsClient().get('dateFormat:tz');
export const browserTimezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone;

View file

@ -10,11 +10,11 @@ exports[`KpiNetwork Component rendering it renders loading icons 1`] = `
"uniqueDestinationPrivateIps": 18,
"uniqueDestinationPrivateIpsHistogram": Array [
Object {
"x": "2019-02-09T16:00:00.000Z",
"x": 1549728000000,
"y": 8,
},
Object {
"x": "2019-02-09T19:00:00.000Z",
"x": 1549738800000,
"y": 0,
},
],
@ -22,11 +22,11 @@ exports[`KpiNetwork Component rendering it renders loading icons 1`] = `
"uniqueSourcePrivateIps": 383,
"uniqueSourcePrivateIpsHistogram": Array [
Object {
"x": "2019-02-09T16:00:00.000Z",
"x": 1549728000000,
"y": 8,
},
Object {
"x": "2019-02-09T19:00:00.000Z",
"x": 1549738800000,
"y": 0,
},
],
@ -46,11 +46,11 @@ exports[`KpiNetwork Component rendering it renders the default widget 1`] = `
"uniqueDestinationPrivateIps": 18,
"uniqueDestinationPrivateIpsHistogram": Array [
Object {
"x": "2019-02-09T16:00:00.000Z",
"x": 1549728000000,
"y": 8,
},
Object {
"x": "2019-02-09T19:00:00.000Z",
"x": 1549738800000,
"y": 0,
},
],
@ -58,11 +58,11 @@ exports[`KpiNetwork Component rendering it renders the default widget 1`] = `
"uniqueSourcePrivateIps": 383,
"uniqueSourcePrivateIpsHistogram": Array [
Object {
"x": "2019-02-09T16:00:00.000Z",
"x": 1549728000000,
"y": 8,
},
Object {
"x": "2019-02-09T19:00:00.000Z",
"x": 1549738800000,
"y": 0,
},
],

View file

@ -14,22 +14,22 @@ export const mockData: { KpiNetwork: KpiNetworkData } = {
uniqueSourcePrivateIps: 383,
uniqueSourcePrivateIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
},
],
uniqueDestinationPrivateIps: 18,
uniqueDestinationPrivateIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
},
],
@ -176,9 +176,9 @@ export const mockEnableChartsData = {
{
key: 'uniqueSourcePrivateIpsHistogram',
value: [
{ x: '2019-02-09T16:00:00.000Z', y: 8 },
{ x: new Date('2019-02-09T16:00:00.000Z').valueOf(), y: 8 },
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
},
],
@ -189,7 +189,10 @@ export const mockEnableChartsData = {
},
{
key: 'uniqueDestinationPrivateIpsHistogram',
value: [{ x: '2019-02-09T16:00:00.000Z', y: 8 }, { x: '2019-02-09T19:00:00.000Z', y: 0 }],
value: [
{ x: new Date('2019-02-09T16:00:00.000Z').valueOf(), y: 8 },
{ x: new Date('2019-02-09T19:00:00.000Z').valueOf(), y: 0 },
],
name: 'Dist.',
description: 'Destination',
color: '#490092',

View file

@ -14,39 +14,53 @@ import {
addValueToFields,
addValueToAreaChart,
addValueToBarChart,
useKpiMatrixStatus,
StatItems,
} from '.';
import { BarChart } from '../charts/barchart';
import { AreaChart } from '../charts/areachart';
import { EuiHorizontalRule } from '@elastic/eui';
import { fieldTitleChartMapping, KpiNetworkBaseComponent } from '../page/network/kpi_network';
import { fieldTitleChartMapping } from '../page/network/kpi_network';
import {
mockData,
mockNoChartMappings,
mockDisableChartsInitialData,
mockEnableChartsData,
mockEnableChartsInitialData,
mockNoChartMappings,
} from '../page/network/kpi_network/mock';
import { mockGlobalState, apolloClientObservable } from '../../mock';
import { State, createStore } from '../../store';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { KpiNetworkData, KpiHostsData } from '../../graphql/types';
jest.mock('../charts/barchart');
jest.mock('../charts/areachart');
describe('Stat Items Component', () => {
const state: State = mockGlobalState;
const store = createStore(state, apolloClientObservable);
describe.each([
[
mount(
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
key="mock-keys"
/>
<ReduxStoreProvider store={store}>
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
key="mock-keys"
/>
</ReduxStoreProvider>
),
],
[
mount(
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
areaChart={[]}
barChart={[]}
key="mock-keys"
/>
<ReduxStoreProvider store={store}>
<StatItemsComponent
fields={[{ key: 'hosts', value: null, color: '#3185FC', icon: 'cross' }]}
description="HOSTS"
areaChart={[]}
barChart={[]}
key="mock-keys"
/>
</ReduxStoreProvider>
),
],
])('disable charts', wrapper => {
@ -99,18 +113,18 @@ describe('Stat Items Component', () => {
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: 1556686800000, y: 580213 },
{ x: 1556730000000, y: 1096175 },
{ x: 1556773200000, y: 12382 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#DB1374',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: 1556686800000, y: 565975 },
{ x: 1556730000000, y: 1084366 },
{ x: 1556773200000, y: 12280 },
{ x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 },
],
color: '#490092',
},
@ -128,7 +142,11 @@ describe('Stat Items Component', () => {
};
let wrapper: ReactWrapper;
beforeAll(() => {
wrapper = mount(<StatItemsComponent {...mockStatItemsData} />);
wrapper = mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent {...mockStatItemsData} />
</ReduxStoreProvider>
);
});
test('it renders the default widget', () => {
expect(toJson(wrapper)).toMatchSnapshot();
@ -186,41 +204,52 @@ describe('addValueToBarChart', () => {
describe('useKpiMatrixStatus', () => {
const mockNetworkMappings = fieldTitleChartMapping;
const mockKpiNetworkData = mockData.KpiNetwork;
const MockChildComponent = (mappedStatItemProps: StatItemsProps) => <span />;
const MockHookWrapperComponent = ({
fieldsMapping,
data,
}: {
fieldsMapping: Readonly<StatItems[]>;
data: KpiNetworkData | KpiHostsData;
}) => {
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(fieldsMapping, data);
return (
<div>
{statItemsProps.map(mappedStatItemProps => {
return <MockChildComponent {...mappedStatItemProps} />;
})}
</div>
);
};
test('it updates status correctly', () => {
const wrapper = mount(
<KpiNetworkBaseComponent fieldsMapping={mockNetworkMappings} data={{}} />
<>
<MockHookWrapperComponent fieldsMapping={mockNetworkMappings} data={mockKpiNetworkData} />
</>
);
expect(wrapper.find(StatItemsComponent).get(0).props).toEqual(mockEnableChartsInitialData);
wrapper.setProps({ data: mockKpiNetworkData });
wrapper.update();
expect(wrapper.find(StatItemsComponent).get(0).props).toEqual(mockEnableChartsData);
expect(wrapper.find('MockChildComponent').get(0).props).toEqual(mockEnableChartsData);
});
test('it should not append areaChart if enableAreaChart is off', () => {
const mockNetworkMappingsNoAreaChart = mockNoChartMappings;
const wrapper = mount(
<KpiNetworkBaseComponent fieldsMapping={mockNetworkMappingsNoAreaChart} data={{}} />
<>
<MockHookWrapperComponent fieldsMapping={mockNoChartMappings} data={mockKpiNetworkData} />
</>
);
expect(wrapper.find(StatItemsComponent).get(0).props).toEqual(mockDisableChartsInitialData);
wrapper.setProps({ data: mockKpiNetworkData });
wrapper.update();
expect(wrapper.find(StatItemsComponent).get(0).props.areaChart).toBeUndefined();
expect(wrapper.find('MockChildComponent').get(0).props.areaChart).toBeUndefined();
});
test('it should not append barChart if enableBarChart is off', () => {
const mockNetworkMappingsNoAreaChart = mockNoChartMappings;
const wrapper = mount(
<KpiNetworkBaseComponent fieldsMapping={mockNetworkMappingsNoAreaChart} data={{}} />
<>
<MockHookWrapperComponent fieldsMapping={mockNoChartMappings} data={mockKpiNetworkData} />
</>
);
expect(wrapper.find(StatItemsComponent).get(0).props).toEqual(mockDisableChartsInitialData);
wrapper.setProps({ data: mockKpiNetworkData });
wrapper.update();
expect(wrapper.find(StatItemsComponent).get(0).props.barChart).toBeUndefined();
expect(wrapper.find('MockChildComponent').get(0).props.barChart).toBeUndefined();
});
});

View file

@ -17,11 +17,13 @@ import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { get, getOr } from 'lodash/fp';
import { ScaleType, niceTimeFormatter } from '@elastic/charts';
import { BarChart } from '../charts/barchart';
import { AreaChart } from '../charts/areachart';
import { getEmptyTagValue } from '../empty_value';
import { ChartConfigsData, ChartData } from '../charts/common';
import { ChartConfigsData, ChartData, ChartSeriesConfigs } from '../charts/common';
import { KpiHostsData, KpiNetworkData } from '../../graphql/types';
import { GlobalTime } from '../../containers/global_time';
const FlexItem = styled(EuiFlexItem)`
min-width: 0;
@ -49,6 +51,8 @@ export interface StatItems {
enableAreaChart?: boolean;
enableBarChart?: boolean;
grow?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | true | false | null;
areachartConfigs?: ChartSeriesConfigs;
barchartConfigs?: ChartSeriesConfigs;
}
export interface StatItemsProps extends StatItems {
@ -56,6 +60,27 @@ export interface StatItemsProps extends StatItems {
barChart?: ChartConfigsData[];
}
export const numberFormatter = (value: string | number): string => value.toLocaleString();
export const areachartConfigs = (from: number, to: number) => ({
series: {
xScaleType: ScaleType.Time,
yScaleType: ScaleType.Linear,
},
axis: {
xTickFormatter: niceTimeFormatter([from, to]),
yTickFormatter: numberFormatter,
},
});
export const barchartConfigs = {
series: {
xScaleType: ScaleType.Ordinal,
yScaleType: ScaleType.Linear,
},
axis: {
xTickFormatter: numberFormatter,
},
};
export const addValueToFields = (
fields: StatItem[],
data: KpiHostsData | KpiNetworkData
@ -129,7 +154,7 @@ export const useKpiMatrixStatus = (
export const StatItemsComponent = React.memo<StatItemsProps>(
({ fields, description, grow, barChart, areaChart, enableAreaChart, enableBarChart }) => {
const isBarChartDataAbailable =
const isBarChartDataAvailable =
barChart &&
barChart.length &&
barChart.every(item => item.value != null && item.value.length > 0);
@ -148,7 +173,7 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
{fields.map(field => (
<FlexItem key={`stat-items-field-${field.key}`}>
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
{(isAreaChartDataAvailable || isBarChartDataAbailable) && field.icon && (
{(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && (
<FlexItem grow={false}>
<EuiIcon
type={field.icon}
@ -177,13 +202,17 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
<EuiFlexGroup>
{enableBarChart && (
<FlexItem>
<BarChart barChart={barChart!} />
<BarChart barChart={barChart} configs={barchartConfigs} />
</FlexItem>
)}
{enableAreaChart && (
<FlexItem>
<AreaChart areaChart={areaChart!} />
<GlobalTime>
{({ from, to }) => (
<AreaChart areaChart={areaChart} configs={areachartConfigs(from, to)} />
)}
</GlobalTime>
</FlexItem>
)}
</EuiFlexGroup>

View file

@ -6932,7 +6932,7 @@
"name": "x",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
@ -7090,7 +7090,7 @@
"name": "x",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},

View file

@ -1068,7 +1068,7 @@ export interface KpiNetworkData {
}
export interface KpiNetworkHistogramData {
x?: string | null;
x?: number | null;
y?: number | null;
}
@ -1096,7 +1096,7 @@ export interface KpiHostsData {
}
export interface KpiHostHistogramData {
x?: string | null;
x?: number | null;
y?: number | null;
}
@ -5041,7 +5041,7 @@ export namespace KpiHostChartFields {
export type Fragment = {
__typename?: 'KpiHostHistogramData';
x?: string | null;
x?: number | null;
y?: number | null;
};
@ -5051,7 +5051,7 @@ export namespace KpiNetworkChartFields {
export type Fragment = {
__typename?: 'KpiNetworkHistogramData';
x?: string | null;
x?: number | null;
y?: number | null;
};

View file

@ -14,6 +14,8 @@ chrome.getUiSettingsClient().get.mockImplementation((key: string) => {
return { pause: false, value: 0 };
case 'siem:defaultIndex':
return ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'];
case 'dateFormat:tz':
return 'Asia/Taipei';
case 'theme:darkMode':
return false;
default:

View file

@ -8,7 +8,7 @@ import gql from 'graphql-tag';
export const kpiHostsSchema = gql`
type KpiHostHistogramData {
x: String
x: Float
y: Float
}

View file

@ -8,7 +8,7 @@ import gql from 'graphql-tag';
export const kpiNetworkSchema = gql`
type KpiNetworkHistogramData {
x: String
x: Float
y: Float
}

View file

@ -1097,7 +1097,7 @@ export interface KpiNetworkData {
}
export interface KpiNetworkHistogramData {
x?: string | null;
x?: number | null;
y?: number | null;
}
@ -1125,7 +1125,7 @@ export interface KpiHostsData {
}
export interface KpiHostHistogramData {
x?: string | null;
x?: number | null;
y?: number | null;
}
@ -5874,13 +5874,13 @@ export namespace KpiNetworkDataResolvers {
export namespace KpiNetworkHistogramDataResolvers {
export interface Resolvers<Context = SiemContext, TypeParent = KpiNetworkHistogramData> {
x?: XResolver<string | null, TypeParent, Context>;
x?: XResolver<number | null, TypeParent, Context>;
y?: YResolver<number | null, TypeParent, Context>;
}
export type XResolver<
R = string | null,
R = number | null,
Parent = KpiNetworkHistogramData,
Context = SiemContext
> = Resolver<R, Parent, Context>;
@ -5984,13 +5984,13 @@ export namespace KpiHostsDataResolvers {
export namespace KpiHostHistogramDataResolvers {
export interface Resolvers<Context = SiemContext, TypeParent = KpiHostHistogramData> {
x?: XResolver<string | null, TypeParent, Context>;
x?: XResolver<number | null, TypeParent, Context>;
y?: YResolver<number | null, TypeParent, Context>;
}
export type XResolver<
R = string | null,
R = number | null,
Parent = KpiHostHistogramData,
Context = SiemContext
> = Resolver<R, Parent, Context>;

View file

@ -27,8 +27,8 @@ const formatGeneralHistogramData = (
data: Array<KpiHostHistogram<KpiHostGeneralHistogramCount>>
): KpiHostHistogramData[] | null => {
return data && data.length > 0
? data.map<KpiHostHistogramData>(({ key_as_string, count }) => ({
x: key_as_string,
? data.map<KpiHostHistogramData>(({ key, count }) => ({
x: key,
y: count.value,
}))
: null;
@ -38,8 +38,8 @@ const formatAuthHistogramData = (
data: Array<KpiHostHistogram<KpiHostAuthHistogramCount>>
): KpiHostHistogramData[] | null => {
return data && data.length > 0
? data.map<KpiHostHistogramData>(({ key_as_string, count }) => ({
x: key_as_string,
? data.map<KpiHostHistogramData>(({ key, count }) => ({
x: key,
y: count.doc_count,
}))
: null;

View file

@ -32,7 +32,7 @@ export const mockRequest = {
filterQuery: '',
},
query:
'fragment ChartFields on KpiHostHistogramData {\n x: key\n y: count {\n value\n doc_count\n __typename\n }\n __typename\n}\n\nquery GetKpiHostsQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiHosts(timerange: $timerange, filterQuery: $filterQuery) {\n hosts\n hostsHistogram {\n ...ChartFields\n __typename\n }\n authSuccess\n authSuccessHistogram {\n ...ChartFields\n __typename\n }\n authFailure\n authFailureHistogram {\n ...ChartFields\n __typename\n }\n uniqueSourceIps\n uniqueSourceIpsHistogram {\n ...ChartFields\n __typename\n }\n uniqueDestinationIps\n uniqueDestinationIpsHistogram {\n ...ChartFields\n __typename\n }\n __typename\n }\n __typename\n }\n}\n',
'fragment KpiHostChartFields on KpiHostHistogramData {\n x\n y\n __typename\n}\n\nquery GetKpiHostsQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String, $defaultIndex: [String!]!) {\n source(id: $sourceId) {\n id\n KpiHosts(timerange: $timerange, filterQuery: $filterQuery, defaultIndex: $defaultIndex) {\n hosts\n hostsHistogram {\n ...KpiHostChartFields\n __typename\n }\n authSuccess\n authSuccessHistogram {\n ...KpiHostChartFields\n __typename\n }\n authFailure\n authFailureHistogram {\n ...KpiHostChartFields\n __typename\n }\n uniqueSourceIps\n uniqueSourceIpsHistogram {\n ...KpiHostChartFields\n __typename\n }\n uniqueDestinationIps\n uniqueDestinationIpsHistogram {\n ...KpiHostChartFields\n __typename\n }\n __typename\n }\n __typename\n }\n}\n',
},
query: {},
};
@ -241,75 +241,75 @@ export const mockResult = {
hosts: 986,
hostsHistogram: [
{
x: '2019-05-03T13:00:00.000Z',
x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
y: 919,
},
{
x: '2019-05-04T01:00:00.000Z',
x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
y: 82,
},
{
x: '2019-05-04T13:00:00.000Z',
x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
y: 4,
},
],
authSuccess: 61,
authSuccessHistogram: [
{
x: '2019-05-03T13:00:00.000Z',
x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
y: 8,
},
{
x: '2019-05-04T01:00:00.000Z',
x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
y: 52,
},
{
x: '2019-05-04T13:00:00.000Z',
x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
y: 1,
},
],
authFailure: 15722,
authFailureHistogram: [
{
x: '2019-05-03T13:00:00.000Z',
x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
y: 11731,
},
{
x: '2019-05-04T01:00:00.000Z',
x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
y: 3979,
},
{
x: '2019-05-04T13:00:00.000Z',
x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
y: 12,
},
],
uniqueSourceIps: 1407,
uniqueSourceIpsHistogram: [
{
x: '2019-05-03T13:00:00.000Z',
x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
y: 1182,
},
{
x: '2019-05-04T01:00:00.000Z',
x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
y: 364,
},
{
x: '2019-05-04T13:00:00.000Z',
x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
y: 63,
},
],
uniqueDestinationIps: 1954,
uniqueDestinationIpsHistogram: [
{
x: '2019-05-03T13:00:00.000Z',
x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
y: 1809,
},
{
x: '2019-05-04T01:00:00.000Z',
x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
y: 407,
},
{
x: '2019-05-04T13:00:00.000Z',
x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
y: 64,
},
],

View file

@ -23,12 +23,12 @@ import { TermAggregation } from '../types';
import { KpiNetworkHistogramData, KpiNetworkData } from '../../graphql/types';
const formatHistogramData = (
data: Array<{ key_as_string: string; count: { value: number } }>
data: Array<{ key: number; count: { value: number } }>
): KpiNetworkHistogramData[] | null => {
return data && data.length > 0
? data.map<KpiNetworkHistogramData>(({ key_as_string, count }) => {
? data.map<KpiNetworkHistogramData>(({ key, count }) => {
return {
x: key_as_string,
x: key,
y: getOr(null, 'value', count),
};
})

View file

@ -36,7 +36,7 @@ export const mockRequest = {
filterQuery: '',
},
query:
'fragment ChartFields on KpiNetworkHistogramData {\n key_as_string\n doc_count\n count {\n value\n __typename\n }\n __typename\n}\n\nquery GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n networkEvents\n networkEventsHistogram {\n ...ChartFields\n __typename\n }\n uniqueFlowId\n activeAgents\n uniqueSourcePrivateIps\n uniqueSourcePrivateIpsHistogram {\n ...ChartFields\n __typename\n }\n uniqueDestinationPrivateIps\n uniqueDestinationPrivateIpsHistogram {\n ...ChartFields\n __typename\n }\n dnsQueries\n tlsHandshakes\n __typename\n }\n __typename\n }\n}\n',
'fragment KpiNetworkChartFields on KpiNetworkHistogramData {\n x\n y\n __typename\n}\n\nquery GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String, $defaultIndex: [String!]!) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery, defaultIndex: $defaultIndex) {\n networkEvents\n uniqueFlowId\n uniqueSourcePrivateIps\n uniqueSourcePrivateIpsHistogram {\n ...KpiNetworkChartFields\n __typename\n }\n uniqueDestinationPrivateIps\n uniqueDestinationPrivateIpsHistogram {\n ...KpiNetworkChartFields\n __typename\n }\n dnsQueries\n tlsHandshakes\n __typename\n }\n __typename\n }\n}\n',
},
query: {},
};
@ -221,15 +221,15 @@ export const mockResult = {
uniqueDestinationPrivateIps: 878,
uniqueDestinationPrivateIpsHistogram: [
{
x: '2019-05-09T23:00:00.000Z',
x: new Date('2019-05-09T23:00:00.000Z').valueOf(),
y: 11,
},
{
x: '2019-05-10T11:00:00.000Z',
x: new Date('2019-05-10T11:00:00.000Z').valueOf(),
y: 877,
},
{
x: '2019-05-10T23:00:00.000Z',
x: new Date('2019-05-10T23:00:00.000Z').valueOf(),
y: 7,
},
],
@ -237,15 +237,15 @@ export const mockResult = {
uniqueSourcePrivateIps: 387,
uniqueSourcePrivateIpsHistogram: [
{
x: '2019-05-09T23:00:00.000Z',
x: new Date('2019-05-09T23:00:00.000Z').valueOf(),
y: 14,
},
{
x: '2019-05-10T11:00:00.000Z',
x: new Date('2019-05-10T11:00:00.000Z').valueOf(),
y: 385,
},
{
x: '2019-05-10T23:00:00.000Z',
x: new Date('2019-05-10T23:00:00.000Z').valueOf(),
y: 7,
},
],

View file

@ -24,22 +24,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
hosts: 1,
hostsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
@ -51,22 +51,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
uniqueSourceIps: 121,
uniqueSourceIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 52,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 31,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 88,
__typename: 'KpiHostHistogramData',
},
@ -74,22 +74,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
uniqueDestinationIps: 154,
uniqueDestinationIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 61,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 45,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 114,
__typename: 'KpiHostHistogramData',
},
@ -128,22 +128,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
hosts: 1,
hostsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 1,
__typename: 'KpiHostHistogramData',
},
@ -155,22 +155,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
uniqueSourceIps: 121,
uniqueSourceIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 52,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 31,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 88,
__typename: 'KpiHostHistogramData',
},
@ -178,22 +178,22 @@ const kpiHostsTests: KbnTestProvider = ({ getService }) => {
uniqueDestinationIps: 154,
uniqueDestinationIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 61,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 45,
__typename: 'KpiHostHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 114,
__typename: 'KpiHostHistogramData',
},

View file

@ -26,22 +26,22 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
uniqueSourcePrivateIps: 8,
uniqueSourcePrivateIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 8,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 7,
__typename: 'KpiNetworkHistogramData',
},
@ -50,22 +50,22 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
uniqueDestinationPrivateIpsHistogram: [
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 8,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 8,
},
],
@ -107,22 +107,22 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
uniqueSourcePrivateIps: 8,
uniqueSourcePrivateIpsHistogram: [
{
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 8,
__typename: 'KpiNetworkHistogramData',
},
{
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 7,
__typename: 'KpiNetworkHistogramData',
},
@ -131,22 +131,22 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
uniqueDestinationPrivateIpsHistogram: [
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T16:00:00.000Z',
x: new Date('2019-02-09T16:00:00.000Z').valueOf(),
y: 8,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T19:00:00.000Z',
x: new Date('2019-02-09T19:00:00.000Z').valueOf(),
y: 0,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-09T22:00:00.000Z',
x: new Date('2019-02-09T22:00:00.000Z').valueOf(),
y: 8,
},
{
__typename: 'KpiNetworkHistogramData',
x: '2019-02-10T01:00:00.000Z',
x: new Date('2019-02-10T01:00:00.000Z').valueOf(),
y: 8,
},
],