[8.15] [Security Solutions] Add a preview button to alerts inside the risk contribution panel (#187148) (#188219)

# Backport

This will backport the following commits from `main` to `8.15`:
- [[Security Solutions] Add a preview button to alerts inside the risk
contribution panel
(#187148)](https://github.com/elastic/kibana/pull/187148)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Pablo
Machado","email":"pablo.nevesmachado@elastic.co"},"sourceCommit":{"committedDate":"2024-07-09T09:54:47Z","message":"[Security
Solutions] Add a preview button to alerts inside the risk contribution
panel (#187148)\n\n## Summary\r\n\r\nThe feature is hidden behind the
flag `entityAlertPreviewEnabled`\r\n\r\n* It adds the extra column to
the risk contribution panel with a button\r\nthat opens the alert
preview
panel\r\n\r\n\r\n\r\n0677de6b-a6fa-461b-92b5-188d79e7274c\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))","sha":"0ee7a7a76d8eefc5ab44e0320a44edc178746212","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport:skip","Theme:
entity_analytics","v8.16.0"],"number":187148,"url":"https://github.com/elastic/kibana/pull/187148","mergeCommit":{"message":"[Security
Solutions] Add a preview button to alerts inside the risk contribution
panel (#187148)\n\n## Summary\r\n\r\nThe feature is hidden behind the
flag `entityAlertPreviewEnabled`\r\n\r\n* It adds the extra column to
the risk contribution panel with a button\r\nthat opens the alert
preview
panel\r\n\r\n\r\n\r\n0677de6b-a6fa-461b-92b5-188d79e7274c\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))","sha":"0ee7a7a76d8eefc5ab44e0320a44edc178746212"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/187148","number":187148,"mergeCommit":{"message":"[Security
Solutions] Add a preview button to alerts inside the risk contribution
panel (#187148)\n\n## Summary\r\n\r\nThe feature is hidden behind the
flag `entityAlertPreviewEnabled`\r\n\r\n* It adds the extra column to
the risk contribution panel with a button\r\nthat opens the alert
preview
panel\r\n\r\n\r\n\r\n0677de6b-a6fa-461b-92b5-188d79e7274c\r\n\r\n\r\n\r\n\r\n###
Checklist\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] Any UI
touched in this PR is usable by keyboard only (learn more\r\nabout
[keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n-
[x] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))","sha":"0ee7a7a76d8eefc5ab44e0320a44edc178746212"}}]}]
BACKPORT-->
This commit is contained in:
Pablo Machado 2024-07-16 11:48:45 +02:00 committed by GitHub
parent 779668816d
commit 02533fa7e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 284 additions and 86 deletions

View file

@ -14,7 +14,7 @@ import { RiskInputsTab } from './tabs/risk_inputs/risk_inputs_tab';
export const RISK_INPUTS_TAB_TEST_ID = `${PREFIX}RiskInputsTab` as const;
export const getRiskInputTab = ({ entityType, entityName }: RiskInputsTabProps) => ({
export const getRiskInputTab = ({ entityType, entityName, scopeId }: RiskInputsTabProps) => ({
id: EntityDetailsLeftPanelTab.RISK_INPUTS,
'data-test-subj': RISK_INPUTS_TAB_TEST_ID,
name: (
@ -23,5 +23,5 @@ export const getRiskInputTab = ({ entityType, entityName }: RiskInputsTabProps)
defaultMessage="Risk contributions"
/>
),
content: <RiskInputsTab entityType={entityType} entityName={entityName} />,
content: <RiskInputsTab entityType={entityType} entityName={entityName} scopeId={scopeId} />,
});

View file

@ -9,7 +9,7 @@ import { render } from '@testing-library/react';
import React from 'react';
import { TestProviders } from '../../../../../common/mock';
import { times } from 'lodash/fp';
import { RiskInputsTab } from './risk_inputs_tab';
import { EXPAND_ALERT_TEST_ID, RiskInputsTab } from './risk_inputs_tab';
import { alertInputDataMock } from '../../mocks';
import { RiskSeverity } from '../../../../../../common/search_strategy';
import { RiskScoreEntity } from '../../../../../../common/entity_analytics/risk_engine';
@ -49,6 +49,12 @@ const riskScore = {
},
};
const mockUseIsExperimentalFeatureEnabled = jest.fn().mockReturnValue(false);
jest.mock('../../../../../common/hooks/use_experimental_features', () => ({
useIsExperimentalFeatureEnabled: () => mockUseIsExperimentalFeatureEnabled(),
}));
const riskScoreWithAssetCriticalityContribution = (contribution: number) => {
const score = JSON.parse(JSON.stringify(riskScore));
score.user.risk.category_2_score = contribution;
@ -74,7 +80,7 @@ describe('RiskInputsTab', () => {
const { getByTestId, queryByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
@ -87,7 +93,7 @@ describe('RiskInputsTab', () => {
const { queryByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
@ -116,13 +122,57 @@ describe('RiskInputsTab', () => {
const { queryByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
expect(queryByTestId('risk-input-contexts-title')).toBeInTheDocument();
});
it('it renders alert preview button when feature flag is enable', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
mockUseRiskScore.mockReturnValue({
loading: false,
error: false,
data: [riskScore],
});
mockUseRiskContributingAlerts.mockReturnValue({
loading: false,
error: false,
data: [alertInputDataMock],
});
const { getByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
expect(getByTestId(EXPAND_ALERT_TEST_ID)).toBeInTheDocument();
});
it('it does not render alert preview button when feature flag is disable', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
mockUseRiskScore.mockReturnValue({
loading: false,
error: false,
data: [riskScore],
});
mockUseRiskContributingAlerts.mockReturnValue({
loading: false,
error: false,
data: [alertInputDataMock],
});
const { queryByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
expect(queryByTestId(EXPAND_ALERT_TEST_ID)).not.toBeInTheDocument();
});
it('Displays 0.00 for the asset criticality contribution if the contribution value is less than -0.01', () => {
mockUseUiSetting.mockReturnValue([true]);
@ -134,7 +184,7 @@ describe('RiskInputsTab', () => {
const { getByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
const contextsTable = getByTestId('risk-input-contexts-table');
@ -153,7 +203,7 @@ describe('RiskInputsTab', () => {
const { getByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
const contextsTable = getByTestId('risk-input-contexts-table');
@ -172,7 +222,7 @@ describe('RiskInputsTab', () => {
const { getByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);
@ -201,7 +251,7 @@ describe('RiskInputsTab', () => {
const { queryByTestId } = render(
<TestProviders>
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" />
<RiskInputsTab entityType={RiskScoreEntity.user} entityName="elastic" scopeId={'scopeId'} />
</TestProviders>
);

View file

@ -14,6 +14,8 @@ import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import { ALERT_RULE_NAME } from '@kbn/rule-data-utils';
import { get } from 'lodash/fp';
import { AlertPreviewButton } from '../../../../../flyout/shared/components/alert_preview_button';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { useGlobalTime } from '../../../../../common/containers/use_global_time';
import { useQueryInspector } from '../../../../../common/components/page/manage_query';
import { formatRiskScore } from '../../../../common';
@ -40,6 +42,7 @@ import { ActionColumn } from '../../components/action_column';
export interface RiskInputsTabProps extends Record<string, unknown> {
entityType: RiskScoreEntity;
entityName: string;
scopeId: string;
}
const FIRST_RECORD_PAGINATION = {
@ -47,9 +50,10 @@ const FIRST_RECORD_PAGINATION = {
querySize: 1,
};
export const EXPAND_ALERT_TEST_ID = 'risk-input-alert-preview-button';
export const RISK_INPUTS_TAB_QUERY_ID = 'RiskInputsTabQuery';
export const RiskInputsTab = ({ entityType, entityName }: RiskInputsTabProps) => {
export const RiskInputsTab = ({ entityType, entityName, scopeId }: RiskInputsTabProps) => {
const { setQuery, deleteQuery } = useGlobalTime();
const [selectedItems, setSelectedItems] = useState<InputAlert[]>([]);
@ -96,9 +100,26 @@ export const RiskInputsTab = ({ entityType, entityName }: RiskInputsTabProps) =>
}),
[]
);
const isPreviewEnabled = useIsExperimentalFeatureEnabled('entityAlertPreviewEnabled');
const inputColumns: Array<EuiBasicTableColumn<InputAlert>> = useMemo(
() => [
...(isPreviewEnabled
? [
{
render: (data: InputAlert) => (
<AlertPreviewButton
id={data._id}
indexName={data.input.index}
scopeId={scopeId}
data-test-subj={EXPAND_ALERT_TEST_ID}
/>
),
width: '5%',
},
]
: []),
{
name: (
<FormattedMessage
@ -153,7 +174,7 @@ export const RiskInputsTab = ({ entityType, entityName }: RiskInputsTabProps) =>
render: formatContribution,
},
],
[]
[isPreviewEnabled, scopeId]
);
const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING);

View file

@ -16,7 +16,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_ex
import { mockFlyoutApi } from '../../shared/mocks/mock_flyout_context';
import { mockContextValue } from '../../shared/mocks/mock_context';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { ALERT_PREVIEW_BANNER } from '../../preview';
import { ALERT_PREVIEW_BANNER } from '../../preview/constants';
import { DocumentDetailsContext } from '../../shared/context';
jest.mock('../hooks/use_paginated_alerts');

View file

@ -7,16 +7,14 @@
import type { ReactElement, ReactNode } from 'react';
import React, { type FC, useMemo, useCallback } from 'react';
import { type Criteria, EuiBasicTable, formatDate, EuiButtonIcon } from '@elastic/eui';
import { type Criteria, EuiBasicTable, formatDate } from '@elastic/eui';
import { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
import type { Filter } from '@kbn/es-query';
import { isRight } from 'fp-ts/lib/Either';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { useDocumentDetailsContext } from '../../shared/context';
import { CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID } from './test_ids';
import { CellTooltipWrapper } from '../../shared/components/cell_tooltip_wrapper';
import type { DataProvider } from '../../../../../common/types';
@ -26,51 +24,11 @@ import { ExpandablePanel } from '../../../shared/components/expandable_panel';
import { InvestigateInTimelineButton } from '../../../../common/components/event_details/table/investigate_in_timeline_button';
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
import { getDataProvider } from '../../../../common/components/event_details/table/use_action_cell_data_provider';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { ALERT_PREVIEW_BANNER } from '../../preview';
import { AlertPreviewButton } from '../../../shared/components/alert_preview_button';
export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS';
const dataProviderLimit = 5;
interface AlertPreviewButtonProps {
/**
* Id of the document
*/
id: string;
/**
* Name of the index used in the parent's page
*/
indexName: string;
}
const AlertPreviewButton: FC<AlertPreviewButtonProps> = ({ id, indexName }) => {
const { openPreviewPanel } = useExpandableFlyoutApi();
const { scopeId } = useDocumentDetailsContext();
const openAlertPreview = useCallback(
() =>
openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
params: {
id,
indexName,
scopeId,
isPreviewMode: true,
banner: ALERT_PREVIEW_BANNER,
},
}),
[openPreviewPanel, id, indexName, scopeId]
);
return (
<EuiButtonIcon
iconType="expand"
data-test-subj={CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID}
onClick={openAlertPreview}
/>
);
};
export interface CorrelationsDetailsAlertsTableProps {
/**
* Text to display in the ExpandablePanel title section
@ -172,7 +130,12 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
? [
{
render: (row: Record<string, unknown>) => (
<AlertPreviewButton id={row.id as string} indexName={row.index as string} />
<AlertPreviewButton
id={row.id as string}
indexName={row.index as string}
data-test-subj={CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID}
scopeId={scopeId}
/>
),
width: '5%',
},
@ -247,7 +210,7 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
},
},
],
[isPreviewEnabled]
[isPreviewEnabled, scopeId]
);
return (

View file

@ -0,0 +1,19 @@
/*
* 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 ALERT_PREVIEW_BANNER = {
title: i18n.translate(
'xpack.securitySolution.flyout.left.insights.correlations.alertPreviewTitle',
{
defaultMessage: 'Preview alert details',
}
),
backgroundColor: 'warning',
textColor: 'warning',
};

View file

@ -8,7 +8,6 @@
import type { FC } from 'react';
import React, { memo } from 'react';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { i18n } from '@kbn/i18n';
import { DocumentDetailsPreviewPanelKey } from '../shared/constants/panel_keys';
import { useTabs } from '../right/hooks/use_tabs';
import { useFlyoutIsExpandable } from '../right/hooks/use_flyout_is_expandable';
@ -18,17 +17,7 @@ import { PanelHeader } from '../right/header';
import { PanelContent } from '../right/content';
import { PreviewPanelFooter } from './footer';
import type { RightPanelTabType } from '../right/tabs';
export const ALERT_PREVIEW_BANNER = {
title: i18n.translate(
'xpack.securitySolution.flyout.left.insights.correlations.alertPreviewTitle',
{
defaultMessage: 'Preview alert details',
}
),
backgroundColor: 'warning',
textColor: 'warning',
};
import { ALERT_PREVIEW_BANNER } from './constants';
/**
* Panel to be displayed in the document details expandable flyout on top of right section

View file

@ -41,16 +41,22 @@ jest.mock('../../../entity_analytics/api/hooks/use_risk_score', () => ({
describe('HostDetailsPanel', () => {
it('render risk inputs panel', () => {
const { getByTestId } = render(<HostDetailsPanel name="elastic" isRiskScoreExist={true} />, {
wrapper: TestProviders,
});
const { getByTestId } = render(
<HostDetailsPanel name="elastic" isRiskScoreExist={true} scopeId={'scopeId'} />,
{
wrapper: TestProviders,
}
);
expect(getByTestId(RISK_INPUTS_TAB_TEST_ID)).toBeInTheDocument();
});
it("doesn't render risk inputs panel when no alerts ids are provided", () => {
const { queryByTestId } = render(<HostDetailsPanel name="elastic" isRiskScoreExist={false} />, {
wrapper: TestProviders,
});
const { queryByTestId } = render(
<HostDetailsPanel name="elastic" isRiskScoreExist={false} scopeId={'scopeId'} />,
{
wrapper: TestProviders,
}
);
expect(queryByTestId(RISK_INPUTS_TAB_TEST_ID)).not.toBeInTheDocument();
});
});

View file

@ -18,6 +18,7 @@ import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine
export interface HostDetailsPanelProps extends Record<string, unknown> {
isRiskScoreExist: boolean;
name: string;
scopeId: string;
}
export interface HostDetailsExpandableFlyoutProps extends FlyoutPanelProps {
key: 'host_details';
@ -25,18 +26,18 @@ export interface HostDetailsExpandableFlyoutProps extends FlyoutPanelProps {
}
export const HostDetailsPanelKey: HostDetailsExpandableFlyoutProps['key'] = 'host_details';
export const HostDetailsPanel = ({ name, isRiskScoreExist }: HostDetailsPanelProps) => {
export const HostDetailsPanel = ({ name, isRiskScoreExist, scopeId }: HostDetailsPanelProps) => {
// Temporary implementation while Host details left panel don't have Asset tabs
const [tabs, selectedTabId, setSelectedTabId] = useMemo(() => {
const isRiskScoreTabAvailable = isRiskScoreExist && name;
return [
isRiskScoreTabAvailable
? [getRiskInputTab({ entityName: name, entityType: RiskScoreEntity.host })]
? [getRiskInputTab({ entityName: name, entityType: RiskScoreEntity.host, scopeId })]
: [],
EntityDetailsLeftPanelTab.RISK_INPUTS,
() => {},
];
}, [name, isRiskScoreExist]);
}, [name, isRiskScoreExist, scopeId]);
return (
<>

View file

@ -112,12 +112,13 @@ export const HostPanel = ({
id: HostDetailsPanelKey,
params: {
name: hostName,
scopeId,
isRiskScoreExist,
path: tab ? { tab } : undefined,
},
});
},
[telemetry, openLeftPanel, hostName, isRiskScoreExist]
[telemetry, openLeftPanel, hostName, isRiskScoreExist, scopeId]
);
const openDefaultPanel = useCallback(() => openTabPanel(), [openTabPanel]);

View file

@ -20,6 +20,7 @@ describe('LeftPanel', () => {
}}
isRiskScoreExist
user={{ name: 'test user', email: [] }}
scopeId={'scopeId'}
/>,
{
wrapper: TestProviders,
@ -39,6 +40,7 @@ describe('LeftPanel', () => {
}}
isRiskScoreExist={false}
user={{ name: 'test user', email: [] }}
scopeId={'scopeId'}
/>,
{
wrapper: TestProviders,

View file

@ -27,6 +27,7 @@ export interface UserDetailsPanelProps extends Record<string, unknown> {
isRiskScoreExist: boolean;
user: UserParam;
path?: PanelPath;
scopeId: string;
}
export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps {
key: 'user_details';
@ -34,9 +35,14 @@ export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps {
}
export const UserDetailsPanelKey: UserDetailsExpandableFlyoutProps['key'] = 'user_details';
export const UserDetailsPanel = ({ isRiskScoreExist, user, path }: UserDetailsPanelProps) => {
export const UserDetailsPanel = ({
isRiskScoreExist,
user,
path,
scopeId,
}: UserDetailsPanelProps) => {
const managedUser = useManagedUser(user.name, user.email);
const tabs = useTabs(managedUser.data, user.name, isRiskScoreExist);
const tabs = useTabs(managedUser.data, user.name, isRiskScoreExist, scopeId);
const { selectedTabId, setSelectedTabId } = useSelectedTab(isRiskScoreExist, user, tabs, path);
if (managedUser.isLoading) return <FlyoutLoading />;

View file

@ -25,7 +25,8 @@ import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_
export const useTabs = (
managedUser: ManagedUserHits,
name: string,
isRiskScoreExist: boolean
isRiskScoreExist: boolean,
scopeId: string
): LeftPanelTabsType =>
useMemo(() => {
const tabs: LeftPanelTabsType = [];
@ -37,6 +38,7 @@ export const useTabs = (
getRiskInputTab({
entityName: name,
entityType: RiskScoreEntity.user,
scopeId,
})
);
}
@ -50,7 +52,7 @@ export const useTabs = (
}
return tabs;
}, [isRiskScoreExist, managedUser, name]);
}, [isRiskScoreExist, managedUser, name, scopeId]);
const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({
id: EntityDetailsLeftPanelTab.OKTA,

View file

@ -115,6 +115,7 @@ export const UserPanel = ({
id: UserDetailsPanelKey,
params: {
isRiskScoreExist: !!userRiskData?.user?.risk,
scopeId,
user: {
name: userName,
email,
@ -123,7 +124,7 @@ export const UserPanel = ({
path: tab ? { tab } : undefined,
});
},
[telemetry, email, openLeftPanel, userName, userRiskData]
[telemetry, openLeftPanel, userRiskData?.user?.risk, userName, email, scopeId]
);
const openPanelFirstTab = useCallback(() => openPanelTab(), [openPanelTab]);

View file

@ -0,0 +1,65 @@
/*
* 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 { fireEvent, render } from '@testing-library/react';
import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout';
import React from 'react';
import { AlertPreviewButton } from './alert_preview_button';
import { DocumentDetailsPreviewPanelKey } from '../../document_details/shared/constants/panel_keys';
import { ALERT_PREVIEW_BANNER } from '../../document_details/preview/constants';
const mockOpenPreviewPanel = jest.fn();
jest.mock('@kbn/expandable-flyout', () => {
return {
useExpandableFlyoutApi: () => ({
openPreviewPanel: mockOpenPreviewPanel,
}),
};
});
describe('AlertPreviewButton', () => {
it('renders the icon', () => {
const { getByTestId } = render(
<AlertPreviewButton
id="1"
indexName="index"
scopeId="scope"
data-test-subj="alertPreviewButton"
/>,
{ wrapper: ExpandableFlyoutProvider }
);
expect(getByTestId('alertPreviewButton')).toBeInTheDocument();
});
it('opens the preview panel when clicked', () => {
const id = '1';
const indexName = 'index';
const scopeId = 'scope';
const { getByTestId } = render(
<AlertPreviewButton
id={id}
indexName={indexName}
scopeId={scopeId}
data-test-subj="alertPreviewButton"
/>,
{ wrapper: ExpandableFlyoutProvider }
);
fireEvent.click(getByTestId('alertPreviewButton'));
expect(mockOpenPreviewPanel).toHaveBeenCalledWith({
id: DocumentDetailsPreviewPanelKey,
params: {
id,
indexName,
scopeId,
isPreviewMode: true,
banner: ALERT_PREVIEW_BANNER,
},
});
});
});

View file

@ -0,0 +1,72 @@
/*
* 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 { EuiButtonIcon } from '@elastic/eui';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import React, { useCallback } from 'react';
import type { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ALERT_PREVIEW_BANNER } from '../../document_details/preview/constants';
import { DocumentDetailsPreviewPanelKey } from '../../document_details/shared/constants/panel_keys';
interface AlertPreviewButtonProps {
/**
* Name of the index used in the parent's page
*/
indexName: string;
/**
* Id of the alert to preview
*/
id: string;
/**
* Data attribute used for testing.
*/
'data-test-subj'?: string;
/**
* Maintain backwards compatibility // TODO remove when possible
*/
scopeId: string;
}
/**
* Icon button showed on tables to launch a preview of the alert details panel.
*/
export const AlertPreviewButton: FC<AlertPreviewButtonProps> = ({
id,
indexName,
'data-test-subj': dataTestSubj,
scopeId,
}) => {
const { openPreviewPanel } = useExpandableFlyoutApi();
const openAlertPreview = useCallback(
() =>
openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
params: {
id,
indexName,
scopeId,
isPreviewMode: true,
banner: ALERT_PREVIEW_BANNER,
},
}),
[openPreviewPanel, id, indexName, scopeId]
);
return (
<EuiButtonIcon
iconType="expand"
data-test-subj={dataTestSubj}
onClick={openAlertPreview}
aria-label={i18n.translate('xpack.securitySolution.flyout.right.alertPreview.ariaLabel', {
defaultMessage: 'Preview alert with id {id}',
values: { id },
})}
/>
);
};