diff --git a/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx b/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx
index 4947bcabac5520..df8246df69d5ea 100644
--- a/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx
+++ b/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx
@@ -22,6 +22,9 @@ import {
type TestType,
type ProjectNavigationChangeListener,
} from './utils';
+import { getServicesMock } from '../mocks/src/jest';
+
+const { cloudLinks: mockCloudLinks } = getServicesMock();
describe('builds navigation tree', () => {
test('render reference UI and build the navigation tree', async () => {
@@ -675,21 +678,41 @@ describe('builds navigation tree', () => {
});
test('should render the cloud links', async () => {
+ const stripLastChar = (str: string = '') => str.substring(0, str.length - 1);
+
const runTests = async (type: TestType, { findByTestId }: RenderResult) => {
try {
expect(await findByTestId(/nav-item-group1.cloudLink1/)).toBeVisible();
expect(await findByTestId(/nav-item-group1.cloudLink2/)).toBeVisible();
expect(await findByTestId(/nav-item-group1.cloudLink3/)).toBeVisible();
- expect((await findByTestId(/nav-item-group1.cloudLink1/)).textContent).toBe(
- 'Mock Users & RolesExternal link'
- );
- expect((await findByTestId(/nav-item-group1.cloudLink2/)).textContent).toBe(
- 'Mock PerformanceExternal link'
- );
- expect((await findByTestId(/nav-item-group1.cloudLink3/)).textContent).toBe(
- 'Mock Billing & SubscriptionsExternal link'
- );
+ {
+ const userAndRolesLink = await findByTestId(/nav-item-group1.cloudLink1/);
+ expect(userAndRolesLink.textContent).toBe('Mock Users & RolesExternal link');
+ const href = userAndRolesLink.getAttribute('href');
+ expect(href).toBe(stripLastChar(mockCloudLinks.userAndRoles?.href));
+ }
+
+ {
+ const performanceLink = await findByTestId(/nav-item-group1.cloudLink2/);
+ expect(performanceLink.textContent).toBe('Mock PerformanceExternal link');
+ const href = performanceLink.getAttribute('href');
+ expect(href).toBe(stripLastChar(mockCloudLinks.performance?.href));
+ }
+
+ {
+ const billingLink = await findByTestId(/nav-item-group1.cloudLink3/);
+ expect(billingLink.textContent).toBe('Mock Billing & SubscriptionsExternal link');
+ const href = billingLink.getAttribute('href');
+ expect(href).toBe(stripLastChar(mockCloudLinks.billingAndSub?.href));
+ }
+
+ {
+ const deploymentLink = await findByTestId(/nav-item-group1.cloudLink4/);
+ expect(deploymentLink.textContent).toBe('Mock DeploymentExternal link');
+ const href = deploymentLink.getAttribute('href');
+ expect(href).toBe(stripLastChar(mockCloudLinks.deployment?.href));
+ }
} catch (e) {
errorHandler(type)(e);
}
@@ -706,6 +729,7 @@ describe('builds navigation tree', () => {
{ id: 'cloudLink1', cloudLink: 'userAndRoles' },
{ id: 'cloudLink2', cloudLink: 'performance' },
{ id: 'cloudLink3', cloudLink: 'billingAndSub' },
+ { id: 'cloudLink4', cloudLink: 'deployment' },
],
},
];
@@ -727,6 +751,7 @@ describe('builds navigation tree', () => {
+
),
diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts
index e1761de6be6fdf..9ae6c2b2a6952e 100644
--- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts
+++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts
@@ -33,15 +33,19 @@ export const getServicesMock = ({
cloudLinks: {
billingAndSub: {
title: 'Mock Billing & Subscriptions',
- href: 'https://cloud.elastic.co/account/billing',
+ href: 'https://cloud.elastic.co/account/billing/',
},
performance: {
title: 'Mock Performance',
- href: 'https://cloud.elastic.co/deployments/123456789/performance',
+ href: 'https://cloud.elastic.co/deployments/123456789/performance/',
},
userAndRoles: {
title: 'Mock Users & Roles',
- href: 'https://cloud.elastic.co/deployments/123456789/security/users',
+ href: 'https://cloud.elastic.co/deployments/123456789/security/users/',
+ },
+ deployment: {
+ title: 'Mock Deployment',
+ href: 'https://cloud.elastic.co/deployments/123456789/',
},
},
};
diff --git a/packages/shared-ux/chrome/navigation/src/cloud_links.tsx b/packages/shared-ux/chrome/navigation/src/cloud_links.tsx
index fd1909551888bf..ce9d9e5990aaba 100644
--- a/packages/shared-ux/chrome/navigation/src/cloud_links.tsx
+++ b/packages/shared-ux/chrome/navigation/src/cloud_links.tsx
@@ -9,11 +9,13 @@ import { i18n } from '@kbn/i18n';
import type { CloudLinkId } from '@kbn/core-chrome-browser';
import type { CloudStart } from '@kbn/cloud-plugin/public';
+export interface CloudLink {
+ title: string;
+ href: string;
+}
+
export type CloudLinks = {
- [id in CloudLinkId]?: {
- title: string;
- href: string;
- };
+ [id in CloudLinkId]?: CloudLink;
};
export const getCloudLinks = (cloud: CloudStart): CloudLinks => {
diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx
index fd3f774bd72fba..f77a288160a3dc 100644
--- a/packages/shared-ux/chrome/navigation/src/services.tsx
+++ b/packages/shared-ux/chrome/navigation/src/services.tsx
@@ -9,15 +9,45 @@
import React, { FC, useContext, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { NavigationKibanaDependencies, NavigationServices } from '../types';
-import { CloudLinks, getCloudLinks } from './cloud_links';
+import { CloudLink, CloudLinks, getCloudLinks } from './cloud_links';
const Context = React.createContext(null);
+const stripTrailingForwardSlash = (str: string) => {
+ return str[str.length - 1] === '/' ? str.substring(0, str.length - 1) : str;
+};
+
+const parseCloudURLs = (cloudLinks: CloudLinks): CloudLinks => {
+ const { userAndRoles, billingAndSub, deployment, performance } = cloudLinks;
+
+ // We remove potential trailing forward slash ("/") at the end of the URL
+ // because it breaks future navigation in Cloud console once we navigate there.
+ const parseLink = (link?: CloudLink): CloudLink | undefined => {
+ if (!link) return undefined;
+ return { ...link, href: stripTrailingForwardSlash(link.href) };
+ };
+
+ return {
+ ...cloudLinks,
+ userAndRoles: parseLink(userAndRoles),
+ billingAndSub: parseLink(billingAndSub),
+ deployment: parseLink(deployment),
+ performance: parseLink(performance),
+ };
+};
+
/**
* A Context Provider that provides services to the component and its dependencies.
*/
export const NavigationProvider: FC = ({ children, ...services }) => {
- return {children};
+ const servicesParsed = useMemo(() => {
+ return {
+ ...services,
+ cloudLinks: parseCloudURLs(services.cloudLinks),
+ };
+ }, [services]);
+
+ return {children};
};
/**
@@ -32,7 +62,10 @@ export const NavigationKibanaProvider: FC = ({
const { basePath } = http;
const { navigateToUrl } = core.application;
- const cloudLinks: CloudLinks = useMemo(() => (cloud ? getCloudLinks(cloud) : {}), [cloud]);
+ const cloudLinks: CloudLinks = useMemo(
+ () => (cloud ? parseCloudURLs(getCloudLinks(cloud)) : {}),
+ [cloud]
+ );
const isSideNavCollapsed = useObservable(chrome.getIsSideNavCollapsed$(), true);
const value: NavigationServices = {