-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solutions] Add risk tab to the user details page (#130256)
* Remove traces of host from risk_score_over_time * Remove traces of host from risk_score_contributor and add query toggle * Add user details risk tab * Improve unit test coverage * Create User Risk Information flyout * run i18n_check.js --fix * Update users by risk table to link to users details risk tab * Improve Host Risk Flyout test coverage * Rename user and host risk tabs * Fix user risk CallOut showing when riskyUsersEnabled FF is disabled * Fix eslint warning
- Loading branch information
Showing
38 changed files
with
1,247 additions
and
643 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
...ck/plugins/security_solution/public/common/components/risk_score_over_time/index.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { render } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { RiskScoreOverTime, scoreFormatter } from '.'; | ||
import { TestProviders } from '../../mock'; | ||
import { LineSeries } from '@elastic/charts'; | ||
|
||
const mockLineSeries = LineSeries as jest.Mock; | ||
|
||
jest.mock('@elastic/charts', () => { | ||
const original = jest.requireActual('@elastic/charts'); | ||
return { | ||
...original, | ||
LineSeries: jest.fn().mockImplementation(() => <></>), | ||
}; | ||
}); | ||
|
||
describe('Risk Score Over Time', () => { | ||
it('renders', () => { | ||
const { queryByTestId } = render( | ||
<TestProviders> | ||
<RiskScoreOverTime | ||
riskScore={[]} | ||
loading={false} | ||
from={'2020-07-07T08:20:18.966Z'} | ||
to={'2020-07-08T08:20:18.966Z'} | ||
queryId={'test_query_id'} | ||
title={'test_query_title'} | ||
toggleStatus={true} | ||
/> | ||
</TestProviders> | ||
); | ||
|
||
expect(queryByTestId('RiskScoreOverTime')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders loader when loading', () => { | ||
const { queryByTestId } = render( | ||
<TestProviders> | ||
<RiskScoreOverTime | ||
loading={true} | ||
from={'2020-07-07T08:20:18.966Z'} | ||
to={'2020-07-08T08:20:18.966Z'} | ||
queryId={'test_query_id'} | ||
title={'test_query_title'} | ||
toggleStatus={true} | ||
/> | ||
</TestProviders> | ||
); | ||
|
||
expect(queryByTestId('RiskScoreOverTime-loading')).toBeInTheDocument(); | ||
}); | ||
|
||
describe('scoreFormatter', () => { | ||
it('renders score formatted', () => { | ||
render( | ||
<TestProviders> | ||
<RiskScoreOverTime | ||
riskScore={[]} | ||
loading={false} | ||
from={'2020-07-07T08:20:18.966Z'} | ||
to={'2020-07-08T08:20:18.966Z'} | ||
queryId={'test_query_id'} | ||
title={'test_query_title'} | ||
toggleStatus={true} | ||
/> | ||
</TestProviders> | ||
); | ||
|
||
const tickFormat = mockLineSeries.mock.calls[0][0].tickFormat; | ||
|
||
expect(tickFormat).toBe(scoreFormatter); | ||
}); | ||
|
||
it('renders a formatted score', () => { | ||
expect(scoreFormatter(3.000001)).toEqual('3'); | ||
expect(scoreFormatter(3.4999)).toEqual('3'); | ||
expect(scoreFormatter(3.51111)).toEqual('4'); | ||
expect(scoreFormatter(3.9999)).toEqual('4'); | ||
}); | ||
}); | ||
}); |
200 changes: 200 additions & 0 deletions
200
x-pack/plugins/security_solution/public/common/components/risk_score_over_time/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React, { useMemo, useCallback } from 'react'; | ||
import { | ||
Chart, | ||
LineSeries, | ||
ScaleType, | ||
Settings, | ||
Axis, | ||
Position, | ||
AnnotationDomainType, | ||
LineAnnotation, | ||
TooltipValue, | ||
} from '@elastic/charts'; | ||
import { euiThemeVars } from '@kbn/ui-theme'; | ||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiText, EuiPanel } from '@elastic/eui'; | ||
import styled from 'styled-components'; | ||
import { chartDefaultSettings, useTheme } from '../charts/common'; | ||
import { useTimeZone } from '../../lib/kibana'; | ||
import { histogramDateTimeFormatter } from '../utils'; | ||
import { HeaderSection } from '../header_section'; | ||
import { InspectButton, InspectButtonContainer } from '../inspect'; | ||
import * as i18n from './translations'; | ||
import { PreferenceFormattedDate } from '../formatted_date'; | ||
import { RiskScore } from '../../../../common/search_strategy'; | ||
|
||
export interface RiskScoreOverTimeProps { | ||
from: string; | ||
to: string; | ||
loading: boolean; | ||
riskScore?: RiskScore[]; | ||
queryId: string; | ||
title: string; | ||
toggleStatus: boolean; | ||
toggleQuery?: (status: boolean) => void; | ||
} | ||
|
||
const RISKY_THRESHOLD = 70; | ||
const DEFAULT_CHART_HEIGHT = 250; | ||
|
||
const StyledEuiText = styled(EuiText)` | ||
font-size: 9px; | ||
font-weight: ${({ theme }) => theme.eui.euiFontWeightSemiBold}; | ||
margin-right: ${({ theme }) => theme.eui.paddingSizes.xs}; | ||
`; | ||
|
||
const LoadingChart = styled(EuiLoadingChart)` | ||
display: block; | ||
text-align: center; | ||
`; | ||
|
||
export const scoreFormatter = (d: number) => Math.round(d).toString(); | ||
|
||
const RiskScoreOverTimeComponent: React.FC<RiskScoreOverTimeProps> = ({ | ||
from, | ||
to, | ||
riskScore, | ||
loading, | ||
queryId, | ||
title, | ||
toggleStatus, | ||
toggleQuery, | ||
}) => { | ||
const timeZone = useTimeZone(); | ||
|
||
const dataTimeFormatter = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); | ||
const headerFormatter = useCallback( | ||
(tooltip: TooltipValue) => <PreferenceFormattedDate value={tooltip.value} />, | ||
[] | ||
); | ||
|
||
const theme = useTheme(); | ||
|
||
const graphData = useMemo( | ||
() => | ||
riskScore | ||
?.map((data) => ({ | ||
x: data['@timestamp'], | ||
y: data.risk_stats.risk_score, | ||
})) | ||
.reverse() ?? [], | ||
[riskScore] | ||
); | ||
|
||
return ( | ||
<InspectButtonContainer> | ||
<EuiPanel hasBorder data-test-subj="RiskScoreOverTime"> | ||
<EuiFlexGroup gutterSize={'none'}> | ||
<EuiFlexItem grow={1}> | ||
<HeaderSection | ||
title={title} | ||
hideSubtitle | ||
toggleQuery={toggleQuery} | ||
toggleStatus={toggleStatus} | ||
/> | ||
</EuiFlexItem> | ||
{toggleStatus && ( | ||
<EuiFlexItem grow={false}> | ||
<InspectButton queryId={queryId} title={title} /> | ||
</EuiFlexItem> | ||
)} | ||
</EuiFlexGroup> | ||
|
||
{toggleStatus && ( | ||
<EuiFlexGroup gutterSize="none" direction="column"> | ||
<EuiFlexItem grow={1}> | ||
<div style={{ height: DEFAULT_CHART_HEIGHT }}> | ||
{loading ? ( | ||
<LoadingChart size="l" data-test-subj="RiskScoreOverTime-loading" /> | ||
) : ( | ||
<Chart> | ||
<Settings | ||
{...chartDefaultSettings} | ||
theme={theme} | ||
tooltip={{ | ||
headerFormatter, | ||
}} | ||
/> | ||
<Axis | ||
id="bottom" | ||
position={Position.Bottom} | ||
tickFormat={dataTimeFormatter} | ||
showGridLines | ||
gridLine={{ | ||
strokeWidth: 1, | ||
opacity: 1, | ||
dash: [3, 5], | ||
}} | ||
/> | ||
<Axis | ||
domain={{ | ||
min: 0, | ||
max: 100, | ||
}} | ||
id="left" | ||
position={Position.Left} | ||
ticks={3} | ||
style={{ | ||
tickLine: { | ||
visible: false, | ||
}, | ||
tickLabel: { | ||
padding: 10, | ||
}, | ||
}} | ||
/> | ||
<LineSeries | ||
id="RiskOverTime" | ||
name={i18n.RISK_SCORE} | ||
xScaleType={ScaleType.Time} | ||
yScaleType={ScaleType.Linear} | ||
xAccessor="x" | ||
yAccessors={['y']} | ||
timeZone={timeZone} | ||
data={graphData} | ||
tickFormat={scoreFormatter} | ||
/> | ||
<LineAnnotation | ||
id="RiskOverTime_annotation" | ||
domainType={AnnotationDomainType.YDomain} | ||
dataValues={[ | ||
{ | ||
dataValue: RISKY_THRESHOLD, | ||
details: `${RISKY_THRESHOLD}`, | ||
header: i18n.RISK_THRESHOLD, | ||
}, | ||
]} | ||
markerPosition="left" | ||
style={{ | ||
line: { | ||
strokeWidth: 1, | ||
stroke: euiThemeVars.euiColorDanger, | ||
opacity: 1, | ||
}, | ||
}} | ||
marker={ | ||
<StyledEuiText color={euiThemeVars.euiColorDarkestShade}> | ||
{i18n.RISKY} | ||
</StyledEuiText> | ||
} | ||
/> | ||
</Chart> | ||
)} | ||
</div> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
)} | ||
</EuiPanel> | ||
</InspectButtonContainer> | ||
); | ||
}; | ||
|
||
RiskScoreOverTimeComponent.displayName = 'RiskScoreOverTimeComponent'; | ||
export const RiskScoreOverTime = React.memo(RiskScoreOverTimeComponent); | ||
RiskScoreOverTime.displayName = 'RiskScoreOverTime'; |
Oops, something went wrong.