[Security Solution] Enable shared-ux nav (#169499)

## Summary

Makes the shared-ux navigation the default side navigation component for
Security projects.

The old Security navigation component and the experimental flag will be
removed altogether in a separate PR.

There is no visual difference from the previous navigation:


![snapshot](2896e8de-45eb-412f-b319-e919e65a0ae7)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sergi Massaneda 2023-10-25 18:20:04 +02:00 committed by GitHub
parent 5c578a06b3
commit c40c2e6f59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 188 additions and 36 deletions

View file

@ -63,6 +63,15 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
getStyles(euiTheme)
);
const dataTestSubj = classNames(`nav-item`, `nav-item-${id}`, {
[`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink,
[`nav-item-id-${id}`]: id,
[`nav-item-isActive`]: isActive,
});
const buttonDataTestSubj = classNames(`panelOpener`, `panelOpener-${id}`, {
[`panelOpener-deepLinkId-${deepLink?.id}`]: !!deepLink,
});
const onLinkClick = useCallback(
(e: React.MouseEvent) => {
if (!href) {
@ -95,7 +104,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
className={itemClassNames}
color="text"
size="s"
data-test-subj={`sideNavItemLink-${id}`}
data-test-subj={dataTestSubj}
/>
</EuiListGroup>
</EuiFlexItem>
@ -111,7 +120,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
aria-label={i18n.translate('sharedUXPackages.chrome.sideNavigation.togglePanel', {
defaultMessage: 'Toggle panel navigation',
})}
data-test-subj={`panelOpener-${id}`}
data-test-subj={buttonDataTestSubj}
/>
</EuiFlexItem>
)}

View file

@ -37,7 +37,9 @@ export const NavigationPanel: FC = () => {
const onOutsideClick = useCallback(
({ target }: Event) => {
// Only close if we are not clicking on the currently selected nav node
if ((target as HTMLButtonElement).dataset.testSubj !== `panelOpener-${selectedNode?.id}`) {
if (
!(target as HTMLButtonElement).dataset.testSubj?.includes(`panelOpener-${selectedNode?.id}`)
) {
close();
}
},

View file

@ -19,7 +19,7 @@ export const allowedExperimentalValues = Object.freeze({
/**
* Enables the use of the of the product navigation from shared-ux package in the Security Solution app
*/
platformNavEnabled: false,
platformNavEnabled: true,
});
type ServerlessExperimentalConfigKeys = Array<keyof ServerlessExperimentalFeatures>;

View file

@ -52,6 +52,9 @@ export const formatNavigationTree = (
breadcrumbStatus: 'hidden',
defaultIsCollapsed: false,
children: bodyChildren,
accordionProps: {
arrowProps: { css: { display: 'none' } },
},
},
],
footer: formatFooterNodesFromLinks(footerNavItems, footerCategories),

View file

@ -8,13 +8,13 @@
import { getBuildingBlockRule } from '../../../objects/rule';
import { OVERVIEW_ALERTS_HISTOGRAM_EMPTY } from '../../../screens/overview';
import { HIGHLIGHTED_ROWS_IN_TABLE } from '../../../screens/rule_details';
import { OVERVIEW } from '../../../screens/security_header';
import { createRule } from '../../../tasks/api_calls/rules';
import { cleanKibana } from '../../../tasks/common';
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
import { login } from '../../../tasks/login';
import { visit } from '../../../tasks/navigation';
import { visitRuleDetailsPage, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details';
import { navigateFromHeaderTo } from '../../../tasks/security_header';
import { OVERVIEW_URL } from '../../../urls/navigation';
const EXPECTED_NUMBER_OF_ALERTS = 5;
@ -42,7 +42,7 @@ describe('Alerts generated by building block rules', { tags: ['@ess', '@serverle
// Make sure rows are highlighted
cy.get(HIGHLIGHTED_ROWS_IN_TABLE).should('exist');
navigateFromHeaderTo(OVERVIEW);
visit(OVERVIEW_URL);
// Check that generated events are hidden on the Overview page
cy.get(OVERVIEW_ALERTS_HISTOGRAM_EMPTY).should('contain.text', 'No results found');

View file

@ -0,0 +1,122 @@
/*
* 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.
*/
// main panels links
export const DASHBOARDS = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:dashboards"]';
export const DASHBOARDS_PANEL_BTN =
'[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:dashboards"]';
export const INVESTIGATIONS =
'[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:investigations"]';
export const INVESTIGATIONS_PANEL_BTN =
'[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:investigations"]';
export const EXPLORE = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:explore"]';
export const EXPLORE_PANEL_BTN =
'[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:explore"]';
export const RULES_LANDING =
'[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:rules-landing"]';
export const RULES_PANEL_BTN =
'[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:rules-landing"]';
export const ASSETS = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:assets"]';
export const ASSETS_PANEL_BTN =
'[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:assets"]';
// main direct links
export const DISCOVER = '[data-test-subj*="nav-item-deepLinkId-discover"]';
export const ALERTS = '[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:alerts"]';
export const CSP_FINDINGS =
'[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:cloud_security_posture-findings"]';
export const CASES = '[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:cases"]';
// nested links
export const OVERVIEW = '[data-test-subj="solutionSideNavPanelLink-overview"]';
export const DETECTION_RESPONSE = '[data-test-subj="solutionSideNavPanelLink-detection_response"]';
export const ENTITY_ANALYTICS = '[data-test-subj="solutionSideNavPanelLink-entity_analytics"]';
export const TIMELINES = '[data-test-subj="solutionSideNavPanelLink-timelines"]';
export const KUBERNETES = '[data-test-subj="solutionSideNavPanelLink-kubernetes"]';
export const CSP_DASHBOARD =
'[data-test-subj="solutionSideNavPanelLink-cloud_security_posture-dashboard"]';
export const HOSTS = '[data-test-subj="solutionSideNavPanelLink-hosts"]';
export const ENDPOINTS = '[data-test-subj="solutionSideNavPanelLink-endpoints"]';
export const POLICIES = '[data-test-subj="solutionSideNavPanelLink-policy"]';
export const TRUSTED_APPS = '[data-test-subj="solutionSideNavPanelLink-trusted_apps"]';
export const EVENT_FILTERS = '[data-test-subj="solutionSideNavPanelLink-event_filters"]';
export const BLOCKLIST = '[data-test-subj="solutionSideNavPanelLink-blocklist"]';
export const CSP_BENCHMARKS =
'[data-test-subj="solutionSideNavPanelLink-cloud_security_posture-benchmarks"]';
export const NETWORK = '[data-test-subj="solutionSideNavPanelLink-network"]';
export const USERS = '[data-test-subj="solutionSideNavPanelLink-users"]';
export const INDICATORS = '[data-test-subj="solutionSideNavItemLink-threat_intelligence"]';
export const RULES = '[data-test-subj="solutionSideNavPanelLink-rules"]';
export const EXCEPTIONS = '[data-test-subj="solutionSideNavPanelLink-exceptions"]';
// opens the navigation panel for a given nested link
export const openNavigationPanelFor = (page: string) => {
let panel;
switch (page) {
case OVERVIEW:
case DETECTION_RESPONSE:
case KUBERNETES:
case ENTITY_ANALYTICS:
case CSP_DASHBOARD: {
panel = DASHBOARDS_PANEL_BTN;
break;
}
case HOSTS:
case NETWORK:
case USERS: {
panel = EXPLORE_PANEL_BTN;
break;
}
case RULES:
case EXCEPTIONS:
case CSP_BENCHMARKS: {
panel = RULES_PANEL_BTN;
break;
}
case ENDPOINTS:
case TRUSTED_APPS:
case EVENT_FILTERS:
case POLICIES:
case BLOCKLIST: {
panel = ASSETS_PANEL_BTN;
break;
}
}
if (panel) {
openNavigationPanel(panel);
}
};
// opens the navigation panel of a main link
export const openNavigationPanel = (page: string) => {
cy.get(page).click();
};

View file

@ -5,14 +5,12 @@
* 2.0.
*/
const serverlessLocator = {
alerts: '[data-test-subj="solutionSideNavItemLink-alerts"]',
};
import { ALERTS } from '../../screens/serverless_security_header';
const navigateTo = (page: string) => {
cy.get(page).click();
};
export const navigateToAlertsPageInServerless = () => {
navigateTo(serverlessLocator.alerts);
navigateTo(ALERTS);
};

View file

@ -12,7 +12,7 @@ export function SvlSecLandingPageProvider({ getService }: FtrProviderContext) {
return {
async assertSvlSecSideNavExists() {
await testSubjects.existOrFail('securitySolutionNavHeading');
await testSubjects.existOrFail('securitySolutionSideNav');
},
};
}

View file

@ -11,7 +11,7 @@ export function MachineLearningNavigationProviderSecurity({ getService }: FtrPro
const testSubjects = getService('testSubjects');
async function navigateToArea(id: string) {
await testSubjects.click('~solutionSideNavItemButton-machine_learning-landing');
await testSubjects.click('~panelOpener-deepLinkId-securitySolutionUI:machine_learning-landing');
await testSubjects.existOrFail(`~solutionSideNavPanelLink-ml:${id}`, {
timeout: 60 * 1000,
});

View file

@ -9,9 +9,9 @@ import { expect } from 'expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default ({ getPageObject, getService }: FtrProviderContext) => {
const common = getPageObject('common');
const dashboard = getPageObject('dashboard');
const lens = getPageObject('lens');
const svlSecNavigation = getService('svlSecNavigation');
const svlCommonPage = getPageObject('svlCommonPage');
const testSubjects = getService('testSubjects');
const cases = getService('cases');
@ -25,9 +25,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
describe('lens visualization', () => {
before(async () => {
await svlCommonPage.login();
await svlSecNavigation.navigateToLandingPage();
await testSubjects.click('solutionSideNavItemLink-dashboards');
await common.navigateToApp('security', { path: 'dashboards' });
await header.waitUntilLoadingHasFinished();
await retry.waitFor('createDashboardButton', async () => {
@ -88,7 +86,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
owner: 'securitySolution',
});
await testSubjects.click('solutionSideNavItemLink-dashboards');
await common.navigateToApp('security', { path: 'dashboards' });
await header.waitUntilLoadingHasFinished();
if (await testSubjects.exists('edit-unsaved-New-Dashboard')) {
await testSubjects.click('edit-unsaved-New-Dashboard');

View file

@ -6,13 +6,16 @@
*/
import expect from '@kbn/expect';
import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common';
import { navigateToCasesApp } from '../../../../../shared/lib/cases/helpers';
import { FtrProviderContext } from '../../../../ftr_provider_context';
const owner = SECURITY_SOLUTION_OWNER;
export default ({ getPageObject, getService }: FtrProviderContext) => {
const common = getPageObject('common');
const header = getPageObject('header');
const svlCommonPage = getPageObject('svlCommonPage');
const svlSecNavigation = getService('svlSecNavigation');
const testSubjects = getService('testSubjects');
const cases = getService('cases');
const svlCases = getService('svlCases');
@ -23,9 +26,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
describe('Configure Case', function () {
before(async () => {
await svlCommonPage.login();
await svlSecNavigation.navigateToLandingPage();
await testSubjects.click('solutionSideNavItemLink-cases');
await header.waitUntilLoadingHasFinished();
await navigateToCasesApp(getPageObject, getService, owner);
await retry.waitFor('configure-case-button exist', async () => {
return await testSubjects.exists('configure-case-button');

View file

@ -6,10 +6,14 @@
*/
import expect from '@kbn/expect';
import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common';
import { CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain';
import { SeverityAll } from '@kbn/cases-plugin/common/ui';
import { navigateToCasesApp } from '../../../../../shared/lib/cases/helpers';
import { FtrProviderContext } from '../../../../ftr_provider_context';
const owner = SECURITY_SOLUTION_OWNER;
export default ({ getPageObject, getService }: FtrProviderContext) => {
const header = getPageObject('header');
const testSubjects = getService('testSubjects');
@ -24,7 +28,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await svlSecNavigation.navigateToLandingPage();
await testSubjects.click('solutionSideNavItemLink-cases');
await navigateToCasesApp(getPageObject, getService, owner);
});
after(async () => {
@ -151,7 +155,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
describe('severity filtering', () => {
// Error: retry.tryForTime timeout: Error: expected 10 to equal 5
before(async () => {
await testSubjects.click('solutionSideNavItemLink-cases');
await navigateToCasesApp(getPageObject, getService, owner);
await cases.api.createCase({ severity: CaseSeverity.LOW, owner: 'securitySolution' });
await cases.api.createCase({ severity: CaseSeverity.LOW, owner: 'securitySolution' });
@ -167,7 +171,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
* There is no easy way to clear the filtering.
* Refreshing the page seems to be easier.
*/
await testSubjects.click('solutionSideNavItemLink-cases');
await navigateToCasesApp(getPageObject, getService, owner);
});
after(async () => {

View file

@ -18,6 +18,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context';
import {
createOneCaseBeforeDeleteAllAfter,
createAndNavigateToCase,
navigateToCasesApp,
} from '../../../../../shared/lib/cases/helpers';
const owner = SECURITY_SOLUTION_OWNER;
@ -473,7 +474,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
];
before(async () => {
await testSubjects.click('solutionSideNavItemLink-cases');
await navigateToCasesApp(getPageObject, getService, owner);
await cases.api.createConfigWithCustomFields({ customFields, owner });
await cases.api.createCase({
customFields: [

View file

@ -6,21 +6,27 @@
*/
import expect from '@kbn/expect';
import { AppDeepLinkId } from '@kbn/core-chrome-browser';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlCommonPage = getPageObject('svlCommonPage');
const svlSecLandingPage = getPageObject('svlSecLandingPage');
const svlSecNavigation = getService('svlSecNavigation');
const svlCommonNavigation = getPageObject('svlCommonNavigation');
const testSubjects = getService('testSubjects');
const browser = getService('browser');
// FLAKY: https://github.com/elastic/kibana/issues/165629
describe.skip('navigation', function () {
describe('navigation', function () {
before(async () => {
await svlCommonPage.login();
await svlSecNavigation.navigateToLandingPage();
});
after(async () => {
await svlCommonPage.forceLogout();
});
it('has security serverless side nav', async () => {
await svlSecLandingPage.assertSvlSecSideNavExists();
await svlCommonNavigation.expectExists();
@ -30,7 +36,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await svlCommonNavigation.breadcrumbs.expectExists();
// TODO: use `deepLinkId` instead of `text`, once security deep links are available in @kbn/core-chrome-browser
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' });
await testSubjects.click('solutionSideNavItemLink-alerts');
await svlCommonNavigation.sidenav.clickLink({
deepLinkId: 'securitySolutionUI:alerts' as AppDeepLinkId,
});
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Alerts' });
await svlCommonNavigation.breadcrumbs.clickHome();
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' });
@ -38,8 +46,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
it('navigate using search', async () => {
await svlCommonNavigation.search.showSearch();
await svlCommonNavigation.search.searchFor('dashboards');
await svlCommonNavigation.search.clickOnOption(1);
await svlCommonNavigation.search.searchFor('security dashboards');
await svlCommonNavigation.search.clickOnOption(0);
await svlCommonNavigation.search.hideSearch();
await expect(await browser.getCurrentUrl()).contain('app/security/dashboards');
@ -49,11 +57,15 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await svlSecLandingPage.assertSvlSecSideNavExists();
await svlCommonNavigation.expectExists();
expect(await testSubjects.existOrFail('solutionSideNavItemLink-cases'));
await svlCommonNavigation.sidenav.expectLinkExists({
deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId,
});
});
it('navigates to cases app', async () => {
await testSubjects.click('solutionSideNavItemLink-cases');
await svlCommonNavigation.sidenav.clickLink({
deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId,
});
expect(await browser.getCurrentUrl()).contain('/app/security/cases');
await testSubjects.existOrFail('cases-all-title');

View file

@ -6,6 +6,7 @@
*/
import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common';
import { AppDeepLinkId } from '@kbn/core-chrome-browser';
import { FtrProviderContext } from '../../../functional/ftr_provider_context';
export const createOneCaseBeforeDeleteAllAfter = (
@ -64,15 +65,15 @@ export const navigateToCasesApp = async (
getService: FtrProviderContext['getService'],
owner: string
) => {
const testSubjects = getService('testSubjects');
const common = getPageObject('common');
const svlCommonNavigation = getPageObject('svlCommonNavigation');
await common.navigateToApp('landingPage');
if (owner === SECURITY_SOLUTION_OWNER) {
await testSubjects.click('solutionSideNavItemLink-cases');
await svlCommonNavigation.sidenav.clickLink({
deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId,
});
} else {
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' });
}