[SIEM] Add chart interactions - update date picker after brush selection on charts (#42440)

* add click and brush events

* replace setAbsoluteRangeDatePicker

* fix type error

* remove a not necessary export
This commit is contained in:
Angela Chuang 2019-08-03 19:08:59 +08:00 committed by GitHub
parent d394ded2ac
commit 392b8e16f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 168 additions and 23 deletions

View file

@ -24,6 +24,7 @@ import {
getTheme,
ChartSeriesConfigs,
browserTimezone,
chartDefaultSettings,
} from './common';
import { AutoSizer } from '../auto_sizer';
@ -68,11 +69,14 @@ export const AreaChartBaseComponent = React.memo<{
const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs);
const xAxisId = getAxisId(`group-${data[0].key}-x`);
const yAxisId = getAxisId(`group-${data[0].key}-y`);
const settings = {
...chartDefaultSettings,
...get('configs.settings', chartConfigs),
};
return chartConfigs.width && chartConfigs.height ? (
<div style={{ height: chartConfigs.height, width: chartConfigs.width, position: 'relative' }}>
<Chart>
<Settings theme={getTheme()} />
<Settings {...settings} theme={getTheme()} />
{data.map(series => {
const seriesKey = series.key;
const seriesSpecId = getSpecId(seriesKey);

View file

@ -18,6 +18,7 @@ import {
getTheme,
ChartSeriesConfigs,
browserTimezone,
chartDefaultSettings,
} from './common';
import { AutoSizer } from '../auto_sizer';
@ -32,10 +33,13 @@ export const BarChartBaseComponent = React.memo<{
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`);
const settings = {
...chartDefaultSettings,
...get('configs.settings', chartConfigs),
};
return chartConfigs.width && chartConfigs.height ? (
<Chart>
<Settings rotation={90} theme={getTheme()} />
<Settings {...settings} theme={getTheme()} />
{data.map(series => {
const barSeriesKey = series.key;
const barSeriesSpecId = getSpecId(barSeriesKey);

View file

@ -17,14 +17,18 @@ import {
ScaleType,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { TickFormatter } from '@elastic/charts/dist/lib/series/specs';
import { TickFormatter, Rotation, Rendering } from '@elastic/charts/dist/lib/series/specs';
import chrome from 'ui/chrome';
import moment from 'moment-timezone';
import { SettingSpecProps } from '@elastic/charts/dist/specs/settings';
const chartHeight = 74;
const chartDefaultRotation: Rotation = 0;
const chartDefaultRendering: Rendering = 'canvas';
const FlexGroup = styled(EuiFlexGroup)`
height: 100%;
`;
export type UpdateDateRange = (min: number, max: number) => void;
export const ChartHolder = () => (
<FlexGroup justifyContent="center" alignItems="center">
@ -38,6 +42,15 @@ export const ChartHolder = () => (
</FlexGroup>
);
export const chartDefaultSettings = {
rotation: chartDefaultRotation,
rendering: chartDefaultRendering,
animatedData: false,
showLegend: false,
showLegendDisplayValue: false,
debug: false,
};
export interface ChartData {
x: number | string | null;
y: number | string | null;
@ -54,6 +67,7 @@ export interface ChartSeriesConfigs {
xTickFormatter?: TickFormatter | undefined;
yTickFormatter?: TickFormatter | undefined;
};
settings?: Partial<SettingSpecProps>;
}
export interface ChartConfigsData {

View file

@ -17,17 +17,32 @@ describe('kpiHostsComponent', () => {
const ID = 'kpiHost';
const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
const narrowDateRange = () => {};
describe('render', () => {
test('it should render spinner if it is loading', () => {
const wrapper: ShallowWrapper = shallow(
<KpiHostsComponent data={mockKpiHostsData} from={from} id={ID} loading={true} to={to} />
<KpiHostsComponent
data={mockKpiHostsData}
from={from}
id={ID}
loading={true}
to={to}
narrowDateRange={narrowDateRange}
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it should render KpiHostsData', () => {
const wrapper: ShallowWrapper = shallow(
<KpiHostsComponent data={mockKpiHostsData} from={from} id={ID} loading={false} to={to} />
<KpiHostsComponent
data={mockKpiHostsData}
from={from}
id={ID}
loading={false}
to={to}
narrowDateRange={narrowDateRange}
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
@ -40,6 +55,7 @@ describe('kpiHostsComponent', () => {
id={ID}
loading={false}
to={to}
narrowDateRange={narrowDateRange}
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
@ -56,7 +72,16 @@ describe('kpiHostsComponent', () => {
});
beforeEach(() => {
shallow(<KpiHostsComponent data={data} from={from} id={ID} loading={false} to={to} />);
shallow(
<KpiHostsComponent
data={data}
from={from}
id={ID}
loading={false}
to={to}
narrowDateRange={narrowDateRange}
/>
);
});
afterEach(() => {
@ -68,7 +93,7 @@ describe('kpiHostsComponent', () => {
});
test(`it should apply correct mapping by given data type`, () => {
expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to);
expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to, narrowDateRange);
});
});
});

View file

@ -13,6 +13,7 @@ import { KpiHostsData, KpiHostDetailsData } from '../../../../graphql/types';
import { StatItemsComponent, StatItemsProps, useKpiMatrixStatus } from '../../../stat_items';
import { kpiHostsMapping } from './kpi_hosts_mapping';
import { kpiHostDetailsMapping } from './kpi_host_details_mapping';
import { UpdateDateRange } from '../../../charts/common';
const kpiWidgetHeight = 247;
@ -21,6 +22,7 @@ interface GenericKpiHostProps {
id: string;
loading: boolean;
to: number;
narrowDateRange: UpdateDateRange;
}
interface KpiHostsProps extends GenericKpiHostProps {
@ -43,10 +45,18 @@ export const KpiHostsComponent = ({
loading,
id,
to,
narrowDateRange,
}: KpiHostsProps | KpiHostDetailsProps) => {
const mappings =
(data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping;
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(mappings, data, id, from, to);
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
mappings,
data,
id,
from,
to,
narrowDateRange
);
return loading ? (
<FlexGroupSpinner justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>

View file

@ -35,6 +35,7 @@ exports[`KpiNetwork Component rendering it renders loading icons 1`] = `
from={1560578400000}
id="kpiNetwork"
loading={true}
narrowDateRange={[MockFunction]}
to={1560837600000}
/>
`;
@ -74,6 +75,7 @@ exports[`KpiNetwork Component rendering it renders the default widget 1`] = `
from={1560578400000}
id="kpiNetwork"
loading={false}
narrowDateRange={[MockFunction]}
to={1560837600000}
/>
`;

View file

@ -19,6 +19,7 @@ describe('KpiNetwork Component', () => {
const state: State = mockGlobalState;
const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
const narrowDateRange = jest.fn();
let store = createStore(state, apolloClientObservable);
@ -36,6 +37,7 @@ describe('KpiNetwork Component', () => {
id="kpiNetwork"
loading={true}
to={to}
narrowDateRange={narrowDateRange}
/>
</ReduxStoreProvider>
);
@ -52,6 +54,7 @@ describe('KpiNetwork Component', () => {
id="kpiNetwork"
loading={false}
to={to}
narrowDateRange={narrowDateRange}
/>
</ReduxStoreProvider>
);

View file

@ -20,6 +20,7 @@ import {
import { KpiNetworkData } from '../../../../graphql/types';
import * as i18n from './translations';
import { UpdateDateRange } from '../../../charts/common';
const kipsPerRow = 2;
const kpiWidgetHeight = 228;
@ -34,6 +35,7 @@ interface KpiNetworkProps {
id: string;
loading: boolean;
to: number;
narrowDateRange: UpdateDateRange;
}
export const fieldTitleChartMapping: Readonly<StatItems[]> = [
@ -124,14 +126,23 @@ export const KpiNetworkBaseComponent = ({
id,
from,
to,
narrowDateRange,
}: {
fieldsMapping: Readonly<StatItems[]>;
data: KpiNetworkData;
id: string;
from: number;
to: number;
narrowDateRange: UpdateDateRange;
}) => {
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(fieldsMapping, data, id, from, to);
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
fieldsMapping,
data,
id,
from,
to,
narrowDateRange
);
return (
<EuiFlexGroup wrap>
@ -143,7 +154,7 @@ export const KpiNetworkBaseComponent = ({
};
export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
({ data, from, id, loading, to }) => {
({ data, from, id, loading, to, narrowDateRange }) => {
return loading ? (
<FlexGroup justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
@ -162,6 +173,7 @@ export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
fieldsMapping={mappingsPerLine}
from={from}
to={to}
narrowDateRange={narrowDateRange}
/>
</React.Fragment>
))}
@ -173,6 +185,7 @@ export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
fieldsMapping={fieldTitleChartMapping}
from={from}
to={to}
narrowDateRange={narrowDateRange}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -7,6 +7,8 @@
import { KpiNetworkData } from '../../../../graphql/types';
import { StatItems } from '../../../stat_items';
export const mockNarrowDateRange = jest.fn();
export const mockData: { KpiNetwork: KpiNetworkData } = {
KpiNetwork: {
networkEvents: 16,
@ -216,4 +218,5 @@ export const mockEnableChartsData = {
id: 'statItem',
index: 4,
to: 1560837600000,
narrowDateRange: mockNarrowDateRange,
};

View file

@ -28,6 +28,7 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
id="statItems"
index={0}
key="mock-keys"
narrowDateRange={[MockFunction]}
to={1560837600000}
>
<Styled(EuiFlexItem)>
@ -271,6 +272,7 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
id="statItems"
index={0}
key="mock-keys"
narrowDateRange={[MockFunction]}
to={1560837600000}
>
<Styled(EuiFlexItem)>
@ -587,6 +589,7 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
id="statItems"
index={0}
key="mock-keys"
narrowDateRange={[MockFunction]}
to={1560837600000}
>
<Styled(EuiFlexItem)>
@ -973,6 +976,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "ordinal",
"yScaleType": "linear",
},
"settings": Object {
"onElementClick": [Function],
"rotation": 90,
},
}
}
>
@ -996,6 +1003,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "ordinal",
"yScaleType": "linear",
},
"settings": Object {
"onElementClick": [Function],
"rotation": 90,
},
}
}
data={
@ -1033,6 +1044,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "ordinal",
"yScaleType": "linear",
},
"settings": Object {
"onElementClick": [Function],
"rotation": 90,
},
}
}
data={
@ -1126,6 +1141,9 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "time",
"yScaleType": "linear",
},
"settings": Object {
"onBrushEnd": [MockFunction],
},
}
}
>
@ -1150,6 +1168,9 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "time",
"yScaleType": "linear",
},
"settings": Object {
"onBrushEnd": [MockFunction],
},
}
}
data={
@ -1204,6 +1225,9 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
"xScaleType": "time",
"yScaleType": "linear",
},
"settings": Object {
"onBrushEnd": [MockFunction],
},
}
}
data={

View file

@ -25,6 +25,7 @@ import {
mockData,
mockEnableChartsData,
mockNoChartMappings,
mockNarrowDateRange,
} from '../page/network/kpi_network/mock';
import { mockGlobalState, apolloClientObservable } from '../../mock';
import { State, createStore } from '../../store';
@ -50,6 +51,7 @@ describe('Stat Items Component', () => {
index={0}
key="mock-keys"
to={to}
narrowDateRange={mockNarrowDateRange}
/>
</ReduxStoreProvider>
),
@ -67,6 +69,7 @@ describe('Stat Items Component', () => {
index={0}
key="mock-keys"
to={to}
narrowDateRange={mockNarrowDateRange}
/>
</ReduxStoreProvider>
),
@ -151,6 +154,7 @@ describe('Stat Items Component', () => {
index: 0,
key: 'mock-keys',
to,
narrowDateRange: mockNarrowDateRange,
};
let wrapper: ReactWrapper;
beforeAll(() => {
@ -229,7 +233,8 @@ describe('useKpiMatrixStatus', () => {
data,
'statItem',
from,
to
to,
mockNarrowDateRange
);
return (

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ScaleType, niceTimeFormatter } from '@elastic/charts';
import { ScaleType, niceTimeFormatter, Rotation } from '@elastic/charts';
import {
EuiFlexGroup,
EuiFlexItem,
@ -18,10 +18,11 @@ import { get, getOr } from 'lodash/fp';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { BrushEndListener, ElementClickListener } from '@elastic/charts/dist/state/chart_state';
import { KpiHostsData, KpiNetworkData } from '../../graphql/types';
import { AreaChart } from '../charts/areachart';
import { BarChart } from '../charts/barchart';
import { ChartConfigsData, ChartData, ChartSeriesConfigs } from '../charts/common';
import { ChartConfigsData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common';
import { getEmptyTagValue } from '../empty_value';
import { InspectButton } from '../inspect';
@ -62,22 +63,31 @@ export interface StatItemsProps extends StatItems {
barChart?: ChartConfigsData[];
from: number;
id: string;
to: number;
narrowDateRange: UpdateDateRange;
}
export const numberFormatter = (value: string | number): string => value.toLocaleString();
export const areachartConfigs = (from: number, to: number) => ({
const statItemBarchartRotation: Rotation = 90;
export const areachartConfigs = (config?: {
xTickFormatter: (value: number) => string;
onBrushEnd?: BrushEndListener;
}) => ({
series: {
xScaleType: ScaleType.Time,
yScaleType: ScaleType.Linear,
},
axis: {
xTickFormatter: niceTimeFormatter([from, to]),
xTickFormatter: get('xTickFormatter', config),
yTickFormatter: numberFormatter,
},
settings: {
onBrushEnd: getOr(() => {}, 'onBrushEnd', config),
},
});
export const barchartConfigs = {
export const barchartConfigs = (config?: { onElementClick?: ElementClickListener }) => ({
series: {
xScaleType: ScaleType.Ordinal,
yScaleType: ScaleType.Linear,
@ -85,7 +95,11 @@ export const barchartConfigs = {
axis: {
xTickFormatter: numberFormatter,
},
};
settings: {
onElementClick: getOr(() => {}, 'onElementClick', config),
rotation: statItemBarchartRotation,
},
});
export const addValueToFields = (
fields: StatItem[],
@ -137,7 +151,8 @@ export const useKpiMatrixStatus = (
data: KpiHostsData | KpiNetworkData,
id: string,
from: number,
to: number
to: number,
narrowDateRange: UpdateDateRange
): StatItemsProps[] => {
const [statItemsProps, setStatItemsProps] = useState(mappings as StatItemsProps[]);
@ -153,6 +168,7 @@ export const useKpiMatrixStatus = (
key: `kpi-summary-${stat.key}`,
from,
to,
narrowDateRange,
};
})
);
@ -174,6 +190,7 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
id,
index,
to,
narrowDateRange,
}) => {
const [isHover, setIsHover] = useState(false);
const isBarChartDataAvailable =
@ -235,13 +252,19 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
<EuiFlexGroup>
{enableBarChart && (
<FlexItem>
<BarChart barChart={barChart} configs={barchartConfigs} />
<BarChart barChart={barChart} configs={barchartConfigs()} />
</FlexItem>
)}
{enableAreaChart && from != null && to != null && (
<FlexItem>
<AreaChart areaChart={areaChart} configs={areachartConfigs(from, to)} />
<AreaChart
areaChart={areaChart}
configs={areachartConfigs({
xTickFormatter: niceTimeFormatter([from, to]),
onBrushEnd: narrowDateRange,
})}
/>
</FlexItem>
)}
</EuiFlexGroup>

View file

@ -151,6 +151,11 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setTimeout(() => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}, 500);
}}
/>
)}
</KpiHostDetailsQuery>

View file

@ -96,6 +96,11 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery, setAbsoluteRang
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={(min: number, max: number) => {
setTimeout(() => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}, 500);
}}
/>
)}
</KpiHostsQuery>

View file

@ -85,6 +85,11 @@ const NetworkComponent = pure<NetworkComponentProps>(
loading={loading}
from={from}
to={to}
narrowDateRange={(min: number, max: number) => {
setTimeout(() => {
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
}, 500);
}}
/>
)}
</KpiNetworkQuery>