[9.0] [Solution Side Nav] Add back external link indicator to sidenav (#215946) (#217242)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Solution Side Nav] Add back external link indicator to sidenav
(#215946)](https://github.com/elastic/kibana/pull/215946)

<!--- Backport version: 9.6.6 -->

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

<!--BACKPORT [{"author":{"name":"Krzysztof
Kowalczyk","email":"krzysztof.kowalczyk@elastic.co"},"sourceCommit":{"committedDate":"2025-04-03T22:34:23Z","message":"[Solution
Side Nav] Add back external link indicator to sidenav (#215946)\n\n##
Summary\n\nThis PR adds back external link indicator to sidenav
items.\n\n![Screenshot 2025-04-01 at 20
35\n14](https://github.com/user-attachments/assets/c82def72-780e-4bb6-812d-d51244e90685)","sha":"e21739b657d57a884b7a8da713a34f4f5c8273a2","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","enhancement","v9.0.0","Team:SharedUX","backport:version","v9.1.0","v8.19.0"],"title":"[Solution
Side Nav] Add back external link indicator to
sidenav","number":215946,"url":"https://github.com/elastic/kibana/pull/215946","mergeCommit":{"message":"[Solution
Side Nav] Add back external link indicator to sidenav (#215946)\n\n##
Summary\n\nThis PR adds back external link indicator to sidenav
items.\n\n![Screenshot 2025-04-01 at 20
35\n14](https://github.com/user-attachments/assets/c82def72-780e-4bb6-812d-d51244e90685)","sha":"e21739b657d57a884b7a8da713a34f4f5c8273a2"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.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/215946","number":215946,"mergeCommit":{"message":"[Solution
Side Nav] Add back external link indicator to sidenav (#215946)\n\n##
Summary\n\nThis PR adds back external link indicator to sidenav
items.\n\n![Screenshot 2025-04-01 at 20
35\n14](https://github.com/user-attachments/assets/c82def72-780e-4bb6-812d-d51244e90685)","sha":"e21739b657d57a884b7a8da713a34f4f5c8273a2"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Krzysztof Kowalczyk 2025-04-04 22:24:35 +02:00 committed by GitHub
parent 001bcfefcf
commit 6cdbf70834
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 38 additions and 28 deletions

View file

@ -175,7 +175,7 @@ describe('initNavigation()', () => {
path: 'group1.foo',
href: '/app/foo',
deepLink: getNavLink({ id: 'foo', title: 'FOO' }),
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
});
expect(node.children![0].href).toBe(node.children![0]!.deepLink!.href);
@ -240,14 +240,14 @@ describe('initNavigation()', () => {
title: '',
path: 'node-1',
type: 'navGroup',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
children: [
{
id: 'node-0', // auto generated
path: 'node-1.node-0',
title: '',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
children: [
{
@ -261,7 +261,7 @@ describe('initNavigation()', () => {
},
href: '/app/foo',
id: 'foo',
isElasticInternalLink: false,
isExternalLink: false,
path: 'node-1.node-0.foo',
sideNavStatus: 'visible',
title: 'FOO',
@ -277,21 +277,21 @@ describe('initNavigation()', () => {
title: 'Footer group',
path: 'node-4',
type: 'navGroup',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
children: [
{
id: 'node-0', // auto generated
path: 'node-4.node-0',
title: '',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
children: [
{
deepLink: expect.any(Object), // we are not testing the deepLink here
href: '/app/foo',
id: 'foo',
isElasticInternalLink: false,
isExternalLink: false,
path: 'node-4.node-0.foo',
sideNavStatus: 'visible',
title: 'FOO',
@ -330,7 +330,7 @@ describe('initNavigation()', () => {
},
"href": "/app/discover",
"id": "discover",
"isElasticInternalLink": false,
"isExternalLink": false,
"onClick": undefined,
"path": "rootNav:analytics.discover",
"sideNavStatus": "visible",
@ -349,7 +349,7 @@ describe('initNavigation()', () => {
},
"href": "/app/dashboards",
"id": "dashboards",
"isElasticInternalLink": false,
"isExternalLink": false,
"onClick": undefined,
"path": "rootNav:analytics.dashboards",
"sideNavStatus": "visible",
@ -368,7 +368,7 @@ describe('initNavigation()', () => {
},
"href": "/app/visualize",
"id": "visualize",
"isElasticInternalLink": false,
"isExternalLink": false,
"onClick": undefined,
"path": "rootNav:analytics.visualize",
"sideNavStatus": "visible",
@ -379,7 +379,7 @@ describe('initNavigation()', () => {
"href": undefined,
"icon": "stats",
"id": "rootNav:analytics",
"isElasticInternalLink": false,
"isExternalLink": false,
"onClick": undefined,
"path": "rootNav:analytics",
"renderAs": "accordion",
@ -457,7 +457,7 @@ describe('initNavigation()', () => {
deepLink: undefined,
href: 'https://cloud.elastic.co/userAndRoles',
id: 'node-0',
isElasticInternalLink: true,
isExternalLink: true,
path: 'group1.node-0',
sideNavStatus: 'visible',
title: 'Users and roles',
@ -468,7 +468,7 @@ describe('initNavigation()', () => {
deepLink: undefined,
href: 'https://cloud.elastic.co/performance',
id: 'node-1',
isElasticInternalLink: true,
isExternalLink: true,
path: 'group1.node-1',
sideNavStatus: 'visible',
title: 'Performance',
@ -479,7 +479,7 @@ describe('initNavigation()', () => {
deepLink: undefined,
href: 'https://cloud.elastic.co/billing',
id: 'node-2',
isElasticInternalLink: true,
isExternalLink: true,
path: 'group1.node-2',
sideNavStatus: 'visible',
title: 'Billing and subscription',
@ -490,7 +490,7 @@ describe('initNavigation()', () => {
deepLink: undefined,
href: 'https://cloud.elastic.co/deployment',
id: 'node-3',
isElasticInternalLink: true,
isExternalLink: true,
path: 'group1.node-3',
sideNavStatus: 'visible',
title: 'Project',
@ -804,7 +804,7 @@ describe('getActiveNodes$()', () => {
id: 'root',
title: 'Root',
path: 'root',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
type: 'navGroup',
},
@ -812,7 +812,7 @@ describe('getActiveNodes$()', () => {
id: 'item1',
title: 'ITEM1',
path: 'root.item1',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
href: '/app/item1',
deepLink: {
@ -861,7 +861,7 @@ describe('getActiveNodes$()', () => {
id: 'root',
title: 'Root',
path: 'root',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
type: 'navGroup',
},
@ -869,7 +869,7 @@ describe('getActiveNodes$()', () => {
id: 'item1',
title: 'ITEM1',
path: 'root.item1',
isElasticInternalLink: false,
isExternalLink: false,
sideNavStatus: 'visible',
href: '/app/item1',
deepLink: {

View file

@ -321,8 +321,8 @@ const initNavNode = <
const id = getNavigationNodeId(node, () => `node-${index}`) as Id;
const title = getTitleForNode(node, { deepLink, cloudLinks });
const isElasticInternalLink = cloudLink != null;
const href = isElasticInternalLink ? cloudLinks[cloudLink]?.href : node.href;
const isExternalLink = cloudLink != null;
const href = isExternalLink ? cloudLinks[cloudLink]?.href : node.href;
const path = parentNodePath ? `${parentNodePath}.${id}` : id;
if (href && !isAbsoluteLink(href) && !onClick) {
@ -337,7 +337,7 @@ const initNavNode = <
path,
title,
deepLink,
isElasticInternalLink,
isExternalLink,
sideNavStatus,
};

View file

@ -242,7 +242,7 @@ export interface ChromeProjectNavigationNode extends NodeDefinitionBase {
/**
* Flag to indicate if the node is an "external" cloud link
*/
isElasticInternalLink?: boolean;
isExternalLink?: boolean;
}
export type PanelSelectedNode = Pick<

View file

@ -118,7 +118,6 @@ const serializeNavNode = (
const serialized: ChromeProjectNavigationNode = {
...navNode,
};
serialized.renderAs = getRenderAs(serialized, { isSideNavCollapsed });
serialized.spaceBefore = getSpaceBefore(serialized, {
isSideNavCollapsed,
@ -258,7 +257,7 @@ const getEuiProps = (
// if it is the highest match in the URL, not if one of its children is also active.
const onlyIfHighestMatch = isAccordion && !isCollapsible;
const isActive = isActiveFromUrl(navNode.path, activeNodes, onlyIfHighestMatch);
const isExternal = Boolean(href) && !navNode.isElasticInternalLink && isAbsoluteLink(href!);
const isExternal = Boolean(href) && navNode.isExternalLink && isAbsoluteLink(href!);
const isAccordionExpanded = !getIsCollapsed(path);
let isSelected = isActive;
@ -369,7 +368,7 @@ function nodeToEuiCollapsibleNavProps(
_navNode,
deps
);
const { id, path, href, renderAs, isCollapsible, spaceBefore } = navNode;
const { id, path, href, renderAs, isCollapsible, spaceBefore, isExternalLink } = navNode;
if (navNode.renderItem) {
// Leave the rendering to the consumer
@ -411,7 +410,9 @@ function nodeToEuiCollapsibleNavProps(
// Render as an accordion or a link (handled by EUI) depending if
// "items" is undefined or not. If it is undefined --> a link, otherwise an
// accordion is rendered.
...(subItems ? { items: subItems, isCollapsible } : { href, linkProps }),
...(subItems
? { items: subItems, isCollapsible }
: { href, ...linkProps, external: isExternalLink }),
},
];

View file

@ -25,7 +25,8 @@ interface Props {
export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
const { navigateToUrl } = useServices();
const { close: closePanel } = usePanel();
const { id, icon, deepLink, openInNewTab, renderItem } = item;
const { id, icon, deepLink, openInNewTab, isExternalLink, renderItem } = item;
const href = deepLink?.url ?? item.href;
const { euiTheme } = useEuiTheme();
@ -53,6 +54,9 @@ export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
&.sideNavPanelLink:hover {
background-color: ${transparentize(euiTheme.colors.lightShade, 0.5)};
}
& svg[class*='EuiExternalLinkIcon'] {
margin-left: auto;
}
`
)}
size="s"
@ -60,6 +64,7 @@ export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
href={href}
iconType={icon}
onClick={onClick}
external={isExternalLink}
target={openInNewTab ? '_blank' : undefined}
/>
);

View file

@ -111,6 +111,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
title: 'Item 01',
href: '/app/kibana',
icon: 'iInCircle',
isExternalLink: true,
},
{
id: 'item02',
@ -203,6 +204,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
title: 'Item 17',
href: '/app/kibana',
icon: 'iInCircle',
isExternalLink: true,
},
{
id: 'sub2',
@ -240,6 +242,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
title: 'Item-Labs',
href: '/app/kibana',
withBadge: true,
isExternalLink: true,
badgeOptions: {
icon: 'bell',
tooltip: 'This is a tooltip',
@ -414,6 +417,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
title: 'Item-Beta',
href: '/app/kibana',
withBadge: true,
isExternalLink: true,
},
{
id: 'item-labs',