Skip to content

Commit

Permalink
[Security Solution] Add 3rd level breadcrumb to admin page (elastic#7…
Browse files Browse the repository at this point in the history
…1275) (elastic#71592)

[Endpoint Security] Add 3rd level (hosts / policies) breadcrumb to admin page
  • Loading branch information
parkiino authored Jul 14, 2020
1 parent d10a8ce commit 9925cfe
Show file tree
Hide file tree
Showing 25 changed files with 145 additions and 57 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export enum SecurityPageName {
network = 'network',
timelines = 'timelines',
case = 'case',
management = 'management',
administration = 'administration',
}

export const APP_OVERVIEW_PATH = `${APP_PATH}/overview`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
CASES,
DETECTIONS,
HOSTS,
MANAGEMENT,
ADMINISTRATION,
NETWORK,
OVERVIEW,
TIMELINES,
Expand Down Expand Up @@ -73,7 +73,7 @@ describe('top-level navigation common to all pages in the Security app', () => {
});

it('navigates to the Administration page', () => {
navigateFromHeaderTo(MANAGEMENT);
navigateFromHeaderTo(ADMINISTRATION);
cy.url().should('include', ADMINISTRATION_URL);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const HOSTS = '[data-test-subj="navigation-hosts"]';

export const KQL_INPUT = '[data-test-subj="queryInput"]';

export const MANAGEMENT = '[data-test-subj="navigation-management"]';
export const ADMINISTRATION = '[data-test-subj="navigation-administration"]';

export const NETWORK = '[data-test-subj="navigation-network"]';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ export const navTabs: SiemNavTab = {
disabled: false,
urlKey: 'case',
},
[SecurityPageName.management]: {
id: SecurityPageName.management,
[SecurityPageName.administration]: {
id: SecurityPageName.administration,
name: i18n.ADMINISTRATION,
href: APP_MANAGEMENT_PATH,
disabled: false,
urlKey: SecurityPageName.management,
urlKey: SecurityPageName.administration,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/p
import { getBreadcrumbs as getCaseDetailsBreadcrumbs } from '../../../../cases/pages/utils';
import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../detections/pages/detection_engine/rules/utils';
import { getBreadcrumbs as getTimelinesBreadcrumbs } from '../../../../timelines/pages';
import { getBreadcrumbs as getAdminBreadcrumbs } from '../../../../management/pages';
import { SecurityPageName } from '../../../../app/types';
import {
RouteSpyState,
HostRouteSpyState,
NetworkRouteSpyState,
TimelineRouteSpyState,
AdministrationRouteSpyState,
} from '../../../utils/route/types';
import { getAppOverviewUrl } from '../../link_to';

Expand Down Expand Up @@ -61,6 +63,10 @@ const isCaseRoutes = (spyState: RouteSpyState): spyState is RouteSpyState =>
const isAlertsRoutes = (spyState: RouteSpyState) =>
spyState != null && spyState.pageName === SecurityPageName.detections;

const isAdminRoutes = (spyState: RouteSpyState): spyState is AdministrationRouteSpyState =>
spyState != null && spyState.pageName === SecurityPageName.administration;

// eslint-disable-next-line complexity
export const getBreadcrumbsForRoute = (
object: RouteSpyState & TabNavigationProps,
getUrlForApp: GetUrlForApp
Expand Down Expand Up @@ -159,6 +165,27 @@ export const getBreadcrumbsForRoute = (
),
];
}

if (isAdminRoutes(spyState) && object.navTabs) {
const tempNav: SearchNavTab = { urlKey: 'administration', isDetailPage: false };
let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
if (spyState.tabName != null) {
urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
}

return [
...siemRootBreadcrumb,
...getAdminBreadcrumbs(
spyState,
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
),
getUrlForApp
),
];
}

if (
spyState != null &&
object.navTabs &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ describe('SIEM Navigation', () => {
name: 'Cases',
urlKey: 'case',
},
management: {
administration: {
disabled: false,
href: '/app/security/administration',
id: 'management',
id: 'administration',
name: 'Administration',
urlKey: 'management',
urlKey: 'administration',
},
hosts: {
disabled: false,
Expand Down Expand Up @@ -218,12 +218,12 @@ describe('SIEM Navigation', () => {
name: 'Hosts',
urlKey: 'host',
},
management: {
administration: {
disabled: false,
href: '/app/security/administration',
id: 'management',
id: 'administration',
name: 'Administration',
urlKey: 'management',
urlKey: 'administration',
},
network: {
disabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export type SiemNavTabKey =
| SecurityPageName.detections
| SecurityPageName.timelines
| SecurityPageName.case
| SecurityPageName.management;
| SecurityPageName.administration;

export type SiemNavTab = Record<SiemNavTabKey, NavTab>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ export type UrlStateType =
| 'network'
| 'overview'
| 'timeline'
| 'management';
| 'administration';
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export const getUrlType = (pageName: string): UrlStateType => {
return 'timeline';
} else if (pageName === SecurityPageName.case) {
return 'case';
} else if (pageName === SecurityPageName.administration) {
return 'administration';
}
return 'overview';
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
CONSTANTS.timerange,
CONSTANTS.timeline,
],
management: [],
administration: [],
network: [
CONSTANTS.appQuery,
CONSTANTS.filters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { TimelineType } from '../../../../common/types/timeline';

import { HostsTableType } from '../../../hosts/store/model';
import { NetworkRouteType } from '../../../network/pages/navigation/types';
import { AdministrationSubTab as AdministrationType } from '../../../management/types';
import { FlowTarget } from '../../../graphql/types';

export type SiemRouteType = HostsTableType | NetworkRouteType | TimelineType;
export type SiemRouteType = HostsTableType | NetworkRouteType | TimelineType | AdministrationType;
export interface RouteSpyState {
pageName: string;
detailName: string | undefined;
Expand All @@ -38,6 +39,10 @@ export interface TimelineRouteSpyState extends RouteSpyState {
tabName: TimelineType | undefined;
}

export interface AdministrationRouteSpyState extends RouteSpyState {
tabName: AdministrationType | undefined;
}

export type RouteSpyAction =
| {
type: 'updateSearch';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ManagementStoreGlobalNamespace, ManagementSubTab } from '../types';
import { ManagementStoreGlobalNamespace, AdministrationSubTab } from '../types';
import { APP_ID } from '../../../common/constants';
import { SecurityPageName } from '../../app/types';

// --[ ROUTING ]---------------------------------------------------------------------------
export const MANAGEMENT_APP_ID = `${APP_ID}:${SecurityPageName.management}`;
export const MANAGEMENT_APP_ID = `${APP_ID}:${SecurityPageName.administration}`;
export const MANAGEMENT_ROUTING_ROOT_PATH = '';
export const MANAGEMENT_ROUTING_HOSTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.hosts})`;
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})/:policyId`;
export const MANAGEMENT_ROUTING_HOSTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.hosts})`;
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;

// --[ STORE ]---------------------------------------------------------------------------
/** The SIEM global store namespace where the management state will be mounted */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
} from './constants';
import { ManagementSubTab } from '../types';
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
import { HostIndexUIQueryParams } from '../pages/endpoint_hosts/types';

Expand Down Expand Up @@ -47,7 +47,7 @@ export const getHostListPath = (

if (name === 'hostList') {
return `${generatePath(MANAGEMENT_ROUTING_HOSTS_PATH, {
tabName: ManagementSubTab.hosts,
tabName: AdministrationSubTab.hosts,
})}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
}
return `${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
Expand All @@ -65,17 +65,17 @@ export const getHostDetailsPath = (
const urlSearch = `${urlQueryParams && !isEmpty(search) ? '&' : ''}${search ?? ''}`;

return `${generatePath(MANAGEMENT_ROUTING_HOSTS_PATH, {
tabName: ManagementSubTab.hosts,
tabName: AdministrationSubTab.hosts,
})}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
};

export const getPoliciesPath = (search?: string) =>
`${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
tabName: ManagementSubTab.policies,
tabName: AdministrationSubTab.policies,
})}${appendSearch(search)}`;

export const getPolicyDetailPath = (policyId: string, search?: string) =>
`${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
tabName: ManagementSubTab.policies,
tabName: AdministrationSubTab.policies,
policyId,
})}${appendSearch(search)}`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';

export const HOSTS_TAB = i18n.translate('xpack.securitySolution.hostsTab', {
defaultMessage: 'Hosts',
});

export const POLICIES_TAB = i18n.translate('xpack.securitySolution.policiesTab', {
defaultMessage: 'Policies',
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import React, { memo, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useParams } from 'react-router-dom';
import { PageView, PageViewProps } from '../../common/components/endpoint/page_view';
import { ManagementSubTab } from '../types';
import { AdministrationSubTab } from '../types';
import { SecurityPageName } from '../../app/types';
import { useFormatUrl } from '../../common/components/link_to';
import { getHostListPath, getPoliciesPath } from '../common/routing';
import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler';

export const ManagementPageView = memo<Omit<PageViewProps, 'tabs'>>((options) => {
const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const { tabName } = useParams<{ tabName: ManagementSubTab }>();
const { formatUrl, search } = useFormatUrl(SecurityPageName.administration);
const { tabName } = useParams<{ tabName: AdministrationSubTab }>();

const goToEndpoint = useNavigateByRouterEventHandler(
getHostListPath({ name: 'hostList' }, search)
Expand All @@ -30,20 +30,20 @@ export const ManagementPageView = memo<Omit<PageViewProps, 'tabs'>>((options) =>
}
return [
{
name: i18n.translate('xpack.securitySolution.managementTabs.endpoints', {
name: i18n.translate('xpack.securitySolution.managementTabs.hosts', {
defaultMessage: 'Hosts',
}),
id: ManagementSubTab.hosts,
isSelected: tabName === ManagementSubTab.hosts,
id: AdministrationSubTab.hosts,
isSelected: tabName === AdministrationSubTab.hosts,
href: formatUrl(getHostListPath({ name: 'hostList' })),
onClick: goToEndpoint,
},
{
name: i18n.translate('xpack.securitySolution.managementTabs.policies', {
defaultMessage: 'Policies',
}),
id: ManagementSubTab.policies,
isSelected: tabName === ManagementSubTab.policies,
id: AdministrationSubTab.policies,
isSelected: tabName === AdministrationSubTab.policies,
href: formatUrl(getPoliciesPath()),
onClick: goToPolicies,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
const policyStatus = useHostSelector(
policyResponseStatus
) as keyof typeof POLICY_STATUS_TO_HEALTH_COLOR;
const { formatUrl } = useFormatUrl(SecurityPageName.management);
const { formatUrl } = useFormatUrl(SecurityPageName.administration);

const detailsResultsUpper = useMemo(() => {
return [
Expand Down Expand Up @@ -106,7 +106,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
path: agentDetailsWithFlyoutPath,
state: {
onDoneNavigateTo: [
'securitySolution:management',
'securitySolution:administration',
{
path: getHostDetailsPath({ name: 'hostDetails', selected_host: details.host.id }),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const PolicyResponseFlyoutPanel = memo<{
const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount);
const loading = useHostSelector(policyResponseLoading);
const error = useHostSelector(policyResponseError);
const { formatUrl } = useFormatUrl(SecurityPageName.management);
const { formatUrl } = useFormatUrl(SecurityPageName.administration);
const [detailsUri, detailsRoutePath] = useMemo(
() => [
formatUrl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const HostList = () => {
policyItemsLoading,
endpointPackageVersion,
} = useHostSelector(selector);
const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const { formatUrl, search } = useFormatUrl(SecurityPageName.administration);

const dispatch = useDispatch<(a: HostAction) => void>();

Expand Down Expand Up @@ -127,12 +127,12 @@ export const HostList = () => {
}`,
state: {
onCancelNavigateTo: [
'securitySolution:management',
'securitySolution:administration',
{ path: getHostListPath({ name: 'hostList' }) },
],
onCancelUrl: formatUrl(getHostListPath({ name: 'hostList' })),
onSaveNavigateTo: [
'securitySolution:management',
'securitySolution:administration',
{ path: getHostListPath({ name: 'hostList' }) },
],
},
Expand All @@ -145,7 +145,7 @@ export const HostList = () => {
path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`,
state: {
onDoneNavigateTo: [
'securitySolution:management',
'securitySolution:administration',
{ path: getHostListPath({ name: 'hostList' }) },
],
},
Expand Down Expand Up @@ -422,7 +422,7 @@ export const HostList = () => {
</>
)}
{renderTableOrEmptyState}
<SpyRoute pageName={SecurityPageName.management} />
<SpyRoute pageName={SecurityPageName.administration} />
</ManagementPageView>
);
};
Loading

0 comments on commit 9925cfe

Please sign in to comment.