mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Explore] Risk score entity consolidation part 2 (#142401)
This commit is contained in:
parent
dffe5131d6
commit
8ee13eac52
74 changed files with 1097 additions and 1747 deletions
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ESQuery } from '../../../../typed_json';
|
||||
import { RISKY_HOSTS_INDEX_PREFIX, RISKY_USERS_INDEX_PREFIX } from '../../../../constants';
|
||||
|
||||
/**
|
||||
|
@ -27,6 +28,15 @@ export const buildUserNamesFilter = (userNames: string[]) => {
|
|||
return { terms: { 'user.name': userNames } };
|
||||
};
|
||||
|
||||
export const buildEntityNameFilter = (
|
||||
entityNames: string[],
|
||||
riskEntity: RiskScoreEntity
|
||||
): ESQuery => {
|
||||
return riskEntity === RiskScoreEntity.host
|
||||
? { terms: { 'host.name': entityNames } }
|
||||
: { terms: { 'user.name': entityNames } };
|
||||
};
|
||||
|
||||
export enum RiskQueries {
|
||||
hostsRiskScore = 'hostsRiskScore',
|
||||
usersRiskScore = 'usersRiskScore',
|
||||
|
|
|
@ -15,8 +15,8 @@ import type { RiskSeverity } from '../../../../../common/search_strategy';
|
|||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import type { HostRisk, UserRisk } from '../../../../risk_score/containers';
|
||||
import { getEmptyValue } from '../../empty_value';
|
||||
import { RiskScoreDocLink } from '../../risk_score/risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreHeaderTitle } from '../../risk_score/risk_score_onboarding/risk_score_header_title';
|
||||
import { RiskScoreDocLink } from '../../../../risk_score/components/risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreHeaderTitle } from '../../../../risk_score/components/risk_score_onboarding/risk_score_header_title';
|
||||
|
||||
interface HostRiskEntity {
|
||||
originalRisk?: RiskSeverity | undefined;
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getRiskEntityTranslation } from '../../risk_score/translations';
|
||||
import { getRiskEntityTranslation } from '../../../../risk_score/components/translations';
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
export * from '../../risk_score/translations';
|
||||
export * from '../../../../risk_score/components/translations';
|
||||
|
||||
export const FEED_NAME_PREPOSITION = i18n.translate(
|
||||
'xpack.securitySolution.eventDetails.ctiSummary.feedNamePreposition',
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { ONLY_FIRST_ITEM_PAGINATION, useRiskScoreData } from './use_risk_score_data';
|
||||
import { useUserRiskScore, useHostRiskScore } from '../../../risk_score/containers';
|
||||
import { useRiskScore } from '../../../risk_score/containers';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
|
||||
const mockUseUserRiskScore = useUserRiskScore as jest.Mock;
|
||||
const mockUseHostRiskScore = useHostRiskScore as jest.Mock;
|
||||
const mockUseBasicDataFromDetailsData = useBasicDataFromDetailsData as jest.Mock;
|
||||
jest.mock('../../../risk_score/containers');
|
||||
jest.mock('../../../timelines/components/side_panel/event_details/helpers');
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
const mockUseBasicDataFromDetailsData = useBasicDataFromDetailsData as jest.Mock;
|
||||
const defaultResult = {
|
||||
data: [],
|
||||
inspect: {},
|
||||
|
@ -24,6 +24,7 @@ const defaultResult = {
|
|||
isModuleEnabled: true,
|
||||
refetch: () => {},
|
||||
totalCount: 0,
|
||||
loading: false,
|
||||
};
|
||||
const defaultRisk = {
|
||||
loading: false,
|
||||
|
@ -41,8 +42,7 @@ const defaultArgs = [
|
|||
describe('useRiskScoreData', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseUserRiskScore.mockReturnValue([false, defaultResult]);
|
||||
mockUseHostRiskScore.mockReturnValue([false, defaultResult]);
|
||||
mockUseRiskScore.mockReturnValue(defaultResult);
|
||||
mockUseBasicDataFromDetailsData.mockReturnValue({
|
||||
hostName: 'host',
|
||||
userName: 'user',
|
||||
|
@ -63,15 +63,17 @@ describe('useRiskScoreData', () => {
|
|||
renderHook(() => useRiskScoreData(defaultArgs), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockUseUserRiskScore).toHaveBeenCalledWith({
|
||||
expect(mockUseRiskScore).toHaveBeenCalledWith({
|
||||
filterQuery: { terms: { 'user.name': ['user'] } },
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
skip: false,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
});
|
||||
expect(mockUseHostRiskScore).toHaveBeenCalledWith({
|
||||
expect(mockUseRiskScore).toHaveBeenCalledWith({
|
||||
filterQuery: { terms: { 'host.name': ['host'] } },
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
skip: false,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -80,15 +82,17 @@ describe('useRiskScoreData', () => {
|
|||
renderHook(() => useRiskScoreData(defaultArgs), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockUseUserRiskScore).toHaveBeenCalledWith({
|
||||
expect(mockUseRiskScore).toHaveBeenCalledWith({
|
||||
filterQuery: undefined,
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
skip: true,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
});
|
||||
expect(mockUseHostRiskScore).toHaveBeenCalledWith({
|
||||
expect(mockUseRiskScore).toHaveBeenCalledWith({
|
||||
filterQuery: undefined,
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
skip: true,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
|
||||
import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy';
|
||||
import {
|
||||
buildHostNamesFilter,
|
||||
buildUserNamesFilter,
|
||||
RiskScoreEntity,
|
||||
} from '../../../../common/search_strategy';
|
||||
import type { HostRisk, UserRisk } from '../../../risk_score/containers';
|
||||
import { useUserRiskScore, useHostRiskScore } from '../../../risk_score/containers';
|
||||
import { useRiskScore } from '../../../risk_score/containers';
|
||||
|
||||
export const ONLY_FIRST_ITEM_PAGINATION = {
|
||||
cursorStart: 0,
|
||||
|
@ -24,16 +28,15 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
|
|||
[hostName]
|
||||
);
|
||||
|
||||
const [
|
||||
hostRiskLoading,
|
||||
{
|
||||
data: hostRiskData,
|
||||
isLicenseValid: isHostLicenseValid,
|
||||
isModuleEnabled: isHostRiskModuleEnabled,
|
||||
},
|
||||
] = useHostRiskScore({
|
||||
const {
|
||||
data: hostRiskData,
|
||||
loading: hostRiskLoading,
|
||||
isLicenseValid: isHostLicenseValid,
|
||||
isModuleEnabled: isHostRiskModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery: hostNameFilterQuery,
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: !hostNameFilterQuery,
|
||||
});
|
||||
|
||||
|
@ -51,16 +54,15 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
|
|||
[userName]
|
||||
);
|
||||
|
||||
const [
|
||||
userRiskLoading,
|
||||
{
|
||||
data: userRiskData,
|
||||
isLicenseValid: isUserLicenseValid,
|
||||
isModuleEnabled: isUserRiskModuleEnabled,
|
||||
},
|
||||
] = useUserRiskScore({
|
||||
const {
|
||||
data: userRiskData,
|
||||
loading: userRiskLoading,
|
||||
isLicenseValid: isUserLicenseValid,
|
||||
isModuleEnabled: isUserRiskModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery: userNameFilterQuery,
|
||||
pagination: ONLY_FIRST_ITEM_PAGINATION,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
skip: !userNameFilterQuery,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,77 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreUpgradeButton } from '../risk_score_onboarding/risk_score_upgrade_button';
|
||||
import { useCheckSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_check_signal_index';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
import * as i18n from './translations';
|
||||
import * as overviewI18n from '../../../../overview/components/entity_analytics/common/translations';
|
||||
import { RiskScoreHeaderTitle } from '../risk_score_onboarding/risk_score_header_title';
|
||||
|
||||
export const RiskScoresDeprecated = ({
|
||||
entityType,
|
||||
refetch,
|
||||
timerange,
|
||||
}: {
|
||||
entityType: RiskScoreEntity;
|
||||
refetch: inputsModel.Refetch;
|
||||
timerange: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
}) => {
|
||||
const { signalIndexExists } = useCheckSignalIndex();
|
||||
|
||||
const translations = useMemo(
|
||||
() => ({
|
||||
body: i18n.UPGRADE_RISK_SCORE_DESCRIPTION,
|
||||
signal: !signalIndexExists ? i18n.ENABLE_RISK_SCORE_POPOVER : null,
|
||||
...(entityType === RiskScoreEntity.host
|
||||
? {
|
||||
cta: i18n.UPGRADE_HOST_RISK_SCORE,
|
||||
}
|
||||
: {
|
||||
cta: i18n.UPGRADE_USER_RISK_SCORE,
|
||||
}),
|
||||
}),
|
||||
[entityType, signalIndexExists]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<HeaderSection
|
||||
title={<RiskScoreHeaderTitle riskScoreEntity={entityType} />}
|
||||
titleSize="s"
|
||||
tooltip={
|
||||
entityType === RiskScoreEntity.user
|
||||
? overviewI18n.USER_RISK_TABLE_TOOLTIP
|
||||
: overviewI18n.HOST_RISK_TABLE_TOOLTIP
|
||||
}
|
||||
/>
|
||||
<EuiEmptyPrompt
|
||||
title={<h2>{translations.cta}</h2>}
|
||||
body={translations.body}
|
||||
actions={
|
||||
<EuiToolTip content={translations.signal}>
|
||||
<RiskScoreUpgradeButton
|
||||
refetch={refetch}
|
||||
riskScoreEntity={entityType}
|
||||
disabled={!signalIndexExists}
|
||||
timerange={timerange}
|
||||
data-test-subj={`upgrade_${entityType}_risk_score`}
|
||||
title={translations.cta}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -1,37 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const UPGRADE_HOST_RISK_SCORE = i18n.translate(
|
||||
'xpack.securitySolution.riskDeprecated.hosts.upgradeHostRiskScore',
|
||||
{
|
||||
defaultMessage: 'Upgrade Host Risk Score',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPGRADE_USER_RISK_SCORE = i18n.translate(
|
||||
'xpack.securitySolution.riskDeprecated.users.upgradeUserRiskScore',
|
||||
{
|
||||
defaultMessage: 'Upgrade User Risk Score',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPGRADE_RISK_SCORE_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskDeprecated.entity.upgradeHostRiskScoreDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Current data is no longer supported. Please migrate your data and upgrade the module.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_RISK_SCORE_POPOVER = i18n.translate(
|
||||
'xpack.securitySolution.riskDeprecated.entity.enableRiskScorePopoverTitle',
|
||||
{
|
||||
defaultMessage: 'Alerts need to be available before upgrading module.',
|
||||
}
|
||||
);
|
|
@ -1,62 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import { useCheckSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_check_signal_index';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
import { RiskScoreDocLink } from '../risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreEnableButton } from '../risk_score_onboarding/risk_score_enable_button';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import * as i18n from './translations';
|
||||
import { RiskScoreHeaderTitle } from '../risk_score_onboarding/risk_score_header_title';
|
||||
|
||||
const EntityAnalyticsHostRiskScoreDisableComponent = ({
|
||||
refetch,
|
||||
timerange,
|
||||
}: {
|
||||
refetch: inputsModel.Refetch;
|
||||
timerange: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
}) => {
|
||||
const { signalIndexExists } = useCheckSignalIndex();
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<HeaderSection
|
||||
title={<RiskScoreHeaderTitle riskScoreEntity={RiskScoreEntity.host} />}
|
||||
titleSize="s"
|
||||
/>
|
||||
<EuiEmptyPrompt
|
||||
title={<h2>{i18n.ENABLE_HOST_RISK_SCORE}</h2>}
|
||||
body={
|
||||
<>
|
||||
{i18n.ENABLE_HOST_RISK_SCORE_DESCRIPTION}{' '}
|
||||
<RiskScoreDocLink riskScoreEntity={RiskScoreEntity.host} />
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<EuiToolTip content={!signalIndexExists ? i18n.ENABLE_RISK_SCORE_POPOVER : null}>
|
||||
<RiskScoreEnableButton
|
||||
refetch={refetch}
|
||||
riskScoreEntity={RiskScoreEntity.host}
|
||||
disabled={!signalIndexExists}
|
||||
timerange={timerange}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const EntityAnalyticsHostRiskScoreDisable = React.memo(
|
||||
EntityAnalyticsHostRiskScoreDisableComponent
|
||||
);
|
||||
EntityAnalyticsHostRiskScoreDisable.displayName = 'EntityAnalyticsHostRiskScoreDisable';
|
|
@ -1,58 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const HOST_RISK_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.hostsRiskDashboard.title',
|
||||
{
|
||||
defaultMessage: 'Host Risk Scores',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_HOST_RISK_SCORE = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.hostsRiskDashboard.enableHostRiskScore',
|
||||
{
|
||||
defaultMessage: 'Enable Host Risk Score',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_HOST_RISK_SCORE_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.hostsRiskDashboard.enableHostRiskScoreDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Once you have enabled this feature you can get quick access to the host risk scores in this section.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_RISK_SCORE_POPOVER = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.hostsRiskDashboard.enableRiskScorePopoverTitle',
|
||||
{
|
||||
defaultMessage: 'Alerts need to be available before enabling module',
|
||||
}
|
||||
);
|
||||
|
||||
export const USER_RISK_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.usersRiskDashboard.title',
|
||||
{
|
||||
defaultMessage: 'User Risk Scores',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_USER_RISK_SCORE = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.usersRiskDashboard.enableUserRiskScore',
|
||||
{
|
||||
defaultMessage: 'Enable User Risk Score',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_USER_RISK_SCORE_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskScoreDisabled.usersRiskDashboard.enableUserRiskScoreDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Once you have enabled this feature you can get quick access to the user risk scores in this section.',
|
||||
}
|
||||
);
|
|
@ -1,62 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { useCheckSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_check_signal_index';
|
||||
import { RiskScoreHeaderTitle } from '../risk_score_onboarding/risk_score_header_title';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
import { RiskScoreDocLink } from '../risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreEnableButton } from '../risk_score_onboarding/risk_score_enable_button';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const EntityAnalyticsUserRiskScoreDisableComponent = ({
|
||||
refetch,
|
||||
timerange,
|
||||
}: {
|
||||
refetch: inputsModel.Refetch;
|
||||
timerange: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
}) => {
|
||||
const { signalIndexExists } = useCheckSignalIndex();
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<HeaderSection
|
||||
title={<RiskScoreHeaderTitle riskScoreEntity={RiskScoreEntity.user} />}
|
||||
titleSize="s"
|
||||
/>
|
||||
<EuiEmptyPrompt
|
||||
title={<h2>{i18n.ENABLE_USER_RISK_SCORE}</h2>}
|
||||
body={
|
||||
<>
|
||||
{i18n.ENABLE_USER_RISK_SCORE_DESCRIPTION}{' '}
|
||||
<RiskScoreDocLink riskScoreEntity={RiskScoreEntity.user} />
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<EuiToolTip content={!signalIndexExists ? i18n.ENABLE_RISK_SCORE_POPOVER : null}>
|
||||
<RiskScoreEnableButton
|
||||
disabled={!signalIndexExists}
|
||||
refetch={refetch}
|
||||
riskScoreEntity={RiskScoreEntity.user}
|
||||
timerange={timerange}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const EntityAnalyticsUserRiskScoreDisable = React.memo(
|
||||
EntityAnalyticsUserRiskScoreDisableComponent
|
||||
);
|
||||
EntityAnalyticsUserRiskScoreDisable.displayName = 'EntityAnalyticsUserRiskScoreDisable';
|
|
@ -1,43 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { HostRiskInformationButtonIcon, HostRiskInformationButtonEmpty } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
describe('Host Risk Flyout', () => {
|
||||
describe('HostRiskInformationButtonIcon', () => {
|
||||
it('renders', () => {
|
||||
const { queryByTestId } = render(<HostRiskInformationButtonIcon />);
|
||||
|
||||
expect(queryByTestId('open-risk-information-flyout-trigger')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('HostRiskInformationButtonEmpty', () => {
|
||||
it('renders', () => {
|
||||
const { queryByTestId } = render(<HostRiskInformationButtonEmpty />);
|
||||
|
||||
expect(queryByTestId('open-risk-information-flyout-trigger')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('opens and displays table with 5 rows', () => {
|
||||
const NUMBER_OF_ROWS = 1 + 5; // 1 header row + 5 severity rows
|
||||
const { getByTestId, queryByTestId, queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<HostRiskInformationButtonIcon />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('open-risk-information-flyout-trigger'));
|
||||
|
||||
expect(queryByTestId('risk-information-table')).toBeInTheDocument();
|
||||
expect(queryAllByRole('row')).toHaveLength(NUMBER_OF_ROWS);
|
||||
});
|
||||
});
|
|
@ -1,77 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const INFORMATION_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.informationAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Information',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_CLASSIFICATION_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.classificationHeader',
|
||||
{
|
||||
defaultMessage: 'Classification',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_RISK_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.riskHeader',
|
||||
{
|
||||
defaultMessage: 'Host risk score range',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNKNOWN_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.unknownRiskDescription',
|
||||
{
|
||||
defaultMessage: 'Less than 20',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRITICAL_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.criticalRiskDescription',
|
||||
{
|
||||
defaultMessage: '90 and above',
|
||||
}
|
||||
);
|
||||
|
||||
export const TITLE = i18n.translate('xpack.securitySolution.hosts.hostRiskInformation.title', {
|
||||
defaultMessage: 'How is host risk calculated?',
|
||||
});
|
||||
|
||||
export const INTRODUCTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.introduction',
|
||||
{
|
||||
defaultMessage:
|
||||
'The Host Risk Score capability surfaces risky hosts from within your environment.',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPLANATION_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'This feature utilizes a transform, with a scripted metric aggregation to calculate host risk scores based on detection rule alerts with an "open" status, within a 5 day time window. The transform runs hourly to keep the score updated as new detection rule alerts stream in.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOSE_BUTTON_LTEXT = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.closeBtn',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFO_BUTTON_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'How is risk score calculated?',
|
||||
}
|
||||
);
|
|
@ -13,7 +13,7 @@ import { HostsKpiUniqueIps } from './unique_ips';
|
|||
import type { HostsKpiProps } from './types';
|
||||
import { CallOutSwitcher } from '../../../common/components/callouts';
|
||||
import * as i18n from './translations';
|
||||
import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreDocLink } from '../../../risk_score/components/risk_score_onboarding/risk_score_doc_link';
|
||||
import { getHostRiskIndex, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useRiskScoreFeatureStatus } from '../../../risk_score/containers/feature_status';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
|
|
|
@ -18,6 +18,10 @@ import {
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import {
|
||||
RiskInformationButtonIcon,
|
||||
HOST_RISK_INFO_BUTTON_CLASS,
|
||||
} from '../../../../risk_score/components/risk_information';
|
||||
import {
|
||||
InspectButton,
|
||||
BUTTON_CLASS as INPECT_BUTTON_CLASS,
|
||||
|
@ -28,13 +32,9 @@ import * as i18n from './translations';
|
|||
import { useInspectQuery } from '../../../../common/hooks/use_inspect_query';
|
||||
import { useErrorToast } from '../../../../common/hooks/use_error_toast';
|
||||
|
||||
import {
|
||||
HostRiskInformationButtonIcon,
|
||||
HOST_RISK_INFO_BUTTON_CLASS,
|
||||
} from '../../host_risk_information';
|
||||
import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container';
|
||||
import type { KpiRiskScoreStrategyResponse } from '../../../../../common/search_strategy';
|
||||
import { RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import { RiskScore } from '../../../../common/components/severity/common';
|
||||
|
||||
const KpiBaseComponentLoader: React.FC = () => (
|
||||
|
@ -94,7 +94,7 @@ const RiskyHostsComponent: React.FC<{
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<HostRiskInformationButtonIcon />
|
||||
<RiskInformationButtonIcon riskEntity={RiskScoreEntity.host} />
|
||||
</EuiFlexItem>
|
||||
{data?.inspect && (
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { useMemo } from 'react';
|
|||
import { Switch } from 'react-router-dom';
|
||||
import { Route } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { RiskDetailsTabBody } from '../../../risk_score/components/risk_details_tab_body';
|
||||
import { HostsTableType } from '../../store/model';
|
||||
import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body';
|
||||
import { useGlobalTime } from '../../../common/containers/use_global_time';
|
||||
|
@ -22,7 +24,6 @@ import { type } from './utils';
|
|||
import {
|
||||
AuthenticationsQueryTabBody,
|
||||
UncommonProcessQueryTabBody,
|
||||
HostRiskTabBody,
|
||||
SessionsTabBody,
|
||||
} from '../navigation';
|
||||
import { TimelineId } from '../../../../common/types';
|
||||
|
@ -77,7 +78,11 @@ export const HostDetailsTabs = React.memo<HostDetailsTabsProps>(
|
|||
/>
|
||||
</Route>
|
||||
<Route path={`${hostDetailsPagePath}/:tabName(${HostsTableType.risk})`}>
|
||||
<HostRiskTabBody {...tabProps} />
|
||||
<RiskDetailsTabBody
|
||||
{...tabProps}
|
||||
riskEntity={RiskScoreEntity.host}
|
||||
entityName={tabProps.hostName}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={`${hostDetailsPagePath}/:tabName(${HostsTableType.sessions})`}>
|
||||
<SessionsTabBody {...tabProps} />
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useHostRiskScore, useHostRiskScoreKpi } from '../../../risk_score/containers';
|
||||
import { useRiskScore, useRiskScoreKpi } from '../../../risk_score/containers';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { HostRiskScoreQueryTabBody } from './host_risk_score_tab_body';
|
||||
import { HostsType } from '../../store/model';
|
||||
|
@ -18,8 +18,8 @@ jest.mock('../../../common/containers/query_toggle');
|
|||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
describe('Host risk score query tab body', () => {
|
||||
const mockUseHostRiskScore = useHostRiskScore as jest.Mock;
|
||||
const mockUseHostRiskScoreKpi = useHostRiskScoreKpi as jest.Mock;
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
const mockUseRiskScoreKpi = useRiskScoreKpi as jest.Mock;
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const defaultProps = {
|
||||
indexNames: [],
|
||||
|
@ -32,7 +32,7 @@ describe('Host risk score query tab body', () => {
|
|||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseHostRiskScoreKpi.mockReturnValue({
|
||||
mockUseRiskScoreKpi.mockReturnValue({
|
||||
loading: false,
|
||||
severityCount: {
|
||||
unknown: 12,
|
||||
|
@ -42,7 +42,7 @@ describe('Host risk score query tab body', () => {
|
|||
critical: 12,
|
||||
},
|
||||
});
|
||||
mockUseHostRiskScore.mockReturnValue([
|
||||
mockUseRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
hosts: [],
|
||||
|
@ -65,8 +65,8 @@ describe('Host risk score query tab body', () => {
|
|||
<HostRiskScoreQueryTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseHostRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseHostRiskScoreKpi.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseRiskScoreKpi.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
it('toggleStatus=false, skip', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
@ -75,7 +75,7 @@ describe('Host risk score query tab body', () => {
|
|||
<HostRiskScoreQueryTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseHostRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseHostRiskScoreKpi.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseRiskScoreKpi.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { noop } from 'lodash/fp';
|
||||
import { EnableRiskScore } from '../../../risk_score/components/enable_risk_score';
|
||||
import { useGlobalTime } from '../../../common/containers/use_global_time';
|
||||
import { RiskScoresDeprecated } from '../../../common/components/risk_score/risk_score_deprecated';
|
||||
import type { HostsComponentsQueryProps } from './types';
|
||||
import { manageQuery } from '../../../common/components/page/manage_query';
|
||||
import { HostRiskScoreTable } from '../../components/host_risk_score_table';
|
||||
|
@ -17,13 +17,12 @@ import { hostsModel, hostsSelectors } from '../../store';
|
|||
import type { State } from '../../../common/store';
|
||||
import {
|
||||
HostRiskScoreQueryId,
|
||||
useHostRiskScore,
|
||||
useHostRiskScoreKpi,
|
||||
useRiskScore,
|
||||
useRiskScoreKpi,
|
||||
} from '../../../risk_score/containers';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { EntityAnalyticsHostRiskScoreDisable } from '../../../common/components/risk_score/risk_score_disabled/host_risk_score_disabled';
|
||||
import { RiskScoresNoDataDetected } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { RiskScoresNoDataDetected } from '../../../risk_score/components/risk_score_onboarding/risk_score_no_data_detected';
|
||||
|
||||
const HostRiskScoreTableManage = manageQuery(HostRiskScoreTable);
|
||||
|
||||
|
@ -62,32 +61,42 @@ export const HostRiskScoreQueryTabBody = ({
|
|||
const { from, to } = useGlobalTime();
|
||||
const timerange = useMemo(() => ({ from, to }), [from, to]);
|
||||
|
||||
const [
|
||||
const {
|
||||
data,
|
||||
inspect,
|
||||
isDeprecated,
|
||||
isInspected,
|
||||
isModuleEnabled,
|
||||
loading,
|
||||
{ data, totalCount, inspect, isInspected, isDeprecated, refetch, isModuleEnabled },
|
||||
] = useHostRiskScore({
|
||||
refetch,
|
||||
totalCount,
|
||||
} = useRiskScore({
|
||||
filterQuery,
|
||||
skip: querySkip,
|
||||
pagination,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: querySkip,
|
||||
sort,
|
||||
timerange,
|
||||
});
|
||||
|
||||
const { severityCount, loading: isKpiLoading } = useHostRiskScoreKpi({
|
||||
const { severityCount, loading: isKpiLoading } = useRiskScoreKpi({
|
||||
filterQuery,
|
||||
skip: querySkip,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
});
|
||||
|
||||
if (!isModuleEnabled && !loading) {
|
||||
return <EntityAnalyticsHostRiskScoreDisable refetch={refetch} timerange={timerange} />;
|
||||
}
|
||||
const status = {
|
||||
isDisabled: !isModuleEnabled && !loading,
|
||||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (isDeprecated && !loading) {
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
return (
|
||||
<RiskScoresDeprecated
|
||||
<EnableRiskScore
|
||||
{...status}
|
||||
entityType={RiskScoreEntity.host}
|
||||
refetch={refetch}
|
||||
timerange={timerange}
|
||||
entityType={RiskScoreEntity.host}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,87 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
|
||||
import { useHostRiskScore } from '../../../risk_score/containers';
|
||||
import { HostRiskTabBody } from './host_risk_tab_body';
|
||||
import { HostsType } from '../../store/model';
|
||||
|
||||
jest.mock('../../../risk_score/containers');
|
||||
jest.mock('../../../common/containers/query_toggle');
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
describe('Host query tab body', () => {
|
||||
const mockUseUserRiskScore = useHostRiskScore as jest.Mock;
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const defaultProps = {
|
||||
hostName: 'testUser',
|
||||
indexNames: [],
|
||||
setQuery: jest.fn(),
|
||||
skip: false,
|
||||
startDate: '2019-06-25T04:31:59.345Z',
|
||||
endDate: '2019-06-25T06:31:59.345Z',
|
||||
type: HostsType.page,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseUserRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
totalCount: 0,
|
||||
refetch: jest.fn(),
|
||||
isModuleEnabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("doesn't skip when both toggleStatus are true", () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<HostRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't skip when at least one toggleStatus is true", () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<HostRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it('does skip when both toggleStatus are false', () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<HostRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -1,171 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { last } from 'lodash/fp';
|
||||
import { RiskScoresDeprecated } from '../../../common/components/risk_score/risk_score_deprecated';
|
||||
import type { HostsComponentsQueryProps } from './types';
|
||||
import * as i18n from '../translations';
|
||||
import { HostRiskInformationButtonEmpty } from '../../components/host_risk_information';
|
||||
import { HostRiskScoreQueryId, useHostRiskScore } from '../../../risk_score/containers';
|
||||
import { buildHostNamesFilter, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useQueryInspector } from '../../../common/components/page/manage_query';
|
||||
import { RiskScoreOverTime } from '../../../common/components/risk_score_over_time';
|
||||
import { TopRiskScoreContributors } from '../../../common/components/top_risk_score_contributors';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { useDashboardButtonHref } from '../../../common/hooks/use_dashboard_button_href';
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE } from './constants';
|
||||
import { EntityAnalyticsHostRiskScoreDisable } from '../../../common/components/risk_score/risk_score_disabled/host_risk_score_disabled';
|
||||
import { RiskScoresNoDataDetected } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { hostsModel, hostsSelectors } from '../../store';
|
||||
import type { State } from '../../../common/store';
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeL};
|
||||
`;
|
||||
|
||||
const QUERY_ID = HostRiskScoreQueryId.HOST_DETAILS_RISK_SCORE;
|
||||
|
||||
const HostRiskTabBodyComponent: React.FC<
|
||||
Pick<HostsComponentsQueryProps, 'startDate' | 'endDate' | 'setQuery' | 'deleteQuery'> & {
|
||||
hostName: string;
|
||||
}
|
||||
> = ({ hostName, startDate, endDate, setQuery, deleteQuery }) => {
|
||||
const getHostRiskScoreFilterQuerySelector = useMemo(
|
||||
() => hostsSelectors.hostRiskScoreSeverityFilterSelector(),
|
||||
[]
|
||||
);
|
||||
const severitySelectionRedux = useDeepEqualSelector((state: State) =>
|
||||
getHostRiskScoreFilterQuerySelector(state, hostsModel.HostsType.details)
|
||||
);
|
||||
const { buttonHref } = useDashboardButtonHref({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
title: RISKY_HOSTS_DASHBOARD_TITLE,
|
||||
});
|
||||
|
||||
const timerange = useMemo(
|
||||
() => ({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
}),
|
||||
[startDate, endDate]
|
||||
);
|
||||
|
||||
const filterQuery = useMemo(
|
||||
() => (hostName ? buildHostNamesFilter([hostName]) : undefined),
|
||||
[hostName]
|
||||
);
|
||||
|
||||
const { toggleStatus: overTimeToggleStatus, setToggleStatus: setOverTimeToggleStatus } =
|
||||
useQueryToggle(`${QUERY_ID} overTime`);
|
||||
const { toggleStatus: contributorsToggleStatus, setToggleStatus: setContributorsToggleStatus } =
|
||||
useQueryToggle(`${QUERY_ID} contributors`);
|
||||
const [loading, { data, refetch, inspect, isDeprecated, isModuleEnabled }] = useHostRiskScore({
|
||||
filterQuery,
|
||||
onlyLatest: false,
|
||||
skip: !overTimeToggleStatus && !contributorsToggleStatus,
|
||||
timerange,
|
||||
});
|
||||
|
||||
useQueryInspector({
|
||||
queryId: QUERY_ID,
|
||||
loading,
|
||||
refetch,
|
||||
setQuery,
|
||||
deleteQuery,
|
||||
inspect,
|
||||
});
|
||||
|
||||
const toggleContributorsQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setContributorsToggleStatus(status);
|
||||
},
|
||||
[setContributorsToggleStatus]
|
||||
);
|
||||
|
||||
const toggleOverTimeQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setOverTimeToggleStatus(status);
|
||||
},
|
||||
[setOverTimeToggleStatus]
|
||||
);
|
||||
|
||||
const lastHostRiskItem = last(data);
|
||||
|
||||
if (!isModuleEnabled && !loading) {
|
||||
return <EntityAnalyticsHostRiskScoreDisable refetch={refetch} timerange={timerange} />;
|
||||
}
|
||||
|
||||
if (isDeprecated && !loading) {
|
||||
return (
|
||||
<RiskScoresDeprecated
|
||||
entityType={RiskScoreEntity.host}
|
||||
refetch={refetch}
|
||||
timerange={timerange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isModuleEnabled && severitySelectionRedux.length === 0 && data && data.length === 0) {
|
||||
return <RiskScoresNoDataDetected entityType={RiskScoreEntity.host} refetch={refetch} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={2}>
|
||||
<RiskScoreOverTime
|
||||
from={startDate}
|
||||
to={endDate}
|
||||
loading={loading}
|
||||
riskScore={data}
|
||||
queryId={QUERY_ID}
|
||||
title={i18n.HOST_RISK_SCORE_OVER_TIME}
|
||||
toggleStatus={overTimeToggleStatus}
|
||||
toggleQuery={toggleOverTimeQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<TopRiskScoreContributors
|
||||
loading={loading}
|
||||
queryId={QUERY_ID}
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
rules={lastHostRiskItem ? lastHostRiskItem.host.risk.rule_risks : []}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<StyledEuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={buttonHref}
|
||||
isDisabled={!buttonHref}
|
||||
data-test-subj="risky-hosts-view-dashboard-button"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.VIEW_DASHBOARD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<HostRiskInformationButtonEmpty />
|
||||
</EuiFlexItem>
|
||||
</StyledEuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
HostRiskTabBodyComponent.displayName = 'HostRiskTabBodyComponent';
|
||||
|
||||
export const HostRiskTabBody = React.memo(HostRiskTabBodyComponent);
|
||||
|
||||
HostRiskTabBody.displayName = 'HostRiskTabBody';
|
|
@ -8,6 +8,5 @@
|
|||
export * from './authentications_query_tab_body';
|
||||
export * from './hosts_query_tab_body';
|
||||
export * from './uncommon_process_query_tab_body';
|
||||
export * from './host_risk_tab_body';
|
||||
export * from './host_risk_score_tab_body';
|
||||
export * from './sessions_tab_body';
|
||||
|
|
|
@ -30,8 +30,7 @@ jest.mock('../../../../common/components/ml/hooks/use_ml_capabilities', () => ({
|
|||
|
||||
jest.mock('../../../../risk_score/containers', () => {
|
||||
return {
|
||||
useHostRiskScoreKpi: () => ({ severityCount: mockSeverityCount }),
|
||||
useUserRiskScoreKpi: () => ({ severityCount: mockSeverityCount }),
|
||||
useRiskScoreKpi: () => ({ severityCount: mockSeverityCount }),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -10,9 +10,14 @@ import styled from 'styled-components';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { sumBy } from 'lodash/fp';
|
||||
import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public';
|
||||
import { useHostRiskScoreKpi, useUserRiskScoreKpi } from '../../../../risk_score/containers';
|
||||
import { useRiskScoreKpi } from '../../../../risk_score/containers';
|
||||
import { LinkAnchor, useGetSecuritySolutionLinkProps } from '../../../../common/components/links';
|
||||
import { Direction, RiskScoreFields, RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import {
|
||||
Direction,
|
||||
RiskScoreEntity,
|
||||
RiskScoreFields,
|
||||
RiskSeverity,
|
||||
} from '../../../../../common/search_strategy';
|
||||
import * as i18n from './translations';
|
||||
import { getTabsOnHostsUrl } from '../../../../common/components/link_to/redirect_to_hosts';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
|
@ -49,15 +54,19 @@ export const EntityAnalyticsHeader = () => {
|
|||
loading: hostRiskLoading,
|
||||
inspect: inspectHostRiskScore,
|
||||
refetch: refetchHostRiskScore,
|
||||
} = useHostRiskScoreKpi({ timerange });
|
||||
} = useRiskScoreKpi({
|
||||
timerange,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
});
|
||||
|
||||
const {
|
||||
severityCount: usersSeverityCount,
|
||||
loading: userRiskLoading,
|
||||
refetch: refetchUserRiskScore,
|
||||
inspect: inspectUserRiskScore,
|
||||
} = useUserRiskScoreKpi({
|
||||
} = useRiskScoreKpi({
|
||||
timerange,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
});
|
||||
|
||||
const { data } = useNotableAnomaliesSearch({ skip: false, from, to });
|
||||
|
@ -69,7 +78,7 @@ export const EntityAnalyticsHeader = () => {
|
|||
services: { ml, http },
|
||||
} = useKibana();
|
||||
|
||||
const [goToHostRiskTabFilterdByCritical, hostRiskTabUrl] = useMemo(() => {
|
||||
const [goToHostRiskTabFilteredByCritical, hostRiskTabUrl] = useMemo(() => {
|
||||
const { onClick, href } = getSecuritySolutionLinkProps({
|
||||
deepLinkId: SecurityPageName.hosts,
|
||||
path: getTabsOnHostsUrl(HostsTableType.risk),
|
||||
|
@ -92,7 +101,7 @@ export const EntityAnalyticsHeader = () => {
|
|||
return [onClick, href];
|
||||
}, [dispatch, getSecuritySolutionLinkProps]);
|
||||
|
||||
const [goToUserRiskTabFilterdByCritical, userRiskTabUrl] = useMemo(() => {
|
||||
const [goToUserRiskTabFilteredByCritical, userRiskTabUrl] = useMemo(() => {
|
||||
const { onClick, href } = getSecuritySolutionLinkProps({
|
||||
deepLinkId: SecurityPageName.users,
|
||||
path: getTabsOnUsersUrl(UsersTableType.risk),
|
||||
|
@ -161,7 +170,7 @@ export const EntityAnalyticsHeader = () => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<LinkAnchor
|
||||
onClick={goToHostRiskTabFilterdByCritical}
|
||||
onClick={goToHostRiskTabFilteredByCritical}
|
||||
href={hostRiskTabUrl}
|
||||
data-test-subj="critical_hosts_link"
|
||||
>
|
||||
|
@ -183,7 +192,7 @@ export const EntityAnalyticsHeader = () => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<LinkAnchor
|
||||
onClick={goToUserRiskTabFilterdByCritical}
|
||||
onClick={goToUserRiskTabFilteredByCritical}
|
||||
href={userRiskTabUrl}
|
||||
data-test-subj="critical_users_link"
|
||||
>
|
||||
|
|
|
@ -11,12 +11,7 @@ import { TestProviders } from '../../../../common/mock';
|
|||
import { EntityAnalyticsRiskScores } from '.';
|
||||
import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import type { SeverityCount } from '../../../../common/components/severity/types';
|
||||
import {
|
||||
useHostRiskScore,
|
||||
useHostRiskScoreKpi,
|
||||
useUserRiskScore,
|
||||
useUserRiskScoreKpi,
|
||||
} from '../../../../risk_score/containers';
|
||||
import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers';
|
||||
|
||||
const mockSeverityCount: SeverityCount = {
|
||||
[RiskSeverity.low]: 1,
|
||||
|
@ -40,32 +35,26 @@ const defaultProps = {
|
|||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
loading: false,
|
||||
};
|
||||
const mockUseHostRiskScore = useHostRiskScore as jest.Mock;
|
||||
const mockUseHostRiskScoreKpi = useHostRiskScoreKpi as jest.Mock;
|
||||
const mockUseUserRiskScore = useUserRiskScore as jest.Mock;
|
||||
const mockUseUserRiskScoreKpi = useUserRiskScoreKpi as jest.Mock;
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
const mockUseRiskScoreKpi = useRiskScoreKpi as jest.Mock;
|
||||
jest.mock('../../../../risk_score/containers');
|
||||
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
'EntityAnalyticsRiskScores entityType: %s',
|
||||
(riskEntity) => {
|
||||
const entity =
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? { mockUseRiskScoreKpi: mockUseHostRiskScoreKpi, mockUseRiskScore: mockUseHostRiskScore }
|
||||
: { mockUseRiskScoreKpi: mockUseUserRiskScoreKpi, mockUseRiskScore: mockUseUserRiskScore };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
entity.mockUseRiskScoreKpi.mockReturnValue({
|
||||
mockUseRiskScoreKpi.mockReturnValue({
|
||||
severityCount: mockSeverityCount,
|
||||
loading: false,
|
||||
});
|
||||
entity.mockUseRiskScore.mockReturnValue([false, defaultProps]);
|
||||
mockUseRiskScore.mockReturnValue(defaultProps);
|
||||
});
|
||||
|
||||
it('renders enable button when module is disable', () => {
|
||||
entity.mockUseRiskScore.mockReturnValue([false, { ...defaultProps, isModuleEnabled: false }]);
|
||||
mockUseRiskScore.mockReturnValue({ ...defaultProps, isModuleEnabled: false });
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<EntityAnalyticsRiskScores riskEntity={riskEntity} />
|
||||
|
@ -93,7 +82,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(entity.mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it('skips query when toggleStatus is false', () => {
|
||||
|
@ -103,7 +92,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
<EntityAnalyticsRiskScores riskEntity={riskEntity} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(entity.mockUseRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
|
||||
it('renders components when toggleStatus is true', () => {
|
||||
|
|
|
@ -8,10 +8,9 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { EntityAnalyticsUserRiskScoreDisable } from '../../../../common/components/risk_score/risk_score_disabled/user_risk_score.disabled';
|
||||
import { EnableRiskScore } from '../../../../risk_score/components/enable_risk_score';
|
||||
import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users';
|
||||
import { UsersTableType } from '../../../../users/store/model';
|
||||
import { RiskScoresDeprecated } from '../../../../common/components/risk_score/risk_score_deprecated';
|
||||
import { SeverityFilterGroup } from '../../../../common/components/severity/severity_filter_group';
|
||||
import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links';
|
||||
import { getTabsOnHostsUrl } from '../../../../common/components/link_to/redirect_to_hosts';
|
||||
|
@ -19,12 +18,7 @@ import { HostsTableType, HostsType } from '../../../../hosts/store/model';
|
|||
import { getRiskScoreColumns } from './columns';
|
||||
import { LastUpdatedAt } from '../../../../common/components/last_updated_at';
|
||||
import { HeaderSection } from '../../../../common/components/header_section';
|
||||
import {
|
||||
useHostRiskScore,
|
||||
useHostRiskScoreKpi,
|
||||
useUserRiskScore,
|
||||
useUserRiskScoreKpi,
|
||||
} from '../../../../risk_score/containers';
|
||||
import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers';
|
||||
|
||||
import type { RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
|
@ -39,21 +33,18 @@ import { hostsActions } from '../../../../hosts/store';
|
|||
import { RiskScoreDonutChart } from '../common/risk_score_donut_chart';
|
||||
import { BasicTableWithoutBorderBottom } from '../common/basic_table_without_border_bottom';
|
||||
import { RISKY_HOSTS_DOC_LINK, RISKY_USERS_DOC_LINK } from '../../../../../common/constants';
|
||||
import { EntityAnalyticsHostRiskScoreDisable } from '../../../../common/components/risk_score/risk_score_disabled/host_risk_score_disabled';
|
||||
import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title';
|
||||
import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { RiskScoreHeaderTitle } from '../../../../risk_score/components/risk_score_onboarding/risk_score_header_title';
|
||||
import { RiskScoresNoDataDetected } from '../../../../risk_score/components/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries';
|
||||
import { Loader } from '../../../../common/components/loader';
|
||||
import { Panel } from '../../../../common/components/panel';
|
||||
import * as commonI18n from '../common/translations';
|
||||
import { usersActions } from '../../../../users/store';
|
||||
|
||||
const TABLE_QUERY_ID = (riskEntity: RiskScoreEntity) =>
|
||||
riskEntity === RiskScoreEntity.host ? 'hostRiskDashboardTable' : 'userRiskDashboardTable';
|
||||
const RISK_KPI_QUERY_ID = (riskEntity: RiskScoreEntity) =>
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? 'headerHostRiskScoreKpiQuery'
|
||||
: 'headerUserRiskScoreKpiQuery';
|
||||
const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable';
|
||||
const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery';
|
||||
const USER_RISK_TABLE_QUERY_ID = 'userRiskDashboardTable';
|
||||
const USER_RISK_KPI_QUERY_ID = 'headerUserRiskScoreKpiQuery';
|
||||
|
||||
const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => {
|
||||
const { deleteQuery, setQuery, from, to } = useGlobalTime();
|
||||
|
@ -65,8 +56,6 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
riskEntity === RiskScoreEntity.host
|
||||
? {
|
||||
docLink: RISKY_HOSTS_DOC_LINK,
|
||||
kpiHook: useHostRiskScoreKpi,
|
||||
riskScoreHook: useHostRiskScore,
|
||||
linkProps: {
|
||||
deepLinkId: SecurityPageName.hosts,
|
||||
path: getTabsOnHostsUrl(HostsTableType.risk),
|
||||
|
@ -79,11 +68,11 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
);
|
||||
},
|
||||
},
|
||||
tableQueryId: HOST_RISK_TABLE_QUERY_ID,
|
||||
kpiQueryId: HOST_RISK_KPI_QUERY_ID,
|
||||
}
|
||||
: {
|
||||
docLink: RISKY_USERS_DOC_LINK,
|
||||
kpiHook: useUserRiskScoreKpi,
|
||||
riskScoreHook: useUserRiskScore,
|
||||
linkProps: {
|
||||
deepLinkId: SecurityPageName.users,
|
||||
path: getTabsOnUsersUrl(UsersTableType.risk),
|
||||
|
@ -95,11 +84,13 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
);
|
||||
},
|
||||
},
|
||||
tableQueryId: USER_RISK_TABLE_QUERY_ID,
|
||||
kpiQueryId: USER_RISK_KPI_QUERY_ID,
|
||||
},
|
||||
[dispatch, riskEntity]
|
||||
);
|
||||
|
||||
const { toggleStatus, setToggleStatus } = useQueryToggle(TABLE_QUERY_ID(riskEntity));
|
||||
const { toggleStatus, setToggleStatus } = useQueryToggle(entity.tableQueryId);
|
||||
const columns = useMemo(() => getRiskScoreColumns(riskEntity), [riskEntity]);
|
||||
const [selectedSeverity, setSelectedSeverity] = useState<RiskSeverity[]>([]);
|
||||
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps();
|
||||
|
@ -123,24 +114,30 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
loading: isKpiLoading,
|
||||
refetch: refetchKpi,
|
||||
inspect: inspectKpi,
|
||||
} = entity.kpiHook({
|
||||
} = useRiskScoreKpi({
|
||||
filterQuery: severityFilter,
|
||||
skip: !toggleStatus,
|
||||
timerange,
|
||||
riskEntity,
|
||||
});
|
||||
|
||||
useQueryInspector({
|
||||
queryId: RISK_KPI_QUERY_ID(riskEntity),
|
||||
queryId: entity.kpiQueryId,
|
||||
loading: isKpiLoading,
|
||||
refetch: refetchKpi,
|
||||
setQuery,
|
||||
deleteQuery,
|
||||
inspect: inspectKpi,
|
||||
});
|
||||
const [
|
||||
isTableLoading,
|
||||
{ data, inspect, refetch, isDeprecated, isLicenseValid, isModuleEnabled },
|
||||
] = entity.riskScoreHook({
|
||||
const {
|
||||
data,
|
||||
loading: isTableLoading,
|
||||
inspect,
|
||||
refetch,
|
||||
isDeprecated,
|
||||
isLicenseValid,
|
||||
isModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery: severityFilter,
|
||||
skip: !toggleStatus,
|
||||
pagination: {
|
||||
|
@ -148,10 +145,11 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
querySize: 5,
|
||||
},
|
||||
timerange,
|
||||
riskEntity,
|
||||
});
|
||||
|
||||
useQueryInspector({
|
||||
queryId: TABLE_QUERY_ID(riskEntity),
|
||||
queryId: entity.tableQueryId,
|
||||
loading: isTableLoading,
|
||||
refetch,
|
||||
setQuery,
|
||||
|
@ -174,17 +172,19 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!isModuleEnabled && !isTableLoading) {
|
||||
return riskEntity === RiskScoreEntity.host ? (
|
||||
<EntityAnalyticsHostRiskScoreDisable refetch={refreshPage} timerange={timerange} />
|
||||
) : (
|
||||
<EntityAnalyticsUserRiskScoreDisable refetch={refreshPage} timerange={timerange} />
|
||||
);
|
||||
}
|
||||
const status = {
|
||||
isDisabled: !isModuleEnabled && !isTableLoading,
|
||||
isDeprecated: isDeprecated && !isTableLoading,
|
||||
};
|
||||
|
||||
if (isDeprecated && !isTableLoading) {
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
return (
|
||||
<RiskScoresDeprecated entityType={riskEntity} refetch={refreshPage} timerange={timerange} />
|
||||
<EnableRiskScore
|
||||
{...status}
|
||||
entityType={riskEntity}
|
||||
refetch={refreshPage}
|
||||
timerange={timerange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
subtitle={
|
||||
<LastUpdatedAt isUpdating={isTableLoading || isKpiLoading} updatedAt={updatedAt} />
|
||||
}
|
||||
id={TABLE_QUERY_ID(riskEntity)}
|
||||
id={entity.tableQueryId}
|
||||
toggleStatus={toggleStatus}
|
||||
toggleQuery={setToggleStatus}
|
||||
tooltip={commonI18n.HOST_RISK_TABLE_TOOLTIP}
|
||||
|
@ -248,7 +248,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
items={data ?? []}
|
||||
columns={columns}
|
||||
loading={isTableLoading}
|
||||
id={TABLE_QUERY_ID(riskEntity)}
|
||||
id={entity.tableQueryId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getRiskEntityTranslation } from '../../../../common/components/risk_score/translations';
|
||||
import { getRiskEntityTranslation } from '../../../../risk_score/components/translations';
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
export * from '../../../../common/components/risk_score/translations';
|
||||
export * from '../../../../risk_score/components/translations';
|
||||
|
||||
export const ENTITY_RISK_TOOLTIP = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.entityAnalytics.riskDashboard.riskToolTip', {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { TestProviders } from '../../../common/mock';
|
|||
import { HostOverview } from '.';
|
||||
import { mockData } from './mock';
|
||||
import { mockAnomalies } from '../../../common/components/ml/mock';
|
||||
import { useHostRiskScore } from '../../../risk_score/containers/all';
|
||||
import { useRiskScore } from '../../../risk_score/containers/all';
|
||||
|
||||
const defaultProps = {
|
||||
data: undefined,
|
||||
|
@ -22,11 +22,12 @@ const defaultProps = {
|
|||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
jest.mock('../../../risk_score/containers/all');
|
||||
|
||||
const mockUseHostRiskScore = useHostRiskScore as jest.Mock;
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
|
||||
describe('Host Summary Component', () => {
|
||||
const mockProps = {
|
||||
|
@ -45,7 +46,7 @@ describe('Host Summary Component', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseHostRiskScore.mockReturnValue([true, { ...defaultProps, isModuleEnabled: false }]);
|
||||
mockUseRiskScore.mockReturnValue({ ...defaultProps, isModuleEnabled: false });
|
||||
});
|
||||
|
||||
test('it renders the default Host Summary', () => {
|
||||
|
@ -80,24 +81,22 @@ describe('Host Summary Component', () => {
|
|||
};
|
||||
const risk = 'very high host risk';
|
||||
const riskScore = 9999999;
|
||||
mockUseHostRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
...defaultProps,
|
||||
data: [
|
||||
{
|
||||
host: {
|
||||
name: 'testHostmame',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_score_norm: riskScore,
|
||||
calculated_level: risk,
|
||||
},
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
...defaultProps,
|
||||
loading: false,
|
||||
data: [
|
||||
{
|
||||
host: {
|
||||
name: 'testHostmame',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_score_norm: riskScore,
|
||||
calculated_level: risk,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexItem, EuiFlexGroup, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-theme';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-theme';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -36,9 +36,9 @@ import { DescriptionListStyled, OverviewWrapper } from '../../../common/componen
|
|||
import * as i18n from './translations';
|
||||
import { EndpointOverview } from './endpoint_overview';
|
||||
import { OverviewDescriptionList } from '../../../common/components/overview_description_list';
|
||||
import { useHostRiskScore } from '../../../risk_score/containers';
|
||||
import { useRiskScore } from '../../../risk_score/containers';
|
||||
import { RiskScore } from '../../../common/components/severity/common';
|
||||
import { RiskScoreHeaderTitle } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title';
|
||||
import { RiskScoreHeaderTitle } from '../../../risk_score/components/risk_score_onboarding/risk_score_header_title';
|
||||
|
||||
interface HostSummaryProps {
|
||||
contextID?: string; // used to provide unique draggable context when viewing in the side panel
|
||||
|
@ -93,8 +93,9 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
}),
|
||||
[from, to]
|
||||
);
|
||||
const [_, { data: hostRisk, isLicenseValid }] = useHostRiskScore({
|
||||
const { data: hostRisk, isLicenseValid } = useRiskScore({
|
||||
filterQuery,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: hostName == null,
|
||||
timerange,
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import '../../../common/mock/match_media';
|
|||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
import { mockAnomalies } from '../../../common/components/ml/mock';
|
||||
import { useUserRiskScore } from '../../../risk_score/containers/all';
|
||||
import { useRiskScore } from '../../../risk_score/containers/all';
|
||||
import type { UserSummaryProps } from '.';
|
||||
import { UserOverview } from '.';
|
||||
|
||||
|
@ -22,11 +22,12 @@ const defaultProps = {
|
|||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
jest.mock('../../../risk_score/containers/all');
|
||||
|
||||
const mockUseUserRiskScore = useUserRiskScore as jest.Mock;
|
||||
const mockRiskScore = useRiskScore as jest.Mock;
|
||||
|
||||
describe('User Summary Component', () => {
|
||||
const mockProps: UserSummaryProps = {
|
||||
|
@ -58,7 +59,7 @@ describe('User Summary Component', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseUserRiskScore.mockReturnValue([true, { ...defaultProps, isModuleEnabled: false }]);
|
||||
mockRiskScore.mockReturnValue({ ...defaultProps, loading: true, isModuleEnabled: false });
|
||||
});
|
||||
|
||||
test('it renders the default User Summary', () => {
|
||||
|
@ -94,24 +95,21 @@ describe('User Summary Component', () => {
|
|||
const risk = 'very high hos risk';
|
||||
const riskScore = 9999999;
|
||||
|
||||
mockUseUserRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
...defaultProps,
|
||||
data: [
|
||||
{
|
||||
user: {
|
||||
name: 'testUsermame',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_level: risk,
|
||||
calculated_score_norm: riskScore,
|
||||
},
|
||||
mockRiskScore.mockReturnValue({
|
||||
...defaultProps,
|
||||
data: [
|
||||
{
|
||||
user: {
|
||||
name: 'testUsermame',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_level: risk,
|
||||
calculated_score_norm: riskScore,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-theme';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-theme';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -33,10 +33,10 @@ import { DescriptionListStyled, OverviewWrapper } from '../../../common/componen
|
|||
import * as i18n from './translations';
|
||||
|
||||
import { OverviewDescriptionList } from '../../../common/components/overview_description_list';
|
||||
import { useUserRiskScore } from '../../../risk_score/containers';
|
||||
import { useRiskScore } from '../../../risk_score/containers';
|
||||
import { RiskScore } from '../../../common/components/severity/common';
|
||||
import type { UserItem } from '../../../../common/search_strategy/security_solution/users/common';
|
||||
import { RiskScoreHeaderTitle } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title';
|
||||
import { RiskScoreHeaderTitle } from '../../../risk_score/components/risk_score_onboarding/risk_score_header_title';
|
||||
|
||||
export interface UserSummaryProps {
|
||||
contextID?: string; // used to provide unique draggable context when viewing in the side panel
|
||||
|
@ -93,10 +93,11 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
[from, to]
|
||||
);
|
||||
|
||||
const [_, { data: userRisk, isLicenseValid }] = useUserRiskScore({
|
||||
const { data: userRisk, isLicenseValid } = useRiskScore({
|
||||
filterQuery,
|
||||
skip: userName == null,
|
||||
timerange,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
});
|
||||
|
||||
const getDefaultRenderer = useCallback(
|
||||
|
|
|
@ -24,7 +24,7 @@ import { useCtiDashboardLinks } from '../containers/overview_cti_links';
|
|||
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
|
||||
import { initialUserPrivilegesState } from '../../common/components/user_privileges/user_privileges_context';
|
||||
import type { EndpointPrivileges } from '../../../common/endpoint/types';
|
||||
import { useHostRiskScore } from '../../risk_score/containers';
|
||||
import { useRiskScore } from '../../risk_score/containers';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { LandingPageComponent } from '../../common/components/landing_page';
|
||||
|
||||
|
@ -99,8 +99,8 @@ const useAllTiDataSourcesMock = useAllTiDataSources as jest.Mock;
|
|||
useAllTiDataSourcesMock.mockReturnValue(mockTiDataSources);
|
||||
|
||||
jest.mock('../../risk_score/containers');
|
||||
const useHostRiskScoreMock = useHostRiskScore as jest.Mock;
|
||||
useHostRiskScoreMock.mockReturnValue([false, { data: [], isModuleEnabled: false }]);
|
||||
const useRiskScoreMock = useRiskScore as jest.Mock;
|
||||
useRiskScoreMock.mockReturnValue({ loading: false, data: [], isModuleEnabled: false });
|
||||
|
||||
jest.mock('../../common/hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { RiskScoreUpgradeButton } from '../risk_score_onboarding/risk_score_upgrade_button';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useCheckSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_check_signal_index';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
import { RiskScoreHeaderTitle } from '../risk_score_onboarding/risk_score_header_title';
|
||||
import { HeaderSection } from '../../../common/components/header_section';
|
||||
import { RiskScoreDocLink } from '../risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreEnableButton } from '../risk_score_onboarding/risk_score_enable_button';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const EnableRiskScoreComponent = ({
|
||||
isDeprecated,
|
||||
isDisabled,
|
||||
entityType,
|
||||
refetch,
|
||||
timerange,
|
||||
}: {
|
||||
isDeprecated: boolean;
|
||||
isDisabled: boolean;
|
||||
entityType: RiskScoreEntity;
|
||||
refetch: inputsModel.Refetch;
|
||||
timerange: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
}) => {
|
||||
const { signalIndexExists } = useCheckSignalIndex();
|
||||
if (!isDeprecated && !isDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const text = isDeprecated
|
||||
? {
|
||||
cta: i18n.UPGRADE_RISK_SCORE(entityType),
|
||||
body: i18n.UPGRADE_RISK_SCORE_DESCRIPTION,
|
||||
}
|
||||
: {
|
||||
cta: i18n.ENABLE_RISK_SCORE(entityType),
|
||||
body: i18n.ENABLE_RISK_SCORE_DESCRIPTION(entityType),
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<HeaderSection title={<RiskScoreHeaderTitle riskScoreEntity={entityType} />} titleSize="s" />
|
||||
<EuiEmptyPrompt
|
||||
title={<h2>{text.cta}</h2>}
|
||||
body={
|
||||
<>
|
||||
{text.body}
|
||||
{` `}
|
||||
<RiskScoreDocLink riskScoreEntity={entityType} />
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<EuiToolTip content={!signalIndexExists ? i18n.ENABLE_RISK_SCORE_POPOVER : null}>
|
||||
{isDeprecated ? (
|
||||
<RiskScoreUpgradeButton
|
||||
refetch={refetch}
|
||||
riskScoreEntity={entityType}
|
||||
disabled={!signalIndexExists}
|
||||
timerange={timerange}
|
||||
data-test-subj={`upgrade_${entityType}_risk_score`}
|
||||
title={text.cta}
|
||||
/>
|
||||
) : (
|
||||
<RiskScoreEnableButton
|
||||
disabled={!signalIndexExists}
|
||||
refetch={refetch}
|
||||
riskScoreEntity={entityType}
|
||||
timerange={timerange}
|
||||
/>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const EnableRiskScore = React.memo(EnableRiskScoreComponent);
|
||||
EnableRiskScore.displayName = 'EnableRiskScore';
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { getRiskEntityTranslation } from '../translations';
|
||||
|
||||
export const ENABLE_RISK_SCORE_POPOVER = i18n.translate(
|
||||
'xpack.securitySolution.enableRiskScore.enableRiskScorePopoverTitle',
|
||||
{
|
||||
defaultMessage: 'Alerts need to be available before enabling module',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPGRADE_RISK_SCORE = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.enableRiskScore.upgradeRiskScore', {
|
||||
defaultMessage: 'Upgrade {riskEntity} Risk Score',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity),
|
||||
},
|
||||
});
|
||||
|
||||
export const UPGRADE_RISK_SCORE_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskDeprecated.entity.upgradeRiskScoreDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Current data is no longer supported. Please migrate your data and upgrade the module.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_RISK_SCORE = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.enableRiskScore.enableRiskScore', {
|
||||
defaultMessage: 'Enable {riskEntity} Risk Score',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity),
|
||||
},
|
||||
});
|
||||
|
||||
export const ENABLE_RISK_SCORE_DESCRIPTION = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.enableRiskScore.enableRiskScoreDescription', {
|
||||
defaultMessage:
|
||||
'Once you have enabled this feature you can get quick access to the {riskEntity} risk scores in this section.',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity, true),
|
||||
},
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
|
||||
import { useRiskScore } from '../../containers';
|
||||
import { RiskDetailsTabBody } from '.';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { HostsType } from '../../../hosts/store/model';
|
||||
import { UsersType } from '../../../users/store/model';
|
||||
|
||||
jest.mock('../../containers');
|
||||
jest.mock('../../../common/containers/query_toggle');
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
'Risk Tab Body entityType: %s',
|
||||
(riskEntity) => {
|
||||
const defaultProps = {
|
||||
entityName: 'testEntity',
|
||||
indexNames: [],
|
||||
setQuery: jest.fn(),
|
||||
skip: false,
|
||||
startDate: '2019-06-25T04:31:59.345Z',
|
||||
endDate: '2019-06-25T06:31:59.345Z',
|
||||
type: riskEntity === RiskScoreEntity.host ? HostsType.page : UsersType.page,
|
||||
riskEntity,
|
||||
};
|
||||
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
loading: false,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
totalCount: 0,
|
||||
refetch: jest.fn(),
|
||||
isModuleEnabled: true,
|
||||
});
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
});
|
||||
|
||||
it('calls with correct arguments for each entity', () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<RiskDetailsTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseRiskScore).toBeCalledWith({
|
||||
filterQuery: {
|
||||
terms: {
|
||||
[`${riskEntity}.name`]: ['testEntity'],
|
||||
},
|
||||
},
|
||||
onlyLatest: false,
|
||||
riskEntity,
|
||||
skip: false,
|
||||
timerange: {
|
||||
from: '2019-06-25T04:31:59.345Z',
|
||||
to: '2019-06-25T06:31:59.345Z',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't skip when both toggleStatus are true", () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<RiskDetailsTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't skip when at least one toggleStatus is true", () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<RiskDetailsTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it('does skip when both toggleStatus are false', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<RiskDetailsTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE, RISKY_USERS_DASHBOARD_TITLE } from '../../constants';
|
||||
import { EnableRiskScore } from '../enable_risk_score';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import type { State } from '../../../common/store';
|
||||
import { hostsModel, hostsSelectors } from '../../../hosts/store';
|
||||
import { usersSelectors } from '../../../users/store';
|
||||
import { RiskInformationButtonEmpty } from '../risk_information';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { useQueryInspector } from '../../../common/components/page/manage_query';
|
||||
import { RiskScoreOverTime } from '../risk_score_over_time';
|
||||
import { TopRiskScoreContributors } from '../../../common/components/top_risk_score_contributors';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { HostRiskScoreQueryId, UserRiskScoreQueryId, useRiskScore } from '../../containers';
|
||||
import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
|
||||
import { buildEntityNameFilter, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import type { UsersComponentsQueryProps } from '../../../users/pages/navigation/types';
|
||||
import type { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types';
|
||||
import { useDashboardButtonHref } from '../../../common/hooks/use_dashboard_button_href';
|
||||
import { RiskScoresNoDataDetected } from '../risk_score_onboarding/risk_score_no_data_detected';
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeL};
|
||||
`;
|
||||
|
||||
type ComponentsQueryProps = HostsComponentsQueryProps | UsersComponentsQueryProps;
|
||||
|
||||
const getDashboardTitle = (riskEntity: RiskScoreEntity) =>
|
||||
riskEntity === RiskScoreEntity.host ? RISKY_HOSTS_DASHBOARD_TITLE : RISKY_USERS_DASHBOARD_TITLE;
|
||||
|
||||
const RiskDetailsTabBodyComponent: React.FC<
|
||||
Pick<ComponentsQueryProps, 'startDate' | 'endDate' | 'setQuery' | 'deleteQuery'> & {
|
||||
entityName: string;
|
||||
riskEntity: RiskScoreEntity;
|
||||
}
|
||||
> = ({ entityName, startDate, endDate, setQuery, deleteQuery, riskEntity }) => {
|
||||
const queryId = useMemo(
|
||||
() =>
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? HostRiskScoreQueryId.HOST_DETAILS_RISK_SCORE
|
||||
: UserRiskScoreQueryId.USER_DETAILS_RISK_SCORE,
|
||||
[riskEntity]
|
||||
);
|
||||
|
||||
const severitySelectionRedux = useDeepEqualSelector((state: State) =>
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? hostsSelectors.hostRiskScoreSeverityFilterSelector()(state, hostsModel.HostsType.details)
|
||||
: usersSelectors.userRiskScoreSeverityFilterSelector()(state)
|
||||
);
|
||||
|
||||
const { buttonHref } = useDashboardButtonHref({
|
||||
to: endDate,
|
||||
from: startDate,
|
||||
title: getDashboardTitle(riskEntity),
|
||||
});
|
||||
|
||||
const timerange = useMemo(
|
||||
() => ({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
}),
|
||||
[startDate, endDate]
|
||||
);
|
||||
|
||||
const { toggleStatus: overTimeToggleStatus, setToggleStatus: setOverTimeToggleStatus } =
|
||||
useQueryToggle(`${queryId} overTime`);
|
||||
const { toggleStatus: contributorsToggleStatus, setToggleStatus: setContributorsToggleStatus } =
|
||||
useQueryToggle(`${queryId} contributors`);
|
||||
|
||||
const filterQuery = useMemo(
|
||||
() => (entityName ? buildEntityNameFilter([entityName], riskEntity) : {}),
|
||||
[entityName, riskEntity]
|
||||
);
|
||||
const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled } = useRiskScore({
|
||||
filterQuery,
|
||||
onlyLatest: false,
|
||||
riskEntity,
|
||||
skip: !overTimeToggleStatus && !contributorsToggleStatus,
|
||||
timerange,
|
||||
});
|
||||
|
||||
const rules = useMemo(() => {
|
||||
const lastRiskItem = data && data.length > 0 ? data[data.length - 1] : null;
|
||||
if (lastRiskItem) {
|
||||
return riskEntity === RiskScoreEntity.host
|
||||
? (lastRiskItem as HostRiskScore).host.risk.rule_risks
|
||||
: (lastRiskItem as UserRiskScore).user.risk.rule_risks;
|
||||
}
|
||||
return [];
|
||||
}, [data, riskEntity]);
|
||||
|
||||
useQueryInspector({
|
||||
queryId,
|
||||
loading,
|
||||
refetch,
|
||||
setQuery,
|
||||
deleteQuery,
|
||||
inspect,
|
||||
});
|
||||
|
||||
const toggleContributorsQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setContributorsToggleStatus(status);
|
||||
},
|
||||
[setContributorsToggleStatus]
|
||||
);
|
||||
|
||||
const toggleOverTimeQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setOverTimeToggleStatus(status);
|
||||
},
|
||||
[setOverTimeToggleStatus]
|
||||
);
|
||||
|
||||
const status = {
|
||||
isDisabled: !isModuleEnabled && !loading,
|
||||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
return (
|
||||
<EnableRiskScore
|
||||
{...status}
|
||||
entityType={riskEntity}
|
||||
refetch={refetch}
|
||||
timerange={timerange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isModuleEnabled && severitySelectionRedux.length === 0 && data && data.length === 0) {
|
||||
return <RiskScoresNoDataDetected entityType={riskEntity} refetch={refetch} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={2}>
|
||||
<RiskScoreOverTime
|
||||
from={startDate}
|
||||
to={endDate}
|
||||
loading={loading}
|
||||
riskScore={data}
|
||||
queryId={queryId}
|
||||
title={i18n.RISK_SCORE_OVER_TIME(riskEntity)}
|
||||
toggleStatus={overTimeToggleStatus}
|
||||
toggleQuery={toggleOverTimeQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<TopRiskScoreContributors
|
||||
loading={loading}
|
||||
queryId={queryId}
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
rules={rules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<StyledEuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={buttonHref}
|
||||
isDisabled={!buttonHref}
|
||||
data-test-subj={`risky-${riskEntity}s-view-dashboard-button`}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.VIEW_DASHBOARD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskInformationButtonEmpty riskEntity={riskEntity} />
|
||||
</EuiFlexItem>
|
||||
</StyledEuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
RiskDetailsTabBodyComponent.displayName = 'RiskDetailsTabBodyComponent';
|
||||
|
||||
export const RiskDetailsTabBody = React.memo(RiskDetailsTabBodyComponent);
|
||||
|
||||
RiskDetailsTabBody.displayName = 'RiskDetailsTabBody';
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { getRiskEntityTranslation } from '../translations';
|
||||
|
||||
export const RISK_SCORE_OVER_TIME = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskTabBody.scoreOverTimeTitle', {
|
||||
defaultMessage: '{riskEntity} risk score over time',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity),
|
||||
},
|
||||
});
|
||||
|
||||
export const VIEW_DASHBOARD_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.riskTabBody.viewDashboardButtonLabel',
|
||||
{
|
||||
defaultMessage: 'View source dashboard',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { RiskInformationButtonEmpty, RiskInformationButtonIcon } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
'Risk Information entityType: %s',
|
||||
(riskEntity) => {
|
||||
describe.each(['RiskInformationButtonIcon', 'RiskInformationButtonEmpty'])(
|
||||
`%s component`,
|
||||
(componentType) => {
|
||||
const Component =
|
||||
componentType === 'RiskInformationButtonIcon'
|
||||
? RiskInformationButtonIcon
|
||||
: RiskInformationButtonEmpty;
|
||||
|
||||
it('renders', () => {
|
||||
const { queryByTestId } = render(<Component riskEntity={riskEntity} />);
|
||||
|
||||
expect(queryByTestId('open-risk-information-flyout-trigger')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens and displays table with 5 rows', () => {
|
||||
const NUMBER_OF_ROWS = 1 + 5; // 1 header row + 5 severity rows
|
||||
const { getByTestId, queryByTestId, queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<Component riskEntity={riskEntity} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('open-risk-information-flyout-trigger'));
|
||||
|
||||
expect(queryByTestId('risk-information-table')).toBeInTheDocument();
|
||||
expect(queryAllByRole('row')).toHaveLength(NUMBER_OF_ROWS);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
|
@ -7,32 +7,31 @@
|
|||
|
||||
import type { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import {
|
||||
useGeneratedHtmlId,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiBasicTable,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiButton,
|
||||
EuiFlyoutHeader,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
import { getRiskEntityTranslation } from '../translations';
|
||||
import * as i18n from './translations';
|
||||
import { useOnOpenCloseHandler } from '../../../helper_hooks';
|
||||
import { RiskScore } from '../../../common/components/severity/common';
|
||||
import { RiskScoreEntity, RiskSeverity } from '../../../../common/search_strategy';
|
||||
import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreDocLink } from '../risk_score_onboarding/risk_score_doc_link';
|
||||
|
||||
const tableColumns: Array<EuiBasicTableColumn<TableItem>> = [
|
||||
const getTableColumns = (riskEntity: RiskScoreEntity): Array<EuiBasicTableColumn<TableItem>> => [
|
||||
{
|
||||
field: 'classification',
|
||||
name: i18n.INFORMATION_CLASSIFICATION_HEADER,
|
||||
|
@ -44,7 +43,7 @@ const tableColumns: Array<EuiBasicTableColumn<TableItem>> = [
|
|||
},
|
||||
{
|
||||
field: 'range',
|
||||
name: i18n.INFORMATION_RISK_HEADER,
|
||||
name: i18n.INFORMATION_RISK_HEADER(riskEntity),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -60,10 +59,10 @@ const tableItems: TableItem[] = [
|
|||
{ classification: RiskSeverity.low, range: '20 - 40' },
|
||||
{ classification: RiskSeverity.unknown, range: i18n.UNKNOWN_RISK_DESCRIPTION },
|
||||
];
|
||||
|
||||
export const HOST_RISK_INFO_BUTTON_CLASS = 'HostRiskInformation__button';
|
||||
export const USER_RISK_INFO_BUTTON_CLASS = 'UserRiskInformation__button';
|
||||
|
||||
export const HostRiskInformationButtonIcon = () => {
|
||||
export const RiskInformationButtonIcon = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => {
|
||||
const [isFlyoutVisible, handleOnOpen, handleOnClose] = useOnOpenCloseHandler();
|
||||
|
||||
return (
|
||||
|
@ -74,15 +73,21 @@ export const HostRiskInformationButtonIcon = () => {
|
|||
iconType="iInCircle"
|
||||
aria-label={i18n.INFORMATION_ARIA_LABEL}
|
||||
onClick={handleOnOpen}
|
||||
className={HOST_RISK_INFO_BUTTON_CLASS}
|
||||
className={
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? HOST_RISK_INFO_BUTTON_CLASS
|
||||
: USER_RISK_INFO_BUTTON_CLASS
|
||||
}
|
||||
data-test-subj="open-risk-information-flyout-trigger"
|
||||
/>
|
||||
{isFlyoutVisible && <HostRiskInformationFlyout handleOnClose={handleOnClose} />}
|
||||
{isFlyoutVisible && (
|
||||
<RiskInformationFlyout riskEntity={riskEntity} handleOnClose={handleOnClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const HostRiskInformationButtonEmpty = () => {
|
||||
export const RiskInformationButtonEmpty = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => {
|
||||
const [isFlyoutVisible, handleOnOpen, handleOnClose] = useOnOpenCloseHandler();
|
||||
|
||||
return (
|
||||
|
@ -90,14 +95,22 @@ export const HostRiskInformationButtonEmpty = () => {
|
|||
<EuiButtonEmpty onClick={handleOnOpen} data-test-subj="open-risk-information-flyout-trigger">
|
||||
{i18n.INFO_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
{isFlyoutVisible && <HostRiskInformationFlyout handleOnClose={handleOnClose} />}
|
||||
{isFlyoutVisible && (
|
||||
<RiskInformationFlyout riskEntity={riskEntity} handleOnClose={handleOnClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const HostRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => void }) => {
|
||||
const RiskInformationFlyout = ({
|
||||
handleOnClose,
|
||||
riskEntity,
|
||||
}: {
|
||||
handleOnClose: () => void;
|
||||
riskEntity: RiskScoreEntity;
|
||||
}) => {
|
||||
const simpleFlyoutTitleId = useGeneratedHtmlId({
|
||||
prefix: 'HostRiskInformation',
|
||||
prefix: 'RiskInformation',
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -110,36 +123,37 @@ const HostRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => voi
|
|||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={simpleFlyoutTitleId}>{i18n.TITLE}</h2>
|
||||
<h2 id={simpleFlyoutTitleId}>{i18n.TITLE(riskEntity)}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText size="s">
|
||||
<p>{i18n.INTRODUCTION}</p>
|
||||
<p>{i18n.EXPLANATION_MESSAGE}</p>
|
||||
<p>{i18n.INTRODUCTION(riskEntity)}</p>
|
||||
<p>{i18n.EXPLANATION_MESSAGE(riskEntity)}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiBasicTable
|
||||
columns={tableColumns}
|
||||
columns={getTableColumns(riskEntity)}
|
||||
items={tableItems}
|
||||
data-test-subj="risk-information-table"
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hosts.hostRiskInformation.learnMore"
|
||||
defaultMessage="You can learn more about host risk {HostRiskScoreDocumentationLink}"
|
||||
id="xpack.securitySolution.riskInformation.learnMore"
|
||||
defaultMessage="You can learn more about {riskEntity} risk {riskScoreDocumentationLink}"
|
||||
values={{
|
||||
HostRiskScoreDocumentationLink: (
|
||||
riskScoreDocumentationLink: (
|
||||
<RiskScoreDocLink
|
||||
riskScoreEntity={RiskScoreEntity.host}
|
||||
riskScoreEntity={riskEntity}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hosts.hostRiskInformation.link"
|
||||
id="xpack.securitySolution.riskInformation.link"
|
||||
defaultMessage="here"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
riskEntity: getRiskEntityTranslation(riskEntity, true),
|
||||
}}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { getRiskEntityTranslation } from '../translations';
|
||||
|
||||
export const INFORMATION_CLASSIFICATION_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.classificationHeader',
|
||||
{
|
||||
defaultMessage: 'Classification',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.informationAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Information',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_RISK_HEADER = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskInformation.riskHeader', {
|
||||
defaultMessage: '{riskEntity} risk score range',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity),
|
||||
},
|
||||
});
|
||||
|
||||
export const UNKNOWN_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.unknownRiskDescription',
|
||||
{
|
||||
defaultMessage: 'Less than 20',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRITICAL_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.criticalRiskDescription',
|
||||
{
|
||||
defaultMessage: '90 and above',
|
||||
}
|
||||
);
|
||||
export const TITLE = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskInformation.title', {
|
||||
defaultMessage: 'How is {riskEntity} risk calculated?',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity, true),
|
||||
},
|
||||
});
|
||||
|
||||
export const INTRODUCTION = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskInformation.introduction', {
|
||||
defaultMessage:
|
||||
'The {riskEntity} Risk Score capability surfaces risky {riskEntityLowerPlural} from within your environment.',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity),
|
||||
riskEntityLowerPlural: getRiskEntityTranslation(riskEntity, true, true),
|
||||
},
|
||||
});
|
||||
|
||||
export const EXPLANATION_MESSAGE = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskInformation.explanation', {
|
||||
defaultMessage:
|
||||
'This feature utilizes a transform, with a scripted metric aggregation to calculate {riskEntityLower} risk scores based on detection rule alerts with an "open" status, within a 5 day time window. The transform runs hourly to keep the score updated as new detection rule alerts stream in.',
|
||||
values: {
|
||||
riskEntityLower: getRiskEntityTranslation(riskEntity, true),
|
||||
},
|
||||
});
|
||||
|
||||
export const CLOSE_BUTTON_LTEXT = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.closeBtn',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFO_BUTTON_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.riskInformation.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'How is risk score calculated?',
|
||||
}
|
||||
);
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { RISKY_HOSTS_DOC_LINK, RISKY_USERS_DOC_LINK } from '../../../../../common/constants';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { LEARN_MORE } from '../../../../overview/components/entity_analytics/risk_score/translations';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { RISKY_HOSTS_DOC_LINK, RISKY_USERS_DOC_LINK } from '../../../../common/constants';
|
||||
import { LEARN_MORE } from '../../../overview/components/entity_analytics/risk_score/translations';
|
||||
|
||||
const RiskScoreDocLinkComponent = ({
|
||||
riskScoreEntity,
|
|
@ -7,8 +7,8 @@
|
|||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../mock/test_providers';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
import { RiskScoreEnableButton } from './risk_score_enable_button';
|
||||
import { installRiskScoreModule } from './utils';
|
|
@ -9,11 +9,11 @@ import { EuiButton } from '@elastic/eui';
|
|||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { useSpaceId } from '../../../hooks/use_space_id';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../hooks/use_fetch';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../common/hooks/use_fetch';
|
||||
import { useRiskScoreToastContent } from './use_risk_score_toast_content';
|
||||
import { installRiskScoreModule } from './utils';
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { NavItemBetaBadge } from '../../navigation/nav_item_beta_badge';
|
||||
import * as i18n from '../../../../overview/components/entity_analytics/common/translations';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { NavItemBetaBadge } from '../../../common/components/navigation/nav_item_beta_badge';
|
||||
import * as i18n from '../../../overview/components/entity_analytics/common/translations';
|
||||
import { TECHNICAL_PREVIEW } from './translations';
|
||||
|
||||
const RiskScoreHeaderTitleComponent = ({
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
|
||||
import { HeaderSection } from '../../header_section';
|
||||
import { HeaderSection } from '../../../common/components/header_section';
|
||||
import * as i18n from './translations';
|
||||
import { RiskScoreHeaderTitle } from './risk_score_header_title';
|
||||
import { RiskScoreRestartButton } from './risk_score_restart_button';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import * as overviewI18n from '../../../../overview/components/entity_analytics/common/translations';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
import * as overviewI18n from '../../../overview/components/entity_analytics/common/translations';
|
||||
|
||||
const RiskScoresNoDataDetectedComponent = ({
|
||||
entityType,
|
|
@ -7,8 +7,8 @@
|
|||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../mock/test_providers';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { RiskScoreRestartButton } from './risk_score_restart_button';
|
||||
|
||||
import { restartRiskScoreTransforms } from './utils';
|
|
@ -9,11 +9,11 @@ import { EuiButton } from '@elastic/eui';
|
|||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { useSpaceId } from '../../../hooks/use_space_id';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../hooks/use_fetch';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../common/hooks/use_fetch';
|
||||
import { useRiskScoreToastContent } from './use_risk_score_toast_content';
|
||||
import { restartRiskScoreTransforms } from './utils';
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../mock/test_providers';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { RiskScoreUpgradeButton } from './risk_score_upgrade_button';
|
||||
import { upgradeHostRiskScoreModule, upgradeUserRiskScoreModule } from './utils';
|
||||
|
|
@ -8,13 +8,13 @@
|
|||
import { EuiButton, EuiConfirmModal } from '@elastic/eui';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useSpaceId } from '../../../hooks/use_space_id';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
import { upgradeHostRiskScoreModule, upgradeUserRiskScoreModule } from './utils';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useRiskScoreToastContent } from './use_risk_score_toast_content';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../hooks/use_fetch';
|
||||
import { REQUEST_NAMES, useFetch } from '../../../common/hooks/use_fetch';
|
||||
import { RiskScoreDocLink } from './risk_score_doc_link';
|
||||
|
||||
const RiskScoreUpgradeButtonComponent = ({
|
|
@ -10,7 +10,7 @@ import React, { useCallback, useMemo } from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import type { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { RiskScoreDocLink } from './risk_score_doc_link';
|
||||
|
||||
const StyledButton = styled(EuiButton)`
|
|
@ -5,25 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import {
|
||||
getLegacyIngestPipelineName,
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
} from '../../../../../common/utils/risk_score_modules';
|
||||
} from '../../../../common/utils/risk_score_modules';
|
||||
import {
|
||||
bulkDeletePrebuiltSavedObjects,
|
||||
bulkCreatePrebuiltSavedObjects,
|
||||
} from '../../../../risk_score/containers/onboarding/api';
|
||||
} from '../../containers/onboarding/api';
|
||||
|
||||
import * as api from '../../../../risk_score/containers/onboarding/api';
|
||||
import * as api from '../../containers/onboarding/api';
|
||||
import {
|
||||
installRiskScoreModule,
|
||||
restartRiskScoreTransforms,
|
||||
uninstallLegacyRiskScoreModule,
|
||||
} from './utils';
|
||||
|
||||
jest.mock('../../../../risk_score/containers/onboarding/api');
|
||||
jest.mock('../../containers/onboarding/api');
|
||||
|
||||
const mockHttp = {
|
||||
post: jest.fn(),
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
import type { HttpSetup, NotificationsStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import * as utils from '../../../../../common/utils/risk_score_modules';
|
||||
import type { inputsModel } from '../../../store';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import * as utils from '../../../../common/utils/risk_score_modules';
|
||||
import type { inputsModel } from '../../../common/store';
|
||||
|
||||
import {
|
||||
createIngestPipeline,
|
||||
|
@ -22,7 +22,7 @@ import {
|
|||
stopTransforms,
|
||||
bulkCreatePrebuiltSavedObjects,
|
||||
bulkDeletePrebuiltSavedObjects,
|
||||
} from '../../../../risk_score/containers/onboarding/api';
|
||||
} from '../../containers/onboarding/api';
|
||||
import {
|
||||
INGEST_PIPELINE_DELETION_ERROR_MESSAGE,
|
||||
INSTALLATION_ERROR,
|
||||
|
@ -30,7 +30,7 @@ import {
|
|||
TRANSFORM_CREATION_ERROR_MESSAGE,
|
||||
TRANSFORM_DELETION_ERROR_MESSAGE,
|
||||
UNINSTALLATION_ERROR,
|
||||
} from '../../../../risk_score/containers/onboarding/api/translations';
|
||||
} from '../../containers/onboarding/api/translations';
|
||||
|
||||
interface InstallRiskyScoreModule {
|
||||
dashboard?: DashboardStart;
|
|
@ -8,7 +8,7 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { RiskScoreOverTime, scoreFormatter } from '.';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { LineSeries } from '@elastic/charts';
|
||||
|
||||
const mockLineSeries = LineSeries as jest.Mock;
|
|
@ -20,13 +20,13 @@ import {
|
|||
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 { chartDefaultSettings, useTheme } from '../../../common/components/charts/common';
|
||||
import { useTimeZone } from '../../../common/lib/kibana';
|
||||
import { histogramDateTimeFormatter } from '../../../common/components/utils';
|
||||
import { HeaderSection } from '../../../common/components/header_section';
|
||||
import { InspectButton, InspectButtonContainer } from '../../../common/components/inspect';
|
||||
import * as i18n from './translations';
|
||||
import { PreferenceFormattedDate } from '../formatted_date';
|
||||
import { PreferenceFormattedDate } from '../../../common/components/formatted_date';
|
||||
import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
|
||||
import { isUserRiskScore } from '../../../../common/search_strategy';
|
||||
|
|
@ -6,29 +6,23 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { RiskScoreEntity } from '../../../common/search_strategy';
|
||||
|
||||
export const HOST = i18n.translate('xpack.securitySolution.riskScore.overview.hostTitle', {
|
||||
defaultMessage: 'Host',
|
||||
});
|
||||
|
||||
export const HOST_LOWERCASE = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.overview.hostLowercase',
|
||||
{
|
||||
defaultMessage: 'host',
|
||||
}
|
||||
);
|
||||
export const HOSTS = i18n.translate('xpack.securitySolution.riskScore.overview.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
});
|
||||
|
||||
export const USER = i18n.translate('xpack.securitySolution.riskScore.overview.userTitle', {
|
||||
defaultMessage: 'User',
|
||||
});
|
||||
|
||||
export const USER_LOWERCASE = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.overview.userLowercase',
|
||||
{
|
||||
defaultMessage: 'user',
|
||||
}
|
||||
);
|
||||
export const USERS = i18n.translate('xpack.securitySolution.riskScore.overview.users', {
|
||||
defaultMessage: 'Users',
|
||||
});
|
||||
|
||||
export const RISK_SCORE_TITLE = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.riskScore.overview.riskScoreTitle', {
|
||||
|
@ -38,11 +32,21 @@ export const RISK_SCORE_TITLE = (riskEntity: RiskScoreEntity) =>
|
|||
},
|
||||
});
|
||||
|
||||
export const getRiskEntityTranslation = (riskEntity: RiskScoreEntity, lowercase = false) =>
|
||||
lowercase
|
||||
? riskEntity === RiskScoreEntity.host
|
||||
? HOST_LOWERCASE
|
||||
: USER_LOWERCASE
|
||||
: riskEntity === RiskScoreEntity.host
|
||||
? HOST
|
||||
: USER;
|
||||
export const getRiskEntityTranslation = (
|
||||
riskEntity: RiskScoreEntity,
|
||||
lowercase = false,
|
||||
plural = false
|
||||
) => {
|
||||
if (lowercase) {
|
||||
if (plural) {
|
||||
return (riskEntity === RiskScoreEntity.host ? HOSTS : USERS).toLowerCase();
|
||||
}
|
||||
|
||||
return (riskEntity === RiskScoreEntity.host ? HOST : USER).toLowerCase();
|
||||
}
|
||||
if (plural) {
|
||||
return riskEntity === RiskScoreEntity.host ? HOSTS : USERS;
|
||||
}
|
||||
|
||||
return riskEntity === RiskScoreEntity.host ? HOST : USER;
|
||||
};
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useHostRiskScore, useUserRiskScore } from '.';
|
||||
import { useRiskScore } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
import { useSearchStrategy } from '../../../common/containers/use_search_strategy';
|
||||
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import { useAppToastsMock } from '../../../common/hooks/use_app_toasts.mock';
|
||||
import { useRiskScoreFeatureStatus } from '../feature_status';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
|
||||
jest.mock('../../../common/containers/use_search_strategy', () => ({
|
||||
useSearchStrategy: jest.fn(),
|
||||
|
@ -30,36 +31,36 @@ const mockSearch = jest.fn();
|
|||
|
||||
let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
||||
|
||||
[useHostRiskScore, useUserRiskScore].forEach((fn) => {
|
||||
const riskEntity = fn.name === 'useHostRiskScore' ? 'host' : 'user';
|
||||
const defaultFeatureStatus = {
|
||||
isLoading: false,
|
||||
isDeprecated: false,
|
||||
isLicenseValid: true,
|
||||
isEnabled: true,
|
||||
refetch: () => {},
|
||||
};
|
||||
const defaultRisk = {
|
||||
const defaultFeatureStatus = {
|
||||
isLoading: false,
|
||||
isDeprecated: false,
|
||||
isLicenseValid: true,
|
||||
isEnabled: true,
|
||||
refetch: () => {},
|
||||
};
|
||||
const defaultRisk = {
|
||||
data: undefined,
|
||||
inspect: {},
|
||||
isInspected: false,
|
||||
isLicenseValid: true,
|
||||
isModuleEnabled: true,
|
||||
isDeprecated: false,
|
||||
totalCount: 0,
|
||||
};
|
||||
const defaultSearchResponse = {
|
||||
loading: false,
|
||||
result: {
|
||||
data: undefined,
|
||||
inspect: {},
|
||||
isInspected: false,
|
||||
isLicenseValid: true,
|
||||
isModuleEnabled: true,
|
||||
isDeprecated: false,
|
||||
totalCount: 0,
|
||||
};
|
||||
const defaultSearchResponse = {
|
||||
loading: false,
|
||||
result: {
|
||||
data: undefined,
|
||||
totalCount: 0,
|
||||
},
|
||||
search: mockSearch,
|
||||
refetch: () => {},
|
||||
inspect: {},
|
||||
error: undefined,
|
||||
};
|
||||
describe(`${fn.name}`, () => {
|
||||
},
|
||||
search: mockSearch,
|
||||
refetch: () => {},
|
||||
inspect: {},
|
||||
error: undefined,
|
||||
};
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
'useRiskScore entityType: %s',
|
||||
(riskEntity) => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
appToastsMock = useAppToastsMock.create();
|
||||
|
@ -73,18 +74,16 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
...defaultFeatureStatus,
|
||||
isLicenseValid: false,
|
||||
});
|
||||
const { result } = renderHook(() => fn(), {
|
||||
const { result } = renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{
|
||||
...defaultRisk,
|
||||
isLicenseValid: false,
|
||||
refetch: result.current[1].refetch,
|
||||
},
|
||||
]);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
isLicenseValid: false,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
test('does not search if feature is not enabled', () => {
|
||||
mockUseRiskScoreFeatureStatus.mockReturnValue({
|
||||
|
@ -92,18 +91,16 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
isEnabled: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => fn(), {
|
||||
const { result } = renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{
|
||||
...defaultRisk,
|
||||
isModuleEnabled: false,
|
||||
refetch: result.current[1].refetch,
|
||||
},
|
||||
]);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
isModuleEnabled: false,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
|
||||
test('does not search if index is deprecated ', () => {
|
||||
|
@ -111,18 +108,16 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
...defaultFeatureStatus,
|
||||
isDeprecated: true,
|
||||
});
|
||||
const { result } = renderHook(() => fn({ skip: true }), {
|
||||
const { result } = renderHook(() => useRiskScore({ riskEntity, skip: true }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{
|
||||
...defaultRisk,
|
||||
isDeprecated: true,
|
||||
refetch: result.current[1].refetch,
|
||||
},
|
||||
]);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
isDeprecated: true,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
|
||||
test('handle index not found error', () => {
|
||||
|
@ -141,13 +136,15 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
},
|
||||
},
|
||||
});
|
||||
const { result } = renderHook(() => fn(), {
|
||||
const { result } = renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{ ...defaultRisk, isModuleEnabled: false, refetch: result.current[1].refetch },
|
||||
]);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
isModuleEnabled: false,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
|
||||
test('show error toast', () => {
|
||||
|
@ -156,7 +153,7 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
...defaultSearchResponse,
|
||||
error,
|
||||
});
|
||||
renderHook(() => fn(), {
|
||||
renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(appToastsMock.addError).toHaveBeenCalledWith(error, {
|
||||
|
@ -165,7 +162,7 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
});
|
||||
|
||||
test('runs search if feature is enabled and not deprecated', () => {
|
||||
renderHook(() => fn(), {
|
||||
renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockSearch).toHaveBeenCalledWith({
|
||||
|
@ -182,19 +179,17 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
totalCount: 0,
|
||||
},
|
||||
});
|
||||
const { result, waitFor } = renderHook(() => fn(), {
|
||||
const { result, waitFor } = renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{
|
||||
...defaultRisk,
|
||||
data: [],
|
||||
refetch: result.current[1].refetch,
|
||||
},
|
||||
]);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
data: [],
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -26,8 +26,12 @@ import type { inputsModel } from '../../../common/store';
|
|||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { useSearchStrategy } from '../../../common/containers/use_search_strategy';
|
||||
|
||||
export interface RiskScoreState<T extends RiskQueries.hostsRiskScore | RiskQueries.usersRiskScore> {
|
||||
data: undefined | StrategyResponseType<T>['data'];
|
||||
export interface RiskScoreState<T extends RiskScoreEntity.host | RiskScoreEntity.user> {
|
||||
data:
|
||||
| undefined
|
||||
| StrategyResponseType<
|
||||
T extends RiskScoreEntity.host ? RiskQueries.hostsRiskScore : RiskQueries.usersRiskScore
|
||||
>['data'];
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
|
@ -35,6 +39,7 @@ export interface RiskScoreState<T extends RiskQueries.hostsRiskScore | RiskQueri
|
|||
isModuleEnabled: boolean;
|
||||
isLicenseValid: boolean;
|
||||
isDeprecated: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface UseRiskScoreParams {
|
||||
|
@ -63,28 +68,7 @@ export const initialResult: Omit<
|
|||
data: undefined,
|
||||
};
|
||||
|
||||
// use this function instead of directly using useRiskScore
|
||||
// typescript is happy with the type specific hooks
|
||||
export const useHostRiskScore = (
|
||||
params?: UseRiskScoreParams
|
||||
): [boolean, RiskScoreState<RiskQueries.hostsRiskScore>] => {
|
||||
return useRiskScore({
|
||||
...params,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
});
|
||||
};
|
||||
|
||||
// use this function instead of directly using useRiskScore
|
||||
// typescript is happy with the type specific hooks
|
||||
export const useUserRiskScore = (
|
||||
params?: UseRiskScoreParams
|
||||
): [boolean, RiskScoreState<RiskQueries.usersRiskScore>] =>
|
||||
useRiskScore({
|
||||
...params,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
});
|
||||
|
||||
const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.user>({
|
||||
export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.user>({
|
||||
timerange,
|
||||
onlyLatest = true,
|
||||
filterQuery,
|
||||
|
@ -92,10 +76,7 @@ const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.user>({
|
|||
skip = false,
|
||||
pagination,
|
||||
riskEntity,
|
||||
}: UseRiskScore<T>): [
|
||||
boolean,
|
||||
RiskScoreState<RiskQueries.hostsRiskScore | RiskQueries.usersRiskScore>
|
||||
] => {
|
||||
}: UseRiskScore<T>): RiskScoreState<T> => {
|
||||
const spaceId = useSpaceId();
|
||||
const defaultIndex = spaceId
|
||||
? riskEntity === RiskScoreEntity.host
|
||||
|
@ -230,5 +211,5 @@ const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.user>({
|
|||
skip,
|
||||
]);
|
||||
|
||||
return [loading || isDeprecatedLoading, riskScoreResponse];
|
||||
return { ...riskScoreResponse, loading: loading || isDeprecatedLoading };
|
||||
};
|
||||
|
|
|
@ -36,71 +36,27 @@ interface RiskScoreKpi {
|
|||
timerange?: { to: string; from: string };
|
||||
}
|
||||
|
||||
type UseHostRiskScoreKpiProps = Omit<
|
||||
UseRiskScoreKpiProps,
|
||||
'defaultIndex' | 'aggBy' | 'featureEnabled' | 'entity'
|
||||
>;
|
||||
type UseUserRiskScoreKpiProps = Omit<
|
||||
UseRiskScoreKpiProps,
|
||||
'defaultIndex' | 'aggBy' | 'featureEnabled' | 'entity'
|
||||
>;
|
||||
|
||||
export const useUserRiskScoreKpi = ({
|
||||
filterQuery,
|
||||
skip,
|
||||
timerange,
|
||||
}: UseUserRiskScoreKpiProps): RiskScoreKpi => {
|
||||
const spaceId = useSpaceId();
|
||||
const defaultIndex = spaceId ? getUserRiskIndex(spaceId) : undefined;
|
||||
const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
|
||||
return useRiskScoreKpi({
|
||||
filterQuery,
|
||||
skip,
|
||||
defaultIndex,
|
||||
entity: RiskScoreEntity.user,
|
||||
featureEnabled: isPlatinumOrTrialLicense,
|
||||
timerange,
|
||||
});
|
||||
};
|
||||
|
||||
export const useHostRiskScoreKpi = ({
|
||||
filterQuery,
|
||||
skip,
|
||||
timerange,
|
||||
}: UseHostRiskScoreKpiProps): RiskScoreKpi => {
|
||||
const spaceId = useSpaceId();
|
||||
const defaultIndex = spaceId ? getHostRiskIndex(spaceId) : undefined;
|
||||
const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
|
||||
return useRiskScoreKpi({
|
||||
filterQuery,
|
||||
skip,
|
||||
defaultIndex,
|
||||
entity: RiskScoreEntity.host,
|
||||
featureEnabled: isPlatinumOrTrialLicense,
|
||||
timerange,
|
||||
});
|
||||
};
|
||||
|
||||
interface UseRiskScoreKpiProps {
|
||||
filterQuery?: string | ESTermQuery;
|
||||
skip?: boolean;
|
||||
defaultIndex: string | undefined;
|
||||
entity: RiskScoreEntity;
|
||||
featureEnabled: boolean;
|
||||
riskEntity: RiskScoreEntity;
|
||||
timerange?: { to: string; from: string };
|
||||
}
|
||||
|
||||
const useRiskScoreKpi = ({
|
||||
export const useRiskScoreKpi = ({
|
||||
filterQuery,
|
||||
skip,
|
||||
defaultIndex,
|
||||
entity,
|
||||
featureEnabled,
|
||||
riskEntity,
|
||||
timerange,
|
||||
}: UseRiskScoreKpiProps): RiskScoreKpi => {
|
||||
const { addError } = useAppToasts();
|
||||
const spaceId = useSpaceId();
|
||||
const featureEnabled = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
const defaultIndex = spaceId
|
||||
? riskEntity === RiskScoreEntity.host
|
||||
? getHostRiskIndex(spaceId)
|
||||
: getUserRiskIndex(spaceId)
|
||||
: undefined;
|
||||
|
||||
const { loading, result, search, refetch, inspect, error } =
|
||||
useSearchStrategy<RiskQueries.kpiRiskScore>({
|
||||
|
@ -119,10 +75,10 @@ const useRiskScoreKpi = ({
|
|||
search({
|
||||
filterQuery,
|
||||
defaultIndex: [defaultIndex],
|
||||
entity,
|
||||
entity: riskEntity,
|
||||
});
|
||||
}
|
||||
}, [defaultIndex, search, filterQuery, skip, entity, featureEnabled]);
|
||||
}, [defaultIndex, search, filterQuery, skip, riskEntity, featureEnabled]);
|
||||
|
||||
// since query does not take timerange arg, we need to manually refetch when time range updates
|
||||
useEffect(() => {
|
||||
|
|
|
@ -14,16 +14,14 @@ import type {
|
|||
} from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE, RISKY_USERS_DASHBOARD_TITLE } from '../../../constants';
|
||||
import {
|
||||
prebuiltSavedObjectsBulkCreateUrl,
|
||||
prebuiltSavedObjectsBulkDeleteUrl,
|
||||
} from '../../../../../common/constants';
|
||||
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import {
|
||||
RISKY_HOSTS_DASHBOARD_TITLE,
|
||||
RISKY_USERS_DASHBOARD_TITLE,
|
||||
} from '../../../../hosts/pages/navigation/constants';
|
||||
|
||||
import {
|
||||
DELETE_SAVED_OBJECTS_FAILURE,
|
||||
IMPORT_SAVED_OBJECTS_FAILURE,
|
||||
|
|
|
@ -96,20 +96,11 @@ jest.mock(
|
|||
jest.mock('../../../../detections/components/alerts_table/actions');
|
||||
jest.mock('../../../../risk_score/containers', () => {
|
||||
return {
|
||||
useHostRiskScore: jest.fn().mockReturnValue([
|
||||
true,
|
||||
{
|
||||
data: undefined,
|
||||
isModuleEnabled: false,
|
||||
},
|
||||
]),
|
||||
useUserRiskScore: jest.fn().mockReturnValue([
|
||||
true,
|
||||
{
|
||||
data: undefined,
|
||||
isModuleEnabled: false,
|
||||
},
|
||||
]),
|
||||
useRiskScore: jest.fn().mockReturnValue({
|
||||
loading: true,
|
||||
data: undefined,
|
||||
isModuleEnabled: false,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -132,7 +123,7 @@ jest.mock('../../../containers/details', () => {
|
|||
};
|
||||
});
|
||||
|
||||
describe('event details footer component', () => {
|
||||
describe('event details panel component', () => {
|
||||
beforeEach(() => {
|
||||
const coreStartMock = coreMock.createStart();
|
||||
(KibanaServices.get as jest.Mock).mockReturnValue(coreStartMock);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { UsersKpiAuthentications } from './authentications';
|
|||
import { TotalUsersKpi } from './total_users';
|
||||
import { CallOutSwitcher } from '../../../common/components/callouts';
|
||||
import * as i18n from './translations';
|
||||
import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link';
|
||||
import { RiskScoreDocLink } from '../../../risk_score/components/risk_score_onboarding/risk_score_doc_link';
|
||||
import { getUserRiskIndex, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { useRiskScoreFeatureStatus } from '../../../risk_score/containers/feature_status';
|
||||
|
|
|
@ -1,35 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { UserRiskInformationButtonEmpty } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
describe('User Risk Flyout', () => {
|
||||
describe('UserRiskInformationButtonEmpty', () => {
|
||||
it('renders', () => {
|
||||
const { queryByTestId } = render(<UserRiskInformationButtonEmpty />);
|
||||
|
||||
expect(queryByTestId('open-risk-information-flyout-trigger')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('opens and displays table with 5 rows', () => {
|
||||
const NUMBER_OF_ROWS = 1 + 5; // 1 header row + 5 severity rows
|
||||
const { getByTestId, queryByTestId, queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<UserRiskInformationButtonEmpty />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('open-risk-information-flyout-trigger'));
|
||||
|
||||
expect(queryByTestId('risk-information-table')).toBeInTheDocument();
|
||||
expect(queryAllByRole('row')).toHaveLength(NUMBER_OF_ROWS);
|
||||
});
|
||||
});
|
|
@ -1,133 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import {
|
||||
useGeneratedHtmlId,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiBasicTable,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutFooter,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { useOnOpenCloseHandler } from '../../../helper_hooks';
|
||||
import { RiskScore } from '../../../common/components/severity/common';
|
||||
import { RiskScoreEntity, RiskSeverity } from '../../../../common/search_strategy';
|
||||
import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link';
|
||||
|
||||
const tableColumns: Array<EuiBasicTableColumn<TableItem>> = [
|
||||
{
|
||||
field: 'classification',
|
||||
name: i18n.INFORMATION_CLASSIFICATION_HEADER,
|
||||
render: (riskScore?: RiskSeverity) => {
|
||||
if (riskScore != null) {
|
||||
return <RiskScore severity={riskScore} hideBackgroundColor />;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'range',
|
||||
name: i18n.INFORMATION_RISK_HEADER,
|
||||
},
|
||||
];
|
||||
|
||||
interface TableItem {
|
||||
range?: string;
|
||||
classification: RiskSeverity;
|
||||
}
|
||||
|
||||
const tableItems: TableItem[] = [
|
||||
{ classification: RiskSeverity.critical, range: i18n.CRITICAL_RISK_DESCRIPTION },
|
||||
{ classification: RiskSeverity.high, range: '70 - 90 ' },
|
||||
{ classification: RiskSeverity.moderate, range: '40 - 70' },
|
||||
{ classification: RiskSeverity.low, range: '20 - 40' },
|
||||
{ classification: RiskSeverity.unknown, range: i18n.UNKNOWN_RISK_DESCRIPTION },
|
||||
];
|
||||
|
||||
export const UserRiskInformationButtonEmpty = () => {
|
||||
const [isFlyoutVisible, handleOnOpen, handleOnClose] = useOnOpenCloseHandler();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonEmpty onClick={handleOnOpen} data-test-subj="open-risk-information-flyout-trigger">
|
||||
{i18n.INFO_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
{isFlyoutVisible && <UserRiskInformationFlyout handleOnClose={handleOnClose} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UserRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => void }) => {
|
||||
const simpleFlyoutTitleId = useGeneratedHtmlId({
|
||||
prefix: 'UserRiskInformation',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
ownFocus
|
||||
onClose={handleOnClose}
|
||||
aria-labelledby={simpleFlyoutTitleId}
|
||||
size={450}
|
||||
data-test-subj="open-risk-information-flyout"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={simpleFlyoutTitleId}>{i18n.TITLE}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText size="s">
|
||||
<p>{i18n.INTRODUCTION}</p>
|
||||
<p>{i18n.EXPLANATION_MESSAGE}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiBasicTable
|
||||
columns={tableColumns}
|
||||
items={tableItems}
|
||||
data-test-subj="risk-information-table"
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.users.userRiskInformation.learnMore"
|
||||
defaultMessage="You can learn more about user risk {UserRiskScoreDocumentationLink}"
|
||||
values={{
|
||||
UserRiskScoreDocumentationLink: (
|
||||
<RiskScoreDocLink
|
||||
riskScoreEntity={RiskScoreEntity.user}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.users.userRiskInformation.link"
|
||||
defaultMessage="here"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={handleOnClose}>{i18n.CLOSE_BUTTON_LTEXT}</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -1,70 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const INFORMATION_CLASSIFICATION_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.classificationHeader',
|
||||
{
|
||||
defaultMessage: 'Classification',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_RISK_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.riskHeader',
|
||||
{
|
||||
defaultMessage: 'User risk score range',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNKNOWN_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.unknownRiskDescription',
|
||||
{
|
||||
defaultMessage: 'Less than 20',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRITICAL_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.criticalRiskDescription',
|
||||
{
|
||||
defaultMessage: '90 and above',
|
||||
}
|
||||
);
|
||||
|
||||
export const TITLE = i18n.translate('xpack.securitySolution.users.userRiskInformation.title', {
|
||||
defaultMessage: 'How is user risk calculated?',
|
||||
});
|
||||
|
||||
export const INTRODUCTION = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.introduction',
|
||||
{
|
||||
defaultMessage:
|
||||
'The User Risk Score capability surfaces risky users from within your environment.',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPLANATION_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'This feature utilizes a transform, with a scripted metric aggregation to calculate user risk scores based on detection rule alerts with an "open" status, within a 5 day time window. The transform runs hourly to keep the score updated as new detection rule alerts stream in.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOSE_BUTTON_LTEXT = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.closeBtn',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFO_BUTTON_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.users.userRiskInformation.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'How is risk score calculated?',
|
||||
}
|
||||
);
|
|
@ -9,6 +9,8 @@ import React, { useMemo } from 'react';
|
|||
import { Switch } from 'react-router-dom';
|
||||
import { Route } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { RiskDetailsTabBody } from '../../../risk_score/components/risk_details_tab_body';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { UsersTableType } from '../../store/model';
|
||||
import { AnomaliesUserTable } from '../../../common/components/ml/tables/anomalies_user_table';
|
||||
import type { UsersDetailsTabsProps } from './types';
|
||||
|
@ -18,7 +20,6 @@ import { TimelineId } from '../../../../common/types';
|
|||
import { EventsQueryTabBody } from '../../../common/components/events_tab';
|
||||
import { userNameExistsFilter } from './helpers';
|
||||
import { AuthenticationsQueryTabBody } from '../navigation';
|
||||
import { UserRiskTabBody } from '../navigation/user_risk_tab_body';
|
||||
|
||||
export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>(
|
||||
({
|
||||
|
@ -67,7 +68,11 @@ export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>(
|
|||
/>
|
||||
</Route>
|
||||
<Route path={`${usersDetailsPagePath}/:tabName(${UsersTableType.risk})`}>
|
||||
<UserRiskTabBody {...tabProps} />
|
||||
<RiskDetailsTabBody
|
||||
{...tabProps}
|
||||
riskEntity={RiskScoreEntity.user}
|
||||
entityName={tabProps.userName}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useUserRiskScore, useUserRiskScoreKpi } from '../../../risk_score/containers';
|
||||
import { useRiskScore, useRiskScoreKpi } from '../../../risk_score/containers';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { UserRiskScoreQueryTabBody } from './user_risk_score_tab_body';
|
||||
import { UsersType } from '../../store/model';
|
||||
|
@ -18,8 +18,8 @@ jest.mock('../../../common/containers/query_toggle');
|
|||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
describe('All users query tab body', () => {
|
||||
const mockUseUserRiskScore = useUserRiskScore as jest.Mock;
|
||||
const mockUseUserRiskScoreKpi = useUserRiskScoreKpi as jest.Mock;
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
const mockUseRiskScoreKpi = useRiskScoreKpi as jest.Mock;
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const defaultProps = {
|
||||
indexNames: [],
|
||||
|
@ -34,20 +34,18 @@ describe('All users query tab body', () => {
|
|||
jest.clearAllMocks();
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
|
||||
mockUseUserRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
totalCount: 0,
|
||||
refetch: jest.fn(),
|
||||
isModuleEnabled: true,
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
loading: false,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
]);
|
||||
mockUseUserRiskScoreKpi.mockReturnValue({
|
||||
isInspected: false,
|
||||
totalCount: 0,
|
||||
refetch: jest.fn(),
|
||||
isModuleEnabled: true,
|
||||
});
|
||||
mockUseRiskScoreKpi.mockReturnValue({
|
||||
loading: false,
|
||||
severityCount: {
|
||||
unknown: 12,
|
||||
|
@ -65,8 +63,8 @@ describe('All users query tab body', () => {
|
|||
<UserRiskScoreQueryTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseUserRiskScoreKpi.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
expect(mockUseRiskScoreKpi.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it('toggleStatus=false, skip', () => {
|
||||
|
@ -76,7 +74,7 @@ describe('All users query tab body', () => {
|
|||
<UserRiskScoreQueryTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseUserRiskScoreKpi.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
expect(mockUseRiskScoreKpi.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { noop } from 'lodash/fp';
|
||||
|
||||
import { EnableRiskScore } from '../../../risk_score/components/enable_risk_score';
|
||||
import { useGlobalTime } from '../../../common/containers/use_global_time';
|
||||
import { RiskScoresDeprecated } from '../../../common/components/risk_score/risk_score_deprecated';
|
||||
import type { UsersComponentsQueryProps } from './types';
|
||||
import { manageQuery } from '../../../common/components/page/manage_query';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
|
@ -19,13 +19,12 @@ import { UserRiskScoreTable } from '../../components/user_risk_score_table';
|
|||
import { usersSelectors } from '../../store';
|
||||
import {
|
||||
UserRiskScoreQueryId,
|
||||
useUserRiskScore,
|
||||
useUserRiskScoreKpi,
|
||||
useRiskScore,
|
||||
useRiskScoreKpi,
|
||||
} from '../../../risk_score/containers';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { EntityAnalyticsUserRiskScoreDisable } from '../../../common/components/risk_score/risk_score_disabled/user_risk_score.disabled';
|
||||
import { RiskScoresNoDataDetected } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { RiskScoresNoDataDetected } from '../../../risk_score/components/risk_score_onboarding/risk_score_no_data_detected';
|
||||
|
||||
const UserRiskScoreTableManage = manageQuery(UserRiskScoreTable);
|
||||
|
||||
|
@ -64,29 +63,39 @@ export const UserRiskScoreQueryTabBody = ({
|
|||
|
||||
const timerange = useMemo(() => ({ from, to }), [from, to]);
|
||||
|
||||
const [
|
||||
const {
|
||||
data,
|
||||
inspect,
|
||||
isDeprecated,
|
||||
isInspected,
|
||||
isModuleEnabled,
|
||||
loading,
|
||||
{ data, totalCount, inspect, isInspected, isDeprecated, refetch, isModuleEnabled },
|
||||
] = useUserRiskScore({
|
||||
refetch,
|
||||
totalCount,
|
||||
} = useRiskScore({
|
||||
filterQuery,
|
||||
skip: querySkip,
|
||||
pagination,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
skip: querySkip,
|
||||
sort,
|
||||
timerange,
|
||||
});
|
||||
|
||||
const { severityCount, loading: isKpiLoading } = useUserRiskScoreKpi({
|
||||
const { severityCount, loading: isKpiLoading } = useRiskScoreKpi({
|
||||
filterQuery,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
skip: querySkip,
|
||||
});
|
||||
|
||||
if (!isModuleEnabled && !loading) {
|
||||
return <EntityAnalyticsUserRiskScoreDisable refetch={refetch} timerange={timerange} />;
|
||||
}
|
||||
const status = {
|
||||
isDisabled: !isModuleEnabled && !loading,
|
||||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (isDeprecated && !loading) {
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
return (
|
||||
<RiskScoresDeprecated
|
||||
<EnableRiskScore
|
||||
{...status}
|
||||
entityType={RiskScoreEntity.user}
|
||||
refetch={refetch}
|
||||
timerange={timerange}
|
||||
|
|
|
@ -1,86 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { UsersType } from '../../store/model';
|
||||
import { useUserRiskScore } from '../../../risk_score/containers';
|
||||
import { UserRiskTabBody } from './user_risk_tab_body';
|
||||
|
||||
jest.mock('../../../risk_score/containers');
|
||||
jest.mock('../../../common/containers/query_toggle');
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
describe('User query tab body', () => {
|
||||
const mockUseUserRiskScore = useUserRiskScore as jest.Mock;
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const defaultProps = {
|
||||
userName: 'testUser',
|
||||
indexNames: [],
|
||||
setQuery: jest.fn(),
|
||||
skip: false,
|
||||
startDate: '2019-06-25T04:31:59.345Z',
|
||||
endDate: '2019-06-25T06:31:59.345Z',
|
||||
type: UsersType.page,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseUserRiskScore.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
totalCount: 0,
|
||||
refetch: jest.fn(),
|
||||
isModuleEnabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("doesn't skip when both toggleStatus are true", () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<UserRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't skip when at least one toggleStatus is true", () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<UserRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
it('does skip when at both toggleStatus are false', () => {
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
mockUseQueryToggle.mockReturnValueOnce({ toggleStatus: false, setToggleStatus: jest.fn() });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<UserRiskTabBody {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(mockUseUserRiskScore.mock.calls[0][0].skip).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -1,165 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { RiskScoresDeprecated } from '../../../common/components/risk_score/risk_score_deprecated';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
import { useQueryInspector } from '../../../common/components/page/manage_query';
|
||||
import { RiskScoreOverTime } from '../../../common/components/risk_score_over_time';
|
||||
import { TopRiskScoreContributors } from '../../../common/components/top_risk_score_contributors';
|
||||
import { useQueryToggle } from '../../../common/containers/query_toggle';
|
||||
import { UserRiskScoreQueryId, useUserRiskScore } from '../../../risk_score/containers';
|
||||
import type { UserRiskScore } from '../../../../common/search_strategy';
|
||||
import { RiskScoreEntity, buildUserNamesFilter } from '../../../../common/search_strategy';
|
||||
import type { UsersComponentsQueryProps } from './types';
|
||||
import { UserRiskInformationButtonEmpty } from '../../components/user_risk_information';
|
||||
import { useDashboardButtonHref } from '../../../common/hooks/use_dashboard_button_href';
|
||||
import { EntityAnalyticsUserRiskScoreDisable } from '../../../common/components/risk_score/risk_score_disabled/user_risk_score.disabled';
|
||||
import { RiskScoresNoDataDetected } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
|
||||
const QUERY_ID = UserRiskScoreQueryId.USER_DETAILS_RISK_SCORE;
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeL};
|
||||
`;
|
||||
|
||||
const RISKY_USERS_DASHBOARD_TITLE = 'Current Risk Score For Users';
|
||||
|
||||
const UserRiskTabBodyComponent: React.FC<
|
||||
Pick<UsersComponentsQueryProps, 'startDate' | 'endDate' | 'setQuery' | 'deleteQuery'> & {
|
||||
userName: string;
|
||||
}
|
||||
> = ({ userName, startDate, endDate, setQuery, deleteQuery }) => {
|
||||
const { buttonHref } = useDashboardButtonHref({
|
||||
to: endDate,
|
||||
from: startDate,
|
||||
title: RISKY_USERS_DASHBOARD_TITLE,
|
||||
});
|
||||
|
||||
const timerange = useMemo(
|
||||
() => ({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
}),
|
||||
[startDate, endDate]
|
||||
);
|
||||
|
||||
const { toggleStatus: overTimeToggleStatus, setToggleStatus: setOverTimeToggleStatus } =
|
||||
useQueryToggle(`${QUERY_ID} overTime`);
|
||||
const { toggleStatus: contributorsToggleStatus, setToggleStatus: setContributorsToggleStatus } =
|
||||
useQueryToggle(`${QUERY_ID} contributors`);
|
||||
const filterQuery = useMemo(
|
||||
() => (userName ? buildUserNamesFilter([userName]) : undefined),
|
||||
[userName]
|
||||
);
|
||||
const [loading, { data, refetch, inspect, isDeprecated, isModuleEnabled }] = useUserRiskScore({
|
||||
filterQuery,
|
||||
onlyLatest: false,
|
||||
skip: !overTimeToggleStatus && !contributorsToggleStatus,
|
||||
timerange,
|
||||
});
|
||||
|
||||
useQueryInspector({
|
||||
queryId: QUERY_ID,
|
||||
loading,
|
||||
refetch,
|
||||
setQuery,
|
||||
deleteQuery,
|
||||
inspect,
|
||||
});
|
||||
|
||||
const toggleContributorsQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setContributorsToggleStatus(status);
|
||||
},
|
||||
[setContributorsToggleStatus]
|
||||
);
|
||||
|
||||
const toggleOverTimeQuery = useCallback(
|
||||
(status: boolean) => {
|
||||
setOverTimeToggleStatus(status);
|
||||
},
|
||||
[setOverTimeToggleStatus]
|
||||
);
|
||||
|
||||
if (!isModuleEnabled && !loading) {
|
||||
return <EntityAnalyticsUserRiskScoreDisable refetch={refetch} timerange={timerange} />;
|
||||
}
|
||||
|
||||
if (isDeprecated && !loading) {
|
||||
return (
|
||||
<RiskScoresDeprecated
|
||||
entityType={RiskScoreEntity.user}
|
||||
refetch={refetch}
|
||||
timerange={timerange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isModuleEnabled && data && data.length === 0) {
|
||||
return <RiskScoresNoDataDetected entityType={RiskScoreEntity.user} refetch={refetch} />;
|
||||
}
|
||||
|
||||
const lastUsertRiskItem: UserRiskScore | null =
|
||||
data && data.length > 0 ? data[data.length - 1] : null;
|
||||
const rules = lastUsertRiskItem ? lastUsertRiskItem.user.risk.rule_risks : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={2}>
|
||||
<RiskScoreOverTime
|
||||
from={startDate}
|
||||
to={endDate}
|
||||
loading={loading}
|
||||
riskScore={data}
|
||||
queryId={QUERY_ID}
|
||||
title={i18n.USER_RISK_SCORE_OVER_TIME}
|
||||
toggleStatus={overTimeToggleStatus}
|
||||
toggleQuery={toggleOverTimeQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<TopRiskScoreContributors
|
||||
loading={loading}
|
||||
queryId={QUERY_ID}
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
rules={rules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<StyledEuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={buttonHref}
|
||||
isDisabled={!buttonHref}
|
||||
data-test-subj="risky-users-view-dashboard-button"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.VIEW_DASHBOARD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<UserRiskInformationButtonEmpty />
|
||||
</EuiFlexItem>
|
||||
</StyledEuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
UserRiskTabBodyComponent.displayName = 'UserRiskTabBodyComponent';
|
||||
|
||||
export const UserRiskTabBody = React.memo(UserRiskTabBodyComponent);
|
||||
|
||||
UserRiskTabBody.displayName = 'UserRiskTabBody';
|
|
@ -1909,6 +1909,11 @@
|
|||
"dataViews.functions.dataViewLoad.id.help": "ID de vue de données à charger",
|
||||
"dataViews.indexPatternLoad.error.kibanaRequest": "Une requête Kibana est nécessaire pour exécuter cette recherche sur le serveur. Veuillez fournir un objet de requête pour les paramètres d'exécution de l'expression.",
|
||||
"dataViews.unableWriteLabel": "Impossible d'écrire la vue de données ! Actualisez la page pour obtenir la dernière version de cette vue de données.",
|
||||
"dataViews.aliasLabel": "Alias",
|
||||
"dataViews.dataStreamLabel": "Flux de données",
|
||||
"dataViews.frozenLabel": "Frozen",
|
||||
"dataViews.indexLabel": "Index",
|
||||
"dataViews.rollupLabel": "Cumul",
|
||||
"discover.advancedSettings.disableDocumentExplorerDescription": "Désactivez cette option pour utiliser le nouveau {documentExplorerDocs} au lieu de la vue classique. l'explorateur de documents offre un meilleur tri des données, des colonnes redimensionnables et une vue en plein écran.",
|
||||
"discover.advancedSettings.discover.showFieldStatisticsDescription": "Activez le {fieldStatisticsDocs} pour afficher des détails tels que les valeurs minimale et maximale d'un champ numérique ou une carte d'un champ géographique. Cette fonctionnalité est en version bêta et susceptible d'être modifiée.",
|
||||
"discover.advancedSettings.discover.showMultifieldsDescription": "Détermine si les {multiFields} doivent s'afficher dans la fenêtre de document étendue. Dans la plupart des cas, les champs multiples sont les mêmes que les champs d'origine. Cette option est uniquement disponible lorsque le paramètre ''searchFieldsFromSource'' est désactivé.",
|
||||
|
@ -3718,10 +3723,8 @@
|
|||
"indexPatternEditor.status.partialMatchLabel.partialMatchDetail": "Votre modèle d'indexation ne correspond à aucun flux de données, index ni alias d'index, mais {strongIndices} {matchedIndicesLength, plural, one {est semblable} other {sont semblables} }.",
|
||||
"indexPatternEditor.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural, one {source} other {# sources} }",
|
||||
"indexPatternEditor.status.successLabel.successDetail": "Votre modèle d'indexation correspond à {sourceCount} {sourceCount, plural, one {source} other {sources} }.",
|
||||
"dataViews.aliasLabel": "Alias",
|
||||
"indexPatternEditor.createIndex.noMatch": "Le nom doit correspondre à au moins un flux de données, index ou alias d'index.",
|
||||
"indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- Je ne souhaite pas utiliser le filtre temporel ---",
|
||||
"dataViews.dataStreamLabel": "Flux de données",
|
||||
"indexPatternEditor.dataView.unableSaveLabel": "Échec de l'enregistrement de la vue de données.",
|
||||
"indexPatternEditor.dataViewExists.ValidationErrorMessage": "Une vue de données de ce nom existe déjà.",
|
||||
"indexPatternEditor.editDataView.editConfirmationModal.confirmButton": "Confirmer",
|
||||
|
@ -3752,8 +3755,6 @@
|
|||
"indexPatternEditor.form.customIndexPatternIdLabel": "ID de vue de données personnalisé",
|
||||
"indexPatternEditor.form.nameAriaLabel": "Champ de nom facultatif",
|
||||
"indexPatternEditor.form.titleAriaLabel": "Champ de modèle d'indexation",
|
||||
"dataViews.frozenLabel": "Frozen",
|
||||
"dataViews.indexLabel": "Index",
|
||||
"indexPatternEditor.loadingHeader": "Recherche d'index correspondants…",
|
||||
"indexPatternEditor.requireTimestampOption.ValidationErrorMessage": "Sélectionnez un champ d'horodatage.",
|
||||
"indexPatternEditor.rollupDataView.createIndex.noMatchError": "Erreur de vue de données de cumul : doit correspondre à un index de cumul",
|
||||
|
@ -3761,7 +3762,6 @@
|
|||
"indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibana propose un support bêta pour les vues de données basées sur les cumuls. Vous pourriez rencontrer des problèmes lors de l'utilisation de ces vues dans les recherches enregistrées, les visualisations et les tableaux de bord. Ils ne sont pas compatibles avec certaines fonctionnalités avancées, telles que Timelion et le Machine Learning.",
|
||||
"indexPatternEditor.rollupDataView.warning.textParagraphTwo": "Vous pouvez mettre une vue de données de cumul en correspondance avec un index de cumul et zéro index régulier ou plus. Une vue de données de cumul dispose d'indicateurs, de champs, d'intervalles et d'agrégations limités. Un index de cumul se limite aux index disposant d'une configuration de tâche ou de plusieurs tâches avec des configurations compatibles.",
|
||||
"indexPatternEditor.rollupIndexPattern.warning.title": "Fonctionnalité bêta",
|
||||
"dataViews.rollupLabel": "Cumul",
|
||||
"indexPatternEditor.saved": "'{indexPatternName}' enregistré",
|
||||
"indexPatternEditor.status.noSystemIndicesLabel": "Aucun flux de données, index ni alias d'index ne correspond à votre modèle d'indexation.",
|
||||
"indexPatternEditor.status.noSystemIndicesWithPromptLabel": "Aucun flux de données, index ni alias d'index ne correspond à votre modèle d'indexation.",
|
||||
|
@ -28423,17 +28423,6 @@
|
|||
"xpack.securitySolution.hostIsolationExceptions.pageAddButtonTitle": "Ajouter l'exception d'isolation de l'hôte",
|
||||
"xpack.securitySolution.hostIsolationExceptions.pageTitle": "Exceptions d'isolation de l'hôte",
|
||||
"xpack.securitySolution.hostIsolationExceptions.searchPlaceholderInfo": "Rechercher sur les champs ci-dessous : nom, description, IP",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.buttonLabel": "Comment le score de risque est-il calculé ?",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.classificationHeader": "Classification",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.closeBtn": "Fermer",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.criticalRiskDescription": "90 et supérieur",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.explanation": "Cette fonctionnalité utilise une transformation, avec une agrégation scriptée personnalisée pour calculer les scores de risque de l'hôte en fonction des alertes de règle de détection ayant le statut \"ouvert\", sur une fenêtre temporelle de 5 jours. La transformation s'exécute toutes les heures afin que le score reste à jour au moment où de nouvelles alertes de règles de détection sont transmises.",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.informationAriaLabel": "Informations",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.introduction": "La fonctionnalité de score de risque de l'hôte détecte les hôtes à risque depuis l'intérieur de votre environnement.",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.link": "ici",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.riskHeader": "Plage de scores de risque de l'hôte",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.title": "Comment le risque de l'hôte est-il calculé ?",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.unknownRiskDescription": "Inférieur à 20",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskScore": "Score de risque",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyLabel": "À risque",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyThresholdHeader": "Seuil du niveau À risque",
|
||||
|
@ -29326,16 +29315,6 @@
|
|||
"xpack.securitySolution.users.navigation.riskTitle": "Risque de l'utilisateur",
|
||||
"xpack.securitySolution.users.navigation.userScoreOverTimeTitle": "Score de risque de l'utilisateur sur la durée",
|
||||
"xpack.securitySolution.users.pageTitle": "Utilisateurs",
|
||||
"xpack.securitySolution.users.userRiskInformation.buttonLabel": "Comment le score de risque est-il calculé ?",
|
||||
"xpack.securitySolution.users.userRiskInformation.classificationHeader": "Classification",
|
||||
"xpack.securitySolution.users.userRiskInformation.closeBtn": "Fermer",
|
||||
"xpack.securitySolution.users.userRiskInformation.criticalRiskDescription": "90 et supérieur",
|
||||
"xpack.securitySolution.users.userRiskInformation.explanation": "Cette fonctionnalité utilise une transformation, avec une agrégation d'indicateurs scriptée pour calculer les scores de risque de l'utilisateur en fonction des alertes de règle de détection ayant le statut \"ouvert\", sur une fenêtre temporelle de 5 jours. La transformation s'exécute toutes les heures afin que le score reste à jour au moment où de nouvelles alertes de règles de détection sont transmises.",
|
||||
"xpack.securitySolution.users.userRiskInformation.introduction": "La fonctionnalité de score de risque de l'utilisateur détecte les utilisateurs à risque depuis l'intérieur de votre environnement.",
|
||||
"xpack.securitySolution.users.userRiskInformation.link": "ici",
|
||||
"xpack.securitySolution.users.userRiskInformation.riskHeader": "Plage de scores de risque de l'utilisateur",
|
||||
"xpack.securitySolution.users.userRiskInformation.title": "Comment le risque de l'utilisateur est-il calculé ?",
|
||||
"xpack.securitySolution.users.userRiskInformation.unknownRiskDescription": "Inférieur à 20",
|
||||
"xpack.securitySolution.usersKpiAuthentications.errorSearchDescription": "Une erreur s'est produite lors de la recherche d'authentifications du KPI des utilisateurs",
|
||||
"xpack.securitySolution.usersKpiAuthentications.failSearchDescription": "Impossible de lancer une recherche sur les authentifications du KPI des utilisateurs",
|
||||
"xpack.securitySolution.usersRiskTable.riskTitle": "Classification de risque de l'utilisateur",
|
||||
|
|
|
@ -1907,6 +1907,11 @@
|
|||
"dataViews.functions.dataViewLoad.id.help": "読み込むデータビューID",
|
||||
"dataViews.indexPatternLoad.error.kibanaRequest": "サーバーでこの検索を実行するには、KibanaRequest が必要です。式実行パラメーターに要求オブジェクトを渡してください。",
|
||||
"dataViews.unableWriteLabel": "データビューを書き込めません。このデータビューへの最新の変更を取得するには、ページを更新してください。",
|
||||
"dataViews.aliasLabel": "エイリアス",
|
||||
"dataViews.dataStreamLabel": "データストリーム",
|
||||
"dataViews.frozenLabel": "凍結",
|
||||
"dataViews.indexLabel": "インデックス",
|
||||
"dataViews.rollupLabel": "ロールアップ",
|
||||
"discover.advancedSettings.disableDocumentExplorerDescription": "クラシックビューではなく、新しい{documentExplorerDocs}を使用するには、このオプションをオフにします。ドキュメントエクスプローラーでは、データの並べ替え、列のサイズ変更、全画面表示といった優れた機能を使用できます。",
|
||||
"discover.advancedSettings.discover.showFieldStatisticsDescription": "{fieldStatisticsDocs}を有効にすると、数値フィールドの最大/最小値やジオフィールドの地図といった詳細が表示されます。この機能はベータ段階で、変更される可能性があります。",
|
||||
"discover.advancedSettings.discover.showMultifieldsDescription": "拡張ドキュメントビューに{multiFields}が表示されるかどうかを制御します。ほとんどの場合、マルチフィールドは元のフィールドと同じです。「searchFieldsFromSource」がオフのときにのみこのオプションを使用できます。",
|
||||
|
@ -3713,10 +3718,8 @@
|
|||
"indexPatternEditor.status.partialMatchLabel.partialMatchDetail": "インデックスパターンはどのデータストリーム、インデックス、インデックスエイリアスとも一致しませんが、{strongIndices} {matchedIndicesLength, plural, other {が} }類似しています。",
|
||||
"indexPatternEditor.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural, other {# ソース} }",
|
||||
"indexPatternEditor.status.successLabel.successDetail": "インデックスパターンは、{sourceCount} {sourceCount, plural, other {ソース} }と一致します。",
|
||||
"dataViews.aliasLabel": "エイリアス",
|
||||
"indexPatternEditor.createIndex.noMatch": "名前は1つ以上のデータストリーム、インデックス、またはインデックスエイリアスと一致する必要があります。",
|
||||
"indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- 時間フィルターを使用しない ---",
|
||||
"dataViews.dataStreamLabel": "データストリーム",
|
||||
"indexPatternEditor.dataView.unableSaveLabel": "データビューの保存に失敗しました。",
|
||||
"indexPatternEditor.dataViewExists.ValidationErrorMessage": "この名前のデータビューはすでに存在します。",
|
||||
"indexPatternEditor.editDataView.editConfirmationModal.confirmButton": "確認",
|
||||
|
@ -3747,8 +3750,6 @@
|
|||
"indexPatternEditor.form.customIndexPatternIdLabel": "カスタムデータビューID",
|
||||
"indexPatternEditor.form.nameAriaLabel": "名前フィールド(任意)",
|
||||
"indexPatternEditor.form.titleAriaLabel": "インデックスパターンフィールド",
|
||||
"dataViews.frozenLabel": "凍結",
|
||||
"dataViews.indexLabel": "インデックス",
|
||||
"indexPatternEditor.loadingHeader": "一致するインデックスを検索中…",
|
||||
"indexPatternEditor.requireTimestampOption.ValidationErrorMessage": "タイムスタンプフィールドを選択します。",
|
||||
"indexPatternEditor.rollupDataView.createIndex.noMatchError": "ロールアップデータビューエラー:ロールアップインデックスの 1 つと一致している必要があります",
|
||||
|
@ -3756,7 +3757,6 @@
|
|||
"indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibanaではロールアップに基づいてデータビューのデータサポートを提供します。保存された検索、可視化、ダッシュボードでこれらを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。",
|
||||
"indexPatternEditor.rollupDataView.warning.textParagraphTwo": "ロールアップデータビューは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップデータビューでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。",
|
||||
"indexPatternEditor.rollupIndexPattern.warning.title": "ベータ機能",
|
||||
"dataViews.rollupLabel": "ロールアップ",
|
||||
"indexPatternEditor.saved": "'{indexPatternName}'が保存されました",
|
||||
"indexPatternEditor.status.noSystemIndicesLabel": "データストリーム、インデックス、またはインデックスエイリアスがインデックスパターンと一致しません。",
|
||||
"indexPatternEditor.status.noSystemIndicesWithPromptLabel": "データストリーム、インデックス、またはインデックスエイリアスがインデックスパターンと一致しません。",
|
||||
|
@ -28398,17 +28398,6 @@
|
|||
"xpack.securitySolution.hostIsolationExceptions.pageAddButtonTitle": "ホスト分離例外を追加",
|
||||
"xpack.securitySolution.hostIsolationExceptions.pageTitle": "ホスト分離例外",
|
||||
"xpack.securitySolution.hostIsolationExceptions.searchPlaceholderInfo": "次のフィールドで検索:名前、説明、IP",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.buttonLabel": "リスクスコアを計算する方法",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.classificationHeader": "分類",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.closeBtn": "閉じる",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.criticalRiskDescription": "90以上",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.explanation": "この機能は変換を利用します。また、5日間の範囲で、スクリプトメトリックアグリゲーションを使用して、「オープン」ステータスの検知ルールアラートに基づいてホストリスクスコアを計算します。変換は毎時実行され、新しい検知ルールアラートを受信するとスコアが常に更新されます。",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.informationAriaLabel": "情報",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.introduction": "ホストリスクスコア機能は、環境内のリスクが高いホストを明らかにします。",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.link": "こちら",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.riskHeader": "ホストリスクスコア範囲",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.title": "ホストリスクを計算する方法",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.unknownRiskDescription": "20未満",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskScore": "リスクスコア",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyLabel": "高リスク",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyThresholdHeader": "高リスクしきい値",
|
||||
|
@ -29301,16 +29290,6 @@
|
|||
"xpack.securitySolution.users.navigation.riskTitle": "ユーザーリスク",
|
||||
"xpack.securitySolution.users.navigation.userScoreOverTimeTitle": "経時的なユーザーリスクスコア",
|
||||
"xpack.securitySolution.users.pageTitle": "ユーザー",
|
||||
"xpack.securitySolution.users.userRiskInformation.buttonLabel": "リスクスコアを計算する方法",
|
||||
"xpack.securitySolution.users.userRiskInformation.classificationHeader": "分類",
|
||||
"xpack.securitySolution.users.userRiskInformation.closeBtn": "閉じる",
|
||||
"xpack.securitySolution.users.userRiskInformation.criticalRiskDescription": "90以上",
|
||||
"xpack.securitySolution.users.userRiskInformation.explanation": "この機能は変換を利用します。また、5日間の範囲で、スクリプトメトリックアグリゲーションを使用して、「オープン」ステータスの検知ルールアラートに基づいてユーザーリスクスコアを計算します。変換は毎時実行され、新しい検知ルールアラートを受信するとスコアが常に更新されます。",
|
||||
"xpack.securitySolution.users.userRiskInformation.introduction": "ユーザーリスクスコア機能は、環境内のリスクが高いユーザーを明らかにします。",
|
||||
"xpack.securitySolution.users.userRiskInformation.link": "こちら",
|
||||
"xpack.securitySolution.users.userRiskInformation.riskHeader": "ユーザーリスクスコア範囲",
|
||||
"xpack.securitySolution.users.userRiskInformation.title": "ユーザーリスクを計算する方法",
|
||||
"xpack.securitySolution.users.userRiskInformation.unknownRiskDescription": "20未満",
|
||||
"xpack.securitySolution.usersKpiAuthentications.errorSearchDescription": "ユーザーKPI認証検索でエラーが発生しました",
|
||||
"xpack.securitySolution.usersKpiAuthentications.failSearchDescription": "ユーザーKPI認証で検索を実行できませんでした",
|
||||
"xpack.securitySolution.usersRiskTable.riskTitle": "ユーザーリスク分類",
|
||||
|
|
|
@ -1909,6 +1909,11 @@
|
|||
"dataViews.functions.dataViewLoad.id.help": "要加载的数据视图 ID",
|
||||
"dataViews.indexPatternLoad.error.kibanaRequest": "在服务器上执行此搜索时需要 Kibana 请求。请向表达式执行模式参数提供请求对象。",
|
||||
"dataViews.unableWriteLabel": "无法写入数据视图!请刷新页面以获取此数据视图的最新更改。",
|
||||
"dataViews.aliasLabel": "别名",
|
||||
"dataViews.dataStreamLabel": "数据流",
|
||||
"dataViews.frozenLabel": "已冻结",
|
||||
"dataViews.indexLabel": "索引",
|
||||
"dataViews.rollupLabel": "汇总/打包",
|
||||
"discover.advancedSettings.disableDocumentExplorerDescription": "要使用新的 {documentExplorerDocs},而非经典视图,请关闭此选项。Document Explorer 提供了更合理的数据排序、可调整大小的列和全屏视图。",
|
||||
"discover.advancedSettings.discover.showFieldStatisticsDescription": "启用 {fieldStatisticsDocs} 以显示详细信息,如数字字段的最小和最大值,或地理字段的地图。此功能为公测版,可能会进行更改。",
|
||||
"discover.advancedSettings.discover.showMultifieldsDescription": "控制 {multiFields} 是否显示在展开的文档视图中。多数情况下,多字段与原始字段相同。此选项仅在 `searchFieldsFromSource` 关闭时可用。",
|
||||
|
@ -3718,10 +3723,8 @@
|
|||
"indexPatternEditor.status.partialMatchLabel.partialMatchDetail": "您的索引模式不匹配任何数据流、索引或索引别名,但{strongIndices}{matchedIndicesLength, plural, other {} }类似。",
|
||||
"indexPatternEditor.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural, one {个源} other {# 个源} }",
|
||||
"indexPatternEditor.status.successLabel.successDetail": "您的索引模式匹配 {sourceCount} 个{sourceCount, plural, other {源} }。",
|
||||
"dataViews.aliasLabel": "别名",
|
||||
"indexPatternEditor.createIndex.noMatch": "名称必须匹配一个或多个数据流、索引或索引别名。",
|
||||
"indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- 我不想使用时间筛选 ---",
|
||||
"dataViews.dataStreamLabel": "数据流",
|
||||
"indexPatternEditor.dataView.unableSaveLabel": "无法保存数据视图。",
|
||||
"indexPatternEditor.dataViewExists.ValidationErrorMessage": "具有此名称的数据视图已存在。",
|
||||
"indexPatternEditor.editDataView.editConfirmationModal.confirmButton": "确认",
|
||||
|
@ -3752,8 +3755,6 @@
|
|||
"indexPatternEditor.form.customIndexPatternIdLabel": "定制数据视图 ID",
|
||||
"indexPatternEditor.form.nameAriaLabel": "名称字段(可选)",
|
||||
"indexPatternEditor.form.titleAriaLabel": "索引模式字段",
|
||||
"dataViews.frozenLabel": "已冻结",
|
||||
"dataViews.indexLabel": "索引",
|
||||
"indexPatternEditor.loadingHeader": "正在寻找匹配的索引......",
|
||||
"indexPatternEditor.requireTimestampOption.ValidationErrorMessage": "选择时间戳字段。",
|
||||
"indexPatternEditor.rollupDataView.createIndex.noMatchError": "汇总/打包数据视图错误:必须匹配一个汇总/打包索引",
|
||||
|
@ -3761,7 +3762,6 @@
|
|||
"indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibana 基于汇总/打包为数据视图提供公测版支持。将这些视图用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。",
|
||||
"indexPatternEditor.rollupDataView.warning.textParagraphTwo": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包数据视图。汇总/打包数据视图的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。",
|
||||
"indexPatternEditor.rollupIndexPattern.warning.title": "公测版功能",
|
||||
"dataViews.rollupLabel": "汇总/打包",
|
||||
"indexPatternEditor.saved": "已保存“{indexPatternName}”",
|
||||
"indexPatternEditor.status.noSystemIndicesLabel": "没有数据流、索引或索引别名匹配您的索引模式。",
|
||||
"indexPatternEditor.status.noSystemIndicesWithPromptLabel": "没有数据流、索引或索引别名匹配您的索引模式。",
|
||||
|
@ -28432,17 +28432,6 @@
|
|||
"xpack.securitySolution.hostIsolationExceptions.pageAddButtonTitle": "添加主机隔离例外",
|
||||
"xpack.securitySolution.hostIsolationExceptions.pageTitle": "主机隔离例外",
|
||||
"xpack.securitySolution.hostIsolationExceptions.searchPlaceholderInfo": "搜索下面的字段:name、description、IP",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.buttonLabel": "如何计算风险分数?",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.classificationHeader": "分类",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.closeBtn": "关闭",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.criticalRiskDescription": "90 及以上",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.explanation": "此功能利用转换,通过脚本指标聚合基于“开放”状态的检测规则告警来计算 5 天时间窗口内的主机风险分数。该转换每小时运行一次,以根据流入的新检测规则告警更新分数。",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.informationAriaLabel": "信息",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.introduction": "主机风险分数功能将显示您环境中存在风险的主机。",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.link": "此处",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.riskHeader": "主机风险分数范围",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.title": "如何计算主机风险?",
|
||||
"xpack.securitySolution.hosts.hostRiskInformation.unknownRiskDescription": "小于 20",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskScore": "风险分数",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyLabel": "有风险",
|
||||
"xpack.securitySolution.hosts.hostScoreOverTime.riskyThresholdHeader": "有风险的阈值",
|
||||
|
@ -29335,16 +29324,6 @@
|
|||
"xpack.securitySolution.users.navigation.riskTitle": "用户风险",
|
||||
"xpack.securitySolution.users.navigation.userScoreOverTimeTitle": "一段时间的用户风险分数",
|
||||
"xpack.securitySolution.users.pageTitle": "用户",
|
||||
"xpack.securitySolution.users.userRiskInformation.buttonLabel": "如何计算风险分数?",
|
||||
"xpack.securitySolution.users.userRiskInformation.classificationHeader": "分类",
|
||||
"xpack.securitySolution.users.userRiskInformation.closeBtn": "关闭",
|
||||
"xpack.securitySolution.users.userRiskInformation.criticalRiskDescription": "90 及以上",
|
||||
"xpack.securitySolution.users.userRiskInformation.explanation": "此功能利用转换,通过脚本指标聚合基于“开放”状态的检测规则告警来计算 5 天时间窗口内的用户风险分数。该转换每小时运行一次,以根据流入的新检测规则告警更新分数。",
|
||||
"xpack.securitySolution.users.userRiskInformation.introduction": "用户风险分数功能将显示您环境中存在风险的用户。",
|
||||
"xpack.securitySolution.users.userRiskInformation.link": "此处",
|
||||
"xpack.securitySolution.users.userRiskInformation.riskHeader": "用户风险分数范围",
|
||||
"xpack.securitySolution.users.userRiskInformation.title": "如何计算用户风险?",
|
||||
"xpack.securitySolution.users.userRiskInformation.unknownRiskDescription": "小于 20",
|
||||
"xpack.securitySolution.usersKpiAuthentications.errorSearchDescription": "搜索用户 KPI 身份验证时发生错误",
|
||||
"xpack.securitySolution.usersKpiAuthentications.failSearchDescription": "无法对用户 KPI 身份验证执行搜索",
|
||||
"xpack.securitySolution.usersRiskTable.riskTitle": "用户风险分类",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue