Skip to content

Commit

Permalink
Merge branch 'main' into 160371-infra-ui-add-alerts-to-asset-details-…
Browse files Browse the repository at this point in the history
…flyout
  • Loading branch information
jennypavlova authored Jul 18, 2023
2 parents c4a0048 + 2796107 commit 6b24ae1
Show file tree
Hide file tree
Showing 61 changed files with 1,568 additions and 393 deletions.
3 changes: 0 additions & 3 deletions config/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ xpack.fleet.internal.activeAgentsSoftLimit: 25000

# Cloud links
xpack.cloud.base_url: "https://cloud.elastic.co"
xpack.cloud.profile_url: "/user/settings"
xpack.cloud.billing_url: "/billing"
xpack.cloud.organization_url: "/account"

# Enable ZDT migration algorithm
migrations.algorithm: zdt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ export class ProjectNavigationService {
const activeNodes = findActiveNodes(
currentPathname,
this.projectNavigationNavTreeFlattened,
location
location,
this.http?.basePath.prepend
);

// Each time we call findActiveNodes() we create a new array of activeNodes. As this array is used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,29 @@ describe('findActiveNodes', () => {
]);
});

test('should find active node at the root', () => {
const flattendNavTree: Record<string, ChromeProjectNavigationNode> = {
'[0]': {
id: 'root',
title: 'Root',
deepLink: getDeepLink('root', `root`),
path: ['root'],
},
};

expect(findActiveNodes(`/foo/root`, flattendNavTree)).toEqual([
[
{
id: 'root',
title: 'Root',
isActive: true,
deepLink: getDeepLink('root', `root`),
path: ['root'],
},
],
]);
});

test('should match the longest matching node', () => {
const flattendNavTree: Record<string, ChromeProjectNavigationNode> = {
'[0]': {
Expand Down Expand Up @@ -345,7 +368,7 @@ describe('findActiveNodes', () => {
id: 'item1',
title: 'Item 1',
path: ['root', 'item1'],
getIsActive: (loc) => loc.pathname.startsWith('/foo'), // Should match
getIsActive: ({ location }) => location.pathname.startsWith('/foo'), // Should match
},
'[0][2]': {
id: 'item2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,25 @@ function serializeDeeplinkUrl(url?: string) {
* @param key The key to extract parent paths from
* @returns An array of parent paths
*/
function extractParentPaths(key: string) {
function extractParentPaths(key: string, navTree: Record<string, ChromeProjectNavigationNode>) {
// Split the string on every '][' to get an array of values without the brackets.
const arr = key.split('][');
if (arr.length === 1) {
return arr;
}
// Add the brackets back in for the first and last elements, and all elements in between.
arr[0] = `${arr[0]}]`;
arr[arr.length - 1] = `[${arr[arr.length - 1]}`;
for (let i = 1; i < arr.length - 1; i++) {
arr[i] = `[${arr[i]}]`;
}

return arr.reduce<string[]>((acc, currentValue, currentIndex) => {
acc.push(arr.slice(0, currentIndex + 1).join(''));
return acc;
}, []);
return arr
.reduce<string[]>((acc, currentValue, currentIndex) => {
acc.push(arr.slice(0, currentIndex + 1).join(''));
return acc;
}, [])
.filter((k) => Boolean(navTree[k]));
}

/**
Expand All @@ -101,7 +106,8 @@ function extractParentPaths(key: string) {
export const findActiveNodes = (
currentPathname: string,
navTree: Record<string, ChromeProjectNavigationNode>,
location?: Location
location?: Location,
prepend: (path: string) => string = (path) => path
): ChromeProjectNavigationNode[][] => {
const activeNodes: ChromeProjectNavigationNode[][] = [];
const matches: string[][] = [];
Expand All @@ -113,9 +119,9 @@ export const findActiveNodes = (

Object.entries(navTree).forEach(([key, node]) => {
if (node.getIsActive && location) {
const isActive = node.getIsActive(location);
const isActive = node.getIsActive({ pathNameSerialized: currentPathname, location, prepend });
if (isActive) {
const keysWithParents = extractParentPaths(key);
const keysWithParents = extractParentPaths(key, navTree);
activeNodes.push(keysWithParents.map(activeNodeFromKey));
}
return;
Expand All @@ -139,7 +145,7 @@ export const findActiveNodes = (
if (matches.length > 0) {
const longestMatch = matches[matches.length - 1];
longestMatch.forEach((key) => {
const keysWithParents = extractParentPaths(key);
const keysWithParents = extractParentPaths(key, navTree);
activeNodes.push(keysWithParents.map(activeNodeFromKey));
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/chrome/core-chrome-browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type {
ChromeUserBanner,
ChromeProjectNavigation,
ChromeProjectNavigationNode,
CloudLinkId,
SideNavCompProps,
SideNavComponent,
ChromeProjectBreadcrumb,
Expand Down
1 change: 1 addition & 0 deletions packages/core/chrome/core-chrome-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type {
ChromeProjectNavigationNode,
AppDeepLinkId,
AppId,
CloudLinkId,
SideNavCompProps,
SideNavComponent,
ChromeSetProjectBreadcrumbsParams,
Expand Down
18 changes: 16 additions & 2 deletions packages/core/chrome/core-chrome-browser/src/project_navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ export type AppDeepLinkId =
| SearchLink
| ObservabilityLink;

/** @public */
export type CloudLinkId = 'userAndRoles' | 'performance' | 'billingAndSub';

export type GetIsActiveFn = (params: {
/** The current path name including the basePath + hash value but **without** any query params */
pathNameSerialized: string;
/** The history Location */
location: Location;
/** Utiliy function to prepend a path with the basePath */
prepend: (path: string) => string;
}) => boolean;

/** @public */
export interface ChromeProjectNavigationNode {
/** Optional id, if not passed a "link" must be provided. */
Expand All @@ -69,7 +81,7 @@ export interface ChromeProjectNavigationNode {
/**
* Optional function to get the active state. This function is called whenever the location changes.
*/
getIsActive?: (location: Location) => boolean;
getIsActive?: GetIsActiveFn;

/**
* Optional flag to indicate if the breadcrumb should be hidden when this node is active.
Expand Down Expand Up @@ -123,6 +135,8 @@ export interface NodeDefinition<
title?: string;
/** App id or deeplink id */
link?: LinkId;
/** Cloud link id */
cloudLink?: CloudLinkId;
/** Optional icon for the navigation node. Note: not all navigation depth will render the icon */
icon?: string;
/** Optional children of the navigation node */
Expand All @@ -134,7 +148,7 @@ export interface NodeDefinition<
/**
* Optional function to get the active state. This function is called whenever the location changes.
*/
getIsActive?: (location: Location) => boolean;
getIsActive?: GetIsActiveFn;

/**
* Optional flag to indicate if the breadcrumb should be hidden when this node is active.
Expand Down
14 changes: 14 additions & 0 deletions packages/shared-ux/chrome/navigation/mocks/src/jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,19 @@ export const getServicesMock = ({
navigateToUrl,
onProjectNavigationChange: jest.fn(),
activeNodes$: of(activeNodes),
cloudLinks: {
billingAndSub: {
title: 'Mock Billing & Subscriptions',
href: 'https://cloud.elastic.co/account/billing',
},
performance: {
title: 'Mock Performance',
href: 'https://cloud.elastic.co/deployments/123456789/performance',
},
userAndRoles: {
title: 'Mock Users & Roles',
href: 'https://cloud.elastic.co/deployments/123456789/security/users',
},
},
};
};
1 change: 1 addition & 0 deletions packages/shared-ux/chrome/navigation/mocks/src/navlinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const allNavLinks: AppDeepLinkId[] = [
'discover',
'fleet',
'integrations',
'management',
'management:api_keys',
'management:cases',
'management:cross_cluster_replication',
Expand Down
14 changes: 14 additions & 0 deletions packages/shared-ux/chrome/navigation/mocks/src/storybook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ export class StorybookMock extends AbstractStorybookMock<{}, NavigationServices>
navLinks$: params.navLinks$ ?? new BehaviorSubject([]),
onProjectNavigationChange: params.onProjectNavigationChange ?? (() => undefined),
activeNodes$: new BehaviorSubject([]),
cloudLinks: {
billingAndSub: {
title: 'Billing & Subscriptions',
href: 'https://cloud.elastic.co/account/billing',
},
performance: {
title: 'Performance',
href: 'https://cloud.elastic.co/deployments/123456789/performance',
},
userAndRoles: {
title: 'Users & Roles',
href: 'https://cloud.elastic.co/deployments/123456789/security/users',
},
},
};
}

Expand Down
58 changes: 58 additions & 0 deletions packages/shared-ux/chrome/navigation/src/cloud_links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { CloudLinkId } from '@kbn/core-chrome-browser';
import type { CloudStart } from '@kbn/cloud-plugin/public';

export type CloudLinks = {
[id in CloudLinkId]?: {
title: string;
href: string;
};
};

export const getCloudLinks = (cloud: CloudStart): CloudLinks => {
const { billingUrl, performanceUrl, usersAndRolesUrl } = cloud;

const links: CloudLinks = {};

if (usersAndRolesUrl) {
links.userAndRoles = {
title: i18n.translate(
'sharedUXPackages.chrome.sideNavigation.cloudLinks.usersAndRolesLinkText',
{
defaultMessage: 'Users and roles',
}
),
href: usersAndRolesUrl,
};
}

if (performanceUrl) {
links.performance = {
title: i18n.translate(
'sharedUXPackages.chrome.sideNavigation.cloudLinks.performanceLinkText',
{
defaultMessage: 'Performance',
}
),
href: performanceUrl,
};
}

if (billingUrl) {
links.billingAndSub = {
title: i18n.translate('sharedUXPackages.chrome.sideNavigation.cloudLinks.billingLinkText', {
defaultMessage: 'Billing and subscription',
}),
href: billingUrl,
};
}

return links;
};
8 changes: 6 additions & 2 deletions packages/shared-ux/chrome/navigation/src/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* Side Public License, v 1.
*/

import React, { FC, useContext } from 'react';
import React, { FC, useContext, useMemo } from 'react';
import { NavigationKibanaDependencies, NavigationServices } from '../types';
import { CloudLinks, getCloudLinks } from './cloud_links';

const Context = React.createContext<NavigationServices | null>(null);

Expand All @@ -25,11 +26,13 @@ export const NavigationKibanaProvider: FC<NavigationKibanaDependencies> = ({
children,
...dependencies
}) => {
const { core, serverless } = dependencies;
const { core, serverless, cloud } = dependencies;
const { chrome, http } = core;
const { basePath } = http;
const { navigateToUrl } = core.application;

const cloudLinks: CloudLinks = useMemo(() => (cloud ? getCloudLinks(cloud) : {}), [cloud]);

const value: NavigationServices = {
basePath,
recentlyAccessed$: chrome.recentlyAccessed.get$(),
Expand All @@ -38,6 +41,7 @@ export const NavigationKibanaProvider: FC<NavigationKibanaDependencies> = ({
navIsOpen: true,
onProjectNavigationChange: serverless.setNavigation,
activeNodes$: serverless.getActiveNavigationNodes$(),
cloudLinks,
};

return <Context.Provider value={value}>{children}</Context.Provider>;
Expand Down
Loading

0 comments on commit 6b24ae1

Please sign in to comment.