mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `9.0`: - [[SecuritySolution] Fix host details flyout left panel tabs (#215672)](https://github.com/elastic/kibana/pull/215672) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Pablo Machado","email":"pablo.nevesmachado@elastic.co"},"sourceCommit":{"committedDate":"2025-03-24T19:47:15Z","message":"[SecuritySolution] Fix host details flyout left panel tabs (#215672)\n\n## Summary\n\nFix Unable to switch between Risk Contributions and Insights on host\ndetails flyout.\n\n\n**Pre Conditions**\n1. Alerts should be available on Kibana.\n2. Entity Risk Score must be enabled.\n\n**Steps**\n1. Navigate to a page where the flyout is available.\n3. For any Entity, open details flyout\n4. Expand Details flyout (left panel).\n5. Observe that the user cannot switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Expected Result**\nThe user should be able to switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Screen Recording**\n\n\nhttps://github.com/user-attachments/assets/3aae6291-5b5b-49a4-83c2-ac657e4e9524\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"6cdbeb95377cb95df567c067c03d0fa33d182b8c","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","Team: SecuritySolution","Theme: entity_analytics","Feature:Entity Analytics","Team:Entity Analytics","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[SecuritySolution] Fix host details flyout left panel tabs","number":215672,"url":"https://github.com/elastic/kibana/pull/215672","mergeCommit":{"message":"[SecuritySolution] Fix host details flyout left panel tabs (#215672)\n\n## Summary\n\nFix Unable to switch between Risk Contributions and Insights on host\ndetails flyout.\n\n\n**Pre Conditions**\n1. Alerts should be available on Kibana.\n2. Entity Risk Score must be enabled.\n\n**Steps**\n1. Navigate to a page where the flyout is available.\n3. For any Entity, open details flyout\n4. Expand Details flyout (left panel).\n5. Observe that the user cannot switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Expected Result**\nThe user should be able to switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Screen Recording**\n\n\nhttps://github.com/user-attachments/assets/3aae6291-5b5b-49a4-83c2-ac657e4e9524\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"6cdbeb95377cb95df567c067c03d0fa33d182b8c"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215672","number":215672,"mergeCommit":{"message":"[SecuritySolution] Fix host details flyout left panel tabs (#215672)\n\n## Summary\n\nFix Unable to switch between Risk Contributions and Insights on host\ndetails flyout.\n\n\n**Pre Conditions**\n1. Alerts should be available on Kibana.\n2. Entity Risk Score must be enabled.\n\n**Steps**\n1. Navigate to a page where the flyout is available.\n3. For any Entity, open details flyout\n4. Expand Details flyout (left panel).\n5. Observe that the user cannot switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Expected Result**\nThe user should be able to switch between `Risk Contributions` and\n`Insights` tabs.\n\n**Screen Recording**\n\n\nhttps://github.com/user-attachments/assets/3aae6291-5b5b-49a4-83c2-ac657e4e9524\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"6cdbeb95377cb95df567c067c03d0fa33d182b8c"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co>
This commit is contained in:
parent
813cc115e9
commit
8df649a47a
3 changed files with 238 additions and 49 deletions
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 { renderHook, act } from '@testing-library/react';
|
||||
import { useSelectedTab, useTabs } from './hooks';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { TestProviders } from '@kbn/timelines-plugin/public/mock';
|
||||
import type { HostDetailsPanelProps } from '.';
|
||||
import type { LeftPanelTabsType } from '../shared/components/left_panel/left_panel_header';
|
||||
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
|
||||
|
||||
jest.mock('@kbn/expandable-flyout', () => ({
|
||||
useExpandableFlyoutApi: jest.fn(() => ({
|
||||
openLeftPanel: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
const defaultParams: HostDetailsPanelProps = {
|
||||
isRiskScoreExist: true,
|
||||
name: 'testHost',
|
||||
scopeId: 'test',
|
||||
};
|
||||
|
||||
const defaultTabs: LeftPanelTabsType = [
|
||||
{
|
||||
id: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
|
||||
'data-test-subj': 'cspInsightsTab',
|
||||
name: <span>{'cspInsightsTab name'}</span>,
|
||||
content: <span>{'cspInsightsTab content'}</span>,
|
||||
},
|
||||
{
|
||||
id: EntityDetailsLeftPanelTab.RISK_INPUTS,
|
||||
'data-test-subj': 'riskTab',
|
||||
name: <span>{'riskTab name'}</span>,
|
||||
content: <span>{'riskTab content'}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
describe('hooks', () => {
|
||||
describe('useSelectedTab', () => {
|
||||
const mockOpenLeftPanel = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
|
||||
});
|
||||
|
||||
it('should return the default tab when no path is provided', () => {
|
||||
const { result } = renderHook(
|
||||
() => useSelectedTab({ path: undefined, ...defaultParams }, defaultTabs),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.current.selectedTabId).toBe(EntityDetailsLeftPanelTab.CSP_INSIGHTS);
|
||||
});
|
||||
|
||||
it('should return the tab matching the path', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useSelectedTab(
|
||||
{ path: { tab: EntityDetailsLeftPanelTab.RISK_INPUTS }, ...defaultParams },
|
||||
defaultTabs
|
||||
),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.current.selectedTabId).toBe(EntityDetailsLeftPanelTab.RISK_INPUTS);
|
||||
});
|
||||
|
||||
it('should call openLeftPanel with the correct parameters when setSelectedTabId is called', () => {
|
||||
const { result } = renderHook(
|
||||
() => useSelectedTab({ path: undefined, ...defaultParams }, defaultTabs),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.setSelectedTabId(EntityDetailsLeftPanelTab.RISK_INPUTS);
|
||||
});
|
||||
|
||||
expect(mockOpenLeftPanel).toHaveBeenCalledWith({
|
||||
id: expect.any(String),
|
||||
params: { ...defaultParams, path: { tab: EntityDetailsLeftPanelTab.RISK_INPUTS } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useTabs', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should include the risk score tab when isRiskScoreExist and name are true', () => {
|
||||
const { result } = renderHook(() => useTabs(defaultParams), { wrapper: TestProviders });
|
||||
|
||||
expect(result.current).toEqual([
|
||||
expect.objectContaining({ id: EntityDetailsLeftPanelTab.RISK_INPUTS }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should include the insights tab when any findings or alerts are present', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useTabs({
|
||||
...defaultParams,
|
||||
isRiskScoreExist: false,
|
||||
hasMisconfigurationFindings: true,
|
||||
hasVulnerabilitiesFindings: true,
|
||||
hasNonClosedAlerts: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.current).toEqual([
|
||||
expect.objectContaining({ id: EntityDetailsLeftPanelTab.CSP_INSIGHTS }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when no tabs are available', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useTabs({
|
||||
isRiskScoreExist: false,
|
||||
name: '',
|
||||
scopeId: 'scope1',
|
||||
hasMisconfigurationFindings: false,
|
||||
hasVulnerabilitiesFindings: false,
|
||||
hasNonClosedAlerts: false,
|
||||
}),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { EntityIdentifierFields, EntityType } from '../../../../common/entity_analytics/types';
|
||||
import {
|
||||
getRiskInputTab,
|
||||
getInsightsInputTab,
|
||||
} from '../../../entity_analytics/components/entity_details_flyout';
|
||||
import type {
|
||||
LeftPanelTabsType,
|
||||
EntityDetailsLeftPanelTab,
|
||||
} from '../shared/components/left_panel/left_panel_header';
|
||||
|
||||
import type { HostDetailsPanelProps } from '.';
|
||||
import { HostDetailsPanelKey } from '.';
|
||||
|
||||
export const useSelectedTab = (params: HostDetailsPanelProps, tabs: LeftPanelTabsType) => {
|
||||
const { openLeftPanel } = useExpandableFlyoutApi();
|
||||
const path = params.path;
|
||||
|
||||
const selectedTabId = useMemo(() => {
|
||||
const defaultTab = tabs.length > 0 ? tabs[0].id : undefined;
|
||||
if (!path) return defaultTab;
|
||||
|
||||
return tabs.find((tab) => tab.id === path.tab)?.id ?? defaultTab;
|
||||
}, [path, tabs]);
|
||||
|
||||
const setSelectedTabId = (tabId: EntityDetailsLeftPanelTab) => {
|
||||
openLeftPanel({
|
||||
id: HostDetailsPanelKey,
|
||||
params: {
|
||||
...params,
|
||||
path: {
|
||||
tab: tabId,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { setSelectedTabId, selectedTabId };
|
||||
};
|
||||
|
||||
export const useTabs = ({
|
||||
isRiskScoreExist,
|
||||
name,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
}: HostDetailsPanelProps): LeftPanelTabsType => {
|
||||
return useMemo(() => {
|
||||
const isRiskScoreTabAvailable = isRiskScoreExist && name;
|
||||
const riskScoreTab = isRiskScoreTabAvailable
|
||||
? [getRiskInputTab({ entityName: name, entityType: EntityType.host, scopeId })]
|
||||
: [];
|
||||
|
||||
// Determine if the Insights tab should be included
|
||||
const insightsTab =
|
||||
hasMisconfigurationFindings || hasVulnerabilitiesFindings || hasNonClosedAlerts
|
||||
? [getInsightsInputTab({ name, fieldName: EntityIdentifierFields.hostName })]
|
||||
: [];
|
||||
|
||||
return [...riskScoreTab, ...insightsTab];
|
||||
}, [
|
||||
isRiskScoreExist,
|
||||
name,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
]);
|
||||
};
|
|
@ -5,19 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||
import { EntityIdentifierFields, EntityType } from '../../../../common/entity_analytics/types';
|
||||
import {
|
||||
getRiskInputTab,
|
||||
getInsightsInputTab,
|
||||
} from '../../../entity_analytics/components/entity_details_flyout';
|
||||
import React from 'react';
|
||||
import { type FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||
import { LeftPanelContent } from '../shared/components/left_panel/left_panel_content';
|
||||
import type { CspInsightLeftPanelSubTab } from '../shared/components/left_panel/left_panel_header';
|
||||
import {
|
||||
import type {
|
||||
CspInsightLeftPanelSubTab,
|
||||
EntityDetailsLeftPanelTab,
|
||||
LeftPanelHeader,
|
||||
} from '../shared/components/left_panel/left_panel_header';
|
||||
import { LeftPanelHeader } from '../shared/components/left_panel/left_panel_header';
|
||||
import { useSelectedTab, useTabs } from './hooks';
|
||||
|
||||
export interface HostDetailsPanelProps extends Record<string, unknown> {
|
||||
isRiskScoreExist: boolean;
|
||||
|
@ -37,46 +33,13 @@ export interface HostDetailsExpandableFlyoutProps extends FlyoutPanelProps {
|
|||
}
|
||||
export const HostDetailsPanelKey: HostDetailsExpandableFlyoutProps['key'] = 'host_details';
|
||||
|
||||
export const HostDetailsPanel = ({
|
||||
name,
|
||||
isRiskScoreExist,
|
||||
scopeId,
|
||||
path,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
}: HostDetailsPanelProps) => {
|
||||
const [selectedTabId, setSelectedTabId] = useState(
|
||||
path?.tab === EntityDetailsLeftPanelTab.CSP_INSIGHTS
|
||||
? EntityDetailsLeftPanelTab.CSP_INSIGHTS
|
||||
: EntityDetailsLeftPanelTab.RISK_INPUTS
|
||||
);
|
||||
export const HostDetailsPanel = (params: HostDetailsPanelProps) => {
|
||||
const tabs = useTabs(params);
|
||||
const { selectedTabId, setSelectedTabId } = useSelectedTab(params, tabs);
|
||||
|
||||
useEffect(() => {
|
||||
if (path?.tab && path.tab !== selectedTabId) {
|
||||
setSelectedTabId(path.tab);
|
||||
}
|
||||
}, [path?.tab, selectedTabId]);
|
||||
|
||||
const [tabs] = useMemo(() => {
|
||||
const isRiskScoreTabAvailable = isRiskScoreExist && name;
|
||||
const riskScoreTab = isRiskScoreTabAvailable
|
||||
? [getRiskInputTab({ entityName: name, entityType: EntityType.host, scopeId })]
|
||||
: [];
|
||||
// Determine if the Insights tab should be included
|
||||
const insightsTab =
|
||||
hasMisconfigurationFindings || hasVulnerabilitiesFindings || hasNonClosedAlerts
|
||||
? [getInsightsInputTab({ name, fieldName: EntityIdentifierFields.hostName })]
|
||||
: [];
|
||||
return [[...riskScoreTab, ...insightsTab], EntityDetailsLeftPanelTab.RISK_INPUTS, () => {}];
|
||||
}, [
|
||||
isRiskScoreExist,
|
||||
name,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
]);
|
||||
if (!selectedTabId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue