mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Stateful sidenav] Update feedback urls (#198143)
This commit is contained in:
parent
2162c56b59
commit
89fe54815d
29 changed files with 113 additions and 77 deletions
|
@ -36,6 +36,7 @@ import type {
|
|||
ChromeSetProjectBreadcrumbsParams,
|
||||
NavigationTreeDefinition,
|
||||
AppDeepLinkId,
|
||||
SolutionId,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import type {
|
||||
|
@ -343,7 +344,10 @@ export class ChromeService {
|
|||
LinkId extends AppDeepLinkId = AppDeepLinkId,
|
||||
Id extends string = string,
|
||||
ChildrenId extends string = Id
|
||||
>(id: string, navigationTree$: Observable<NavigationTreeDefinition<LinkId, Id, ChildrenId>>) {
|
||||
>(
|
||||
id: SolutionId,
|
||||
navigationTree$: Observable<NavigationTreeDefinition<LinkId, Id, ChildrenId>>
|
||||
) {
|
||||
validateChromeStyle();
|
||||
projectNavigation.initNavigation(id, navigationTree$);
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('initNavigation()', () => {
|
|||
|
||||
beforeAll(() => {
|
||||
projectNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -185,7 +185,7 @@ describe('initNavigation()', () => {
|
|||
const { projectNavigation: projNavigation, getNavigationTree: getNavTree } =
|
||||
setupInitNavigation();
|
||||
projNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -210,7 +210,7 @@ describe('initNavigation()', () => {
|
|||
const { projectNavigation: projNavigation } = setupInitNavigation();
|
||||
|
||||
projNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -399,7 +399,7 @@ describe('initNavigation()', () => {
|
|||
|
||||
// 2. initNavigation() is called
|
||||
projectNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -427,7 +427,7 @@ describe('initNavigation()', () => {
|
|||
});
|
||||
|
||||
projectNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
// @ts-expect-error - We pass a non valid cloudLink that is not TS valid
|
||||
of({
|
||||
body: [
|
||||
|
@ -533,7 +533,7 @@ describe('breadcrumbs', () => {
|
|||
const obs = subj.asObservable();
|
||||
|
||||
if (initiateNavigation) {
|
||||
projectNavigation.initNavigation('foo', obs);
|
||||
projectNavigation.initNavigation('es', obs);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -740,7 +740,7 @@ describe('breadcrumbs', () => {
|
|||
{ text: 'custom1', href: '/custom1' },
|
||||
{ text: 'custom2', href: '/custom1/custom2' },
|
||||
]);
|
||||
projectNavigation.initNavigation('foo', of(mockNavigation)); // init navigation
|
||||
projectNavigation.initNavigation('es', of(mockNavigation)); // init navigation
|
||||
|
||||
const breadcrumbs = await firstValueFrom(projectNavigation.getProjectBreadcrumbs$());
|
||||
expect(breadcrumbs).toHaveLength(4);
|
||||
|
@ -779,7 +779,7 @@ describe('getActiveNodes$()', () => {
|
|||
expect(activeNodes).toEqual([]);
|
||||
|
||||
projectNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -835,7 +835,7 @@ describe('getActiveNodes$()', () => {
|
|||
expect(activeNodes).toEqual([]);
|
||||
|
||||
projectNavigation.initNavigation<any>(
|
||||
'foo',
|
||||
'es',
|
||||
of({
|
||||
body: [
|
||||
{
|
||||
|
@ -889,7 +889,7 @@ describe('getActiveNodes$()', () => {
|
|||
|
||||
describe('solution navigations', () => {
|
||||
const solution1: SolutionNavigationDefinition<any> = {
|
||||
id: 'solution1',
|
||||
id: 'es',
|
||||
title: 'Solution 1',
|
||||
icon: 'logoSolution1',
|
||||
homePage: 'discover',
|
||||
|
@ -897,7 +897,7 @@ describe('solution navigations', () => {
|
|||
};
|
||||
|
||||
const solution2: SolutionNavigationDefinition<any> = {
|
||||
id: 'solution2',
|
||||
id: 'oblt',
|
||||
title: 'Solution 2',
|
||||
icon: 'logoSolution2',
|
||||
homePage: 'app2',
|
||||
|
@ -906,7 +906,7 @@ describe('solution navigations', () => {
|
|||
};
|
||||
|
||||
const solution3: SolutionNavigationDefinition<any> = {
|
||||
id: 'solution3',
|
||||
id: 'security',
|
||||
title: 'Solution 3',
|
||||
icon: 'logoSolution3',
|
||||
homePage: 'discover',
|
||||
|
@ -943,30 +943,30 @@ describe('solution navigations', () => {
|
|||
}
|
||||
|
||||
{
|
||||
projectNavigation.updateSolutionNavigations({ 1: solution1, 2: solution2 });
|
||||
projectNavigation.updateSolutionNavigations({ es: solution1, oblt: solution2 });
|
||||
|
||||
const solutionNavs = await lastValueFrom(
|
||||
projectNavigation.getSolutionsNavDefinitions$().pipe(take(1))
|
||||
);
|
||||
expect(solutionNavs).toEqual({ 1: solution1, 2: solution2 });
|
||||
expect(solutionNavs).toEqual({ es: solution1, oblt: solution2 });
|
||||
}
|
||||
|
||||
{
|
||||
// Test partial update
|
||||
projectNavigation.updateSolutionNavigations({ 3: solution3 }, false);
|
||||
projectNavigation.updateSolutionNavigations({ security: solution3 }, false);
|
||||
const solutionNavs = await lastValueFrom(
|
||||
projectNavigation.getSolutionsNavDefinitions$().pipe(take(1))
|
||||
);
|
||||
expect(solutionNavs).toEqual({ 1: solution1, 2: solution2, 3: solution3 });
|
||||
expect(solutionNavs).toEqual({ es: solution1, oblt: solution2, security: solution3 });
|
||||
}
|
||||
|
||||
{
|
||||
// Test full replacement
|
||||
projectNavigation.updateSolutionNavigations({ 4: solution3 }, true);
|
||||
projectNavigation.updateSolutionNavigations({ security: solution3 }, true);
|
||||
const solutionNavs = await lastValueFrom(
|
||||
projectNavigation.getSolutionsNavDefinitions$().pipe(take(1))
|
||||
);
|
||||
expect(solutionNavs).toEqual({ 4: solution3 });
|
||||
expect(solutionNavs).toEqual({ security: solution3 });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -980,8 +980,8 @@ describe('solution navigations', () => {
|
|||
expect(activeSolution).toBeNull();
|
||||
}
|
||||
|
||||
projectNavigation.changeActiveSolutionNavigation('2'); // Set **before** the navs are registered
|
||||
projectNavigation.updateSolutionNavigations({ 1: solution1, 2: solution2 });
|
||||
projectNavigation.changeActiveSolutionNavigation('oblt'); // Set **before** the navs are registered
|
||||
projectNavigation.updateSolutionNavigations({ es: solution1, oblt: solution2 });
|
||||
|
||||
{
|
||||
const activeSolution = await lastValueFrom(
|
||||
|
@ -994,7 +994,7 @@ describe('solution navigations', () => {
|
|||
expect(activeSolution).toEqual(rest);
|
||||
}
|
||||
|
||||
projectNavigation.changeActiveSolutionNavigation('1'); // Set **after** the navs are registered
|
||||
projectNavigation.changeActiveSolutionNavigation('es'); // Set **after** the navs are registered
|
||||
|
||||
{
|
||||
const activeSolution = await lastValueFrom(
|
||||
|
@ -1027,7 +1027,7 @@ describe('solution navigations', () => {
|
|||
|
||||
{
|
||||
const fooSolution: SolutionNavigationDefinition<any> = {
|
||||
id: 'fooSolution',
|
||||
id: 'es',
|
||||
title: 'Foo solution',
|
||||
icon: 'logoSolution',
|
||||
homePage: 'discover',
|
||||
|
@ -1053,8 +1053,8 @@ describe('solution navigations', () => {
|
|||
}),
|
||||
};
|
||||
|
||||
projectNavigation.changeActiveSolutionNavigation('foo');
|
||||
projectNavigation.updateSolutionNavigations({ foo: fooSolution });
|
||||
projectNavigation.changeActiveSolutionNavigation('es');
|
||||
projectNavigation.updateSolutionNavigations({ es: fooSolution });
|
||||
|
||||
projectNavigation.setPanelSelectedNode('link2'); // Set the selected node using its id
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import type {
|
|||
NavigationTreeDefinition,
|
||||
SolutionNavigationDefinitions,
|
||||
CloudLinks,
|
||||
SolutionId,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { InternalHttpStart } from '@kbn/core-http-browser-internal';
|
||||
import {
|
||||
|
@ -86,9 +87,9 @@ export class ProjectNavigationService {
|
|||
private readonly solutionNavDefinitions$ = new BehaviorSubject<SolutionNavigationDefinitions>({});
|
||||
// As the active definition **id** and the definitions are set independently, one before the other without
|
||||
// any guarantee of order, we need to store the next active definition id in a separate BehaviorSubject
|
||||
private readonly nextSolutionNavDefinitionId$ = new BehaviorSubject<string | null>(null);
|
||||
private readonly nextSolutionNavDefinitionId$ = new BehaviorSubject<SolutionId | null>(null);
|
||||
// The active solution navigation definition id that has been initiated and is currently active
|
||||
private readonly activeSolutionNavDefinitionId$ = new BehaviorSubject<string | null>(null);
|
||||
private readonly activeSolutionNavDefinitionId$ = new BehaviorSubject<SolutionId | null>(null);
|
||||
private readonly location$ = new BehaviorSubject<Location>(createLocation('/'));
|
||||
private deepLinksMap$: Observable<Record<string, ChromeNavLink>> = of({});
|
||||
private cloudLinks$ = new BehaviorSubject<CloudLinks>({});
|
||||
|
@ -138,7 +139,7 @@ export class ProjectNavigationService {
|
|||
return this.projectName$.asObservable();
|
||||
},
|
||||
initNavigation: <LinkId extends AppDeepLinkId = AppDeepLinkId>(
|
||||
id: string,
|
||||
id: SolutionId,
|
||||
navTreeDefinition$: Observable<NavigationTreeDefinition<LinkId>>
|
||||
) => {
|
||||
this.initNavigation(id, navTreeDefinition$);
|
||||
|
@ -202,7 +203,7 @@ export class ProjectNavigationService {
|
|||
* @param id Id for the navigation tree definition
|
||||
* @param navTreeDefinition$ The navigation tree definition
|
||||
*/
|
||||
private initNavigation(id: string, navTreeDefinition$: Observable<NavigationTreeDefinition>) {
|
||||
private initNavigation(id: SolutionId, navTreeDefinition$: Observable<NavigationTreeDefinition>) {
|
||||
if (this.activeSolutionNavDefinitionId$.getValue() === id) return;
|
||||
|
||||
if (this.navigationChangeSubscription) {
|
||||
|
@ -220,7 +221,7 @@ export class ProjectNavigationService {
|
|||
.pipe(
|
||||
takeUntil(this.stop$),
|
||||
map(([def, deepLinksMap, cloudLinks]) => {
|
||||
return parseNavigationTree(def, {
|
||||
return parseNavigationTree(id, def, {
|
||||
deepLinks: deepLinksMap,
|
||||
cloudLinks,
|
||||
});
|
||||
|
@ -382,7 +383,7 @@ export class ProjectNavigationService {
|
|||
this.projectHome$.next(homeHref);
|
||||
}
|
||||
|
||||
private changeActiveSolutionNavigation(id: string | null) {
|
||||
private changeActiveSolutionNavigation(id: SolutionId | null) {
|
||||
if (this.nextSolutionNavDefinitionId$.getValue() === id) return;
|
||||
this.nextSolutionNavDefinitionId$.next(id);
|
||||
}
|
||||
|
@ -400,7 +401,7 @@ export class ProjectNavigationService {
|
|||
if (!definitions[id]) return null;
|
||||
|
||||
// We strip out the sideNavComponent from the definition as it should only be used internally
|
||||
const { sideNavComponent, ...definition } = definitions[id];
|
||||
const { sideNavComponent, ...definition } = definitions[id]!;
|
||||
return definition;
|
||||
})
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ import type {
|
|||
CloudLinkId,
|
||||
CloudLinks,
|
||||
ItemDefinition,
|
||||
SolutionId,
|
||||
} from '@kbn/core-chrome-browser/src';
|
||||
import type { Location } from 'history';
|
||||
import type { MouseEventHandler } from 'react';
|
||||
|
@ -364,6 +365,7 @@ const isRecentlyAccessedDefinition = (
|
|||
};
|
||||
|
||||
export const parseNavigationTree = (
|
||||
id: SolutionId,
|
||||
navigationTreeDef: NavigationTreeDefinition,
|
||||
{ deepLinks, cloudLinks }: { deepLinks: Record<string, ChromeNavLink>; cloudLinks: CloudLinks }
|
||||
): {
|
||||
|
@ -376,7 +378,7 @@ export const parseNavigationTree = (
|
|||
const navigationTree: ChromeProjectNavigationNode[] = [];
|
||||
|
||||
// Contains UI layout information (body, footer) and render "special" blocks like recently accessed.
|
||||
const navigationTreeUI: NavigationTreeDefinitionUI = { body: [] };
|
||||
const navigationTreeUI: NavigationTreeDefinitionUI = { id, body: [] };
|
||||
|
||||
const initNodeAndChildren = (
|
||||
node: GroupDefinition | ItemDefinition | NodeDefinition,
|
||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
|||
NavigationTreeDefinitionUI,
|
||||
CloudURLs,
|
||||
SolutionNavigationDefinitions,
|
||||
SolutionId,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
|
@ -66,7 +67,7 @@ export interface InternalChromeStart extends ChromeStart {
|
|||
Id extends string = string,
|
||||
ChildrenId extends string = Id
|
||||
>(
|
||||
id: string,
|
||||
id: SolutionId,
|
||||
navigationTree$: Observable<NavigationTreeDefinition<LinkId, Id, ChildrenId>>
|
||||
): void;
|
||||
|
||||
|
@ -117,6 +118,6 @@ export interface InternalChromeStart extends ChromeStart {
|
|||
* @param id The id of the active solution navigation. If `null` is provided, the solution navigation
|
||||
* will be replaced with the legacy Kibana navigation.
|
||||
*/
|
||||
changeActiveSolutionNavigation(id: string | null): void;
|
||||
changeActiveSolutionNavigation(id: SolutionId | null): void;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -60,4 +60,5 @@ export type {
|
|||
SolutionNavigationDefinitions,
|
||||
EuiSideNavItemTypeEnhanced,
|
||||
RenderAs,
|
||||
SolutionId,
|
||||
} from './src';
|
||||
|
|
|
@ -38,6 +38,7 @@ export type {
|
|||
PanelSelectedNode,
|
||||
AppDeepLinkId,
|
||||
AppId,
|
||||
SolutionId,
|
||||
CloudLinkId,
|
||||
CloudLink,
|
||||
CloudLinks,
|
||||
|
|
|
@ -42,6 +42,8 @@ import type { AppId as SharedApp, DeepLinkId as SharedLink } from '@kbn/deeplink
|
|||
import type { ChromeNavLink } from './nav_links';
|
||||
import type { ChromeRecentlyAccessedHistoryItem } from './recently_accessed';
|
||||
|
||||
export type SolutionId = 'es' | 'oblt' | 'security';
|
||||
|
||||
/** @public */
|
||||
export type AppId =
|
||||
| DevToolsApp
|
||||
|
@ -414,6 +416,7 @@ export interface NavigationTreeDefinition<
|
|||
* with their corresponding "deepLink"...)
|
||||
*/
|
||||
export interface NavigationTreeDefinitionUI {
|
||||
id: SolutionId;
|
||||
body: Array<ChromeProjectNavigationNode | RecentlyAccessedDefinition>;
|
||||
footer?: Array<ChromeProjectNavigationNode | RecentlyAccessedDefinition>;
|
||||
}
|
||||
|
@ -429,7 +432,7 @@ export interface NavigationTreeDefinitionUI {
|
|||
|
||||
export interface SolutionNavigationDefinition<LinkId extends AppDeepLinkId = AppDeepLinkId> {
|
||||
/** Unique id for the solution navigation. */
|
||||
id: string;
|
||||
id: SolutionId;
|
||||
/** Title for the solution navigation. */
|
||||
title: string;
|
||||
/** The navigation tree definition */
|
||||
|
@ -442,9 +445,9 @@ export interface SolutionNavigationDefinition<LinkId extends AppDeepLinkId = App
|
|||
homePage?: LinkId;
|
||||
}
|
||||
|
||||
export interface SolutionNavigationDefinitions {
|
||||
[id: string]: SolutionNavigationDefinition;
|
||||
}
|
||||
export type SolutionNavigationDefinitions = {
|
||||
[id in SolutionId]?: SolutionNavigationDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporary helper interface while we have to maintain both the legacy side navigation
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('Active node', () => {
|
|||
];
|
||||
|
||||
const { findByTestId } = renderNavigation({
|
||||
navTreeDef: of({ body: navigationBody }),
|
||||
navTreeDef: of({ id: 'es', body: navigationBody }),
|
||||
services: { activeNodes$: getActiveNodes$() },
|
||||
});
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('builds navigation tree', () => {
|
|||
test('render reference UI and build the navigation tree', async () => {
|
||||
const { findByTestId } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'group1',
|
||||
|
@ -107,6 +108,7 @@ describe('builds navigation tree', () => {
|
|||
{
|
||||
const { findByTestId, unmount } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [accordionNode],
|
||||
}),
|
||||
services: { navigateToUrl },
|
||||
|
@ -121,6 +123,7 @@ describe('builds navigation tree', () => {
|
|||
{
|
||||
const { findByTestId } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
...accordionNode,
|
||||
|
@ -165,6 +168,7 @@ describe('builds navigation tree', () => {
|
|||
// Side nav is collapsed
|
||||
const { queryAllByTestId, unmount } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [nodes],
|
||||
}),
|
||||
services: { isSideNavCollapsed: true },
|
||||
|
@ -180,6 +184,7 @@ describe('builds navigation tree', () => {
|
|||
// Side nav is not collapsed
|
||||
const { queryAllByTestId, unmount } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [nodes],
|
||||
}),
|
||||
services: { isSideNavCollapsed: false }, // No conversion to accordion
|
||||
|
@ -195,6 +200,7 @@ describe('builds navigation tree', () => {
|
|||
// Panel opener with a link
|
||||
const { queryAllByTestId, unmount } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
...nodes,
|
||||
|
@ -238,6 +244,7 @@ describe('builds navigation tree', () => {
|
|||
|
||||
const { findByTestId } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [node],
|
||||
}),
|
||||
services: { navigateToUrl, eventTracker: new EventTracker({ reportEvent }) },
|
||||
|
@ -276,6 +283,7 @@ describe('builds navigation tree', () => {
|
|||
|
||||
const { findByTestId } = renderNavigation({
|
||||
navTreeDef: of({
|
||||
id: 'es',
|
||||
body: [node],
|
||||
}),
|
||||
services: { navigateToUrl },
|
||||
|
@ -290,6 +298,7 @@ describe('builds navigation tree', () => {
|
|||
|
||||
test('should not render the group if it does not have children', async () => {
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -338,6 +347,7 @@ describe('builds navigation tree', () => {
|
|||
]);
|
||||
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [{ type: 'recentlyAccessed' }],
|
||||
};
|
||||
|
||||
|
@ -364,6 +374,7 @@ describe('builds navigation tree', () => {
|
|||
]);
|
||||
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [{ type: 'recentlyAccessed' }],
|
||||
};
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import { renderNavigation } from './utils';
|
|||
describe('Panel', () => {
|
||||
test('should render group as panel opener', async () => {
|
||||
const navigationTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -60,6 +61,7 @@ describe('Panel', () => {
|
|||
|
||||
test('should not render group if all children are hidden', async () => {
|
||||
const navigationTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -146,6 +148,7 @@ describe('Panel', () => {
|
|||
]);
|
||||
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -196,6 +199,7 @@ describe('Panel', () => {
|
|||
describe('auto generated content', () => {
|
||||
test('should rendre block groups with title', async () => {
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -262,6 +266,7 @@ describe('Panel', () => {
|
|||
|
||||
test('should rendre block groups without title', async () => {
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
@ -327,6 +332,7 @@ describe('Panel', () => {
|
|||
|
||||
test('should rendre accordion groups', async () => {
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
|
|
|
@ -10,11 +10,20 @@
|
|||
import { EuiButton, EuiCallOut, useEuiTheme, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
|
||||
const feedbackUrl = 'https://ela.st/nav-feedback';
|
||||
const feedbackUrls: { [id in SolutionId]: string } = {
|
||||
es: 'https://ela.st/search-nav-feedback',
|
||||
oblt: 'https://ela.st/o11y-nav-feedback',
|
||||
security: 'https://ela.st/security-nav-feedback',
|
||||
};
|
||||
const FEEDBACK_BTN_KEY = 'core.chrome.sideNav.feedbackBtn';
|
||||
|
||||
export const FeedbackBtn: FC = () => {
|
||||
interface Props {
|
||||
solutionId: SolutionId;
|
||||
}
|
||||
|
||||
export const FeedbackBtn: FC<Props> = ({ solutionId }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [showCallOut, setShowCallOut] = useState(
|
||||
sessionStorage.getItem(FEEDBACK_BTN_KEY) !== 'hidden'
|
||||
|
@ -26,7 +35,7 @@ export const FeedbackBtn: FC = () => {
|
|||
};
|
||||
|
||||
const onClick = () => {
|
||||
window.open(feedbackUrl, '_blank');
|
||||
window.open(feedbackUrls[solutionId], '_blank');
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ const NavigationWrapper: FC<Props & Omit<Partial<EuiCollapsibleNavBetaProps>, 'c
|
|||
};
|
||||
|
||||
const groupExamplesNavigationTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
// My custom project
|
||||
{
|
||||
|
@ -257,6 +258,7 @@ export const GroupsExamples = (args: NavigationServices) => {
|
|||
};
|
||||
|
||||
const navigationTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
// My custom project
|
||||
{
|
||||
|
@ -568,6 +570,7 @@ const panelContentProvider: ContentProvider = (id: string) => {
|
|||
};
|
||||
|
||||
const navigationTreeWithPanels: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
// My custom project
|
||||
{
|
||||
|
|
|
@ -52,7 +52,8 @@ const NavigationComp: FC<Props> = ({ navigationTree$, dataTestSubj, panelContent
|
|||
useNavigationService();
|
||||
|
||||
const activeNodes = useObservable(activeNodes$, []);
|
||||
const navigationTree = useObservable(navigationTree$, { body: [] });
|
||||
const navigationTree = useObservable(navigationTree$, { id: 'es', body: [] });
|
||||
const { id: solutionId } = navigationTree;
|
||||
const isFeedbackBtnVisible = useObservable(isFeedbackBtnVisible$, false);
|
||||
|
||||
const contextValue = useMemo<Context>(
|
||||
|
@ -95,7 +96,7 @@ const NavigationComp: FC<Props> = ({ navigationTree$, dataTestSubj, panelContent
|
|||
<EuiFlexItem>{renderNodes(navigationTree.body)}</EuiFlexItem>
|
||||
{isFeedbackBtnVisible && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<FeedbackBtn />
|
||||
<FeedbackBtn solutionId={solutionId} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -106,7 +106,7 @@ describe('Navigation Plugin', () => {
|
|||
await new Promise((resolve) => setTimeout(resolve));
|
||||
|
||||
const definition = {
|
||||
id: 'es',
|
||||
id: 'es' as const,
|
||||
title: 'Elasticsearch',
|
||||
navigationTree$: of({ body: [] }),
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@kbn/core/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { Space } from '@kbn/spaces-plugin/public';
|
||||
import type { SolutionNavigationDefinition } from '@kbn/core-chrome-browser';
|
||||
import type { SolutionId, SolutionNavigationDefinition } from '@kbn/core-chrome-browser';
|
||||
import { InternalChromeStart } from '@kbn/core-chrome-browser-internal';
|
||||
import type { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation';
|
||||
import type {
|
||||
|
@ -195,7 +195,7 @@ export class NavigationPublicPlugin
|
|||
}
|
||||
}
|
||||
|
||||
if (isProjectNav) {
|
||||
if (isProjectNav && solutionView !== 'classic') {
|
||||
chrome.project.changeActiveSolutionNavigation(solutionView!);
|
||||
}
|
||||
}
|
||||
|
@ -210,6 +210,6 @@ function getIsProjectNav(solutionView?: string) {
|
|||
return Boolean(solutionView) && isKnownSolutionView(solutionView);
|
||||
}
|
||||
|
||||
function isKnownSolutionView(solution?: string) {
|
||||
function isKnownSolutionView(solution?: string): solution is SolutionId {
|
||||
return Boolean(solution) && ['oblt', 'es', 'security'].includes(solution!);
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type { OnBoardingDefaultSolution } from './types';
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { OnBoardingDefaultSolution } from './types';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
|
||||
/**
|
||||
* Cloud does not type the value of the "use case" that is set during onboarding for a deployment. Any string can
|
||||
|
@ -14,12 +14,12 @@ import type { OnBoardingDefaultSolution } from './types';
|
|||
* @param value The solution value set by Cloud.
|
||||
* @returns The default solution value for onboarding that matches Kibana naming.
|
||||
*/
|
||||
export function parseOnboardingSolution(value?: string): OnBoardingDefaultSolution | undefined {
|
||||
export function parseOnboardingSolution(value?: string): SolutionId | undefined {
|
||||
if (!value) return;
|
||||
|
||||
const solutions: Array<{
|
||||
cloudValue: 'search' | 'elasticsearch' | 'observability' | 'security';
|
||||
kibanaValue: OnBoardingDefaultSolution;
|
||||
kibanaValue: SolutionId;
|
||||
}> = [
|
||||
{
|
||||
cloudValue: 'search',
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type OnBoardingDefaultSolution = 'es' | 'oblt' | 'security';
|
||||
|
||||
export interface ElasticsearchConfigType {
|
||||
elasticsearch_url?: string;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import type { OnBoardingDefaultSolution } from '../common';
|
||||
|
||||
export interface CloudStart {
|
||||
/**
|
||||
|
@ -192,7 +192,7 @@ export interface CloudSetup {
|
|||
/**
|
||||
* The default solution selected during onboarding.
|
||||
*/
|
||||
defaultSolution?: OnBoardingDefaultSolution;
|
||||
defaultSolution?: SolutionId;
|
||||
};
|
||||
/**
|
||||
* `true` when running on Serverless Elastic Cloud
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
import type { Logger } from '@kbn/logging';
|
||||
import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
|
||||
import type { CloudConfigType } from './config';
|
||||
import { registerCloudUsageCollector } from './collectors';
|
||||
import type { OnBoardingDefaultSolution } from '../common';
|
||||
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
|
||||
import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url';
|
||||
import { decodeCloudId, DecodedCloudId } from '../common/decode_cloud_id';
|
||||
|
@ -108,7 +108,7 @@ export interface CloudSetup {
|
|||
/**
|
||||
* The default solution selected during onboarding.
|
||||
*/
|
||||
defaultSolution?: OnBoardingDefaultSolution;
|
||||
defaultSolution?: SolutionId;
|
||||
};
|
||||
/**
|
||||
* `true` when running on Serverless Elastic Cloud
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/logging-mocks",
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
ChromeSetProjectBreadcrumbsParams,
|
||||
SideNavComponent,
|
||||
NavigationTreeDefinition,
|
||||
SolutionId,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
@ -26,7 +27,7 @@ export interface ServerlessPluginStart {
|
|||
) => void;
|
||||
setProjectHome(homeHref: string): void;
|
||||
initNavigation(
|
||||
id: string,
|
||||
id: SolutionId,
|
||||
navigationTree$: Observable<NavigationTreeDefinition>,
|
||||
config?: {
|
||||
dataTestSubj?: string;
|
||||
|
|
|
@ -149,7 +149,7 @@ export class ServerlessSearchPlugin
|
|||
serverless.setProjectHome(services.searchIndices.startRoute);
|
||||
|
||||
const navigationTree$ = of(navigationTree());
|
||||
serverless.initNavigation('search', navigationTree$, { dataTestSubj: 'svlSearchSideNav' });
|
||||
serverless.initNavigation('es', navigationTree$, { dataTestSubj: 'svlSearchSideNav' });
|
||||
|
||||
const extendCardNavDefinitions = serverless.getNavigationCards(
|
||||
security.authz.isRoleManagementEnabled()
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
|
||||
import type { SOLUTION_VIEW_CLASSIC } from '../../constants';
|
||||
|
||||
export type SolutionView = OnBoardingDefaultSolution | typeof SOLUTION_VIEW_CLASSIC;
|
||||
export type SolutionView = SolutionId | typeof SOLUTION_VIEW_CLASSIC;
|
||||
|
||||
/**
|
||||
* A Space.
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiButtonEmpty, EuiLink, EuiText, EuiTourStep } from '@elastic/eui';
|
|||
import React from 'react';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
|
||||
import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
|
@ -26,7 +26,7 @@ const LearnMoreLink = () => (
|
|||
</EuiLink>
|
||||
);
|
||||
|
||||
const solutionMap: Record<OnBoardingDefaultSolution, string> = {
|
||||
const solutionMap: Record<SolutionId, string> = {
|
||||
es: i18n.translate('xpack.spaces.navControl.tour.esSolution', {
|
||||
defaultMessage: 'Search',
|
||||
}),
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common';
|
||||
import type { Logger, SavedObjectsRepository, SavedObjectsServiceStart } from '@kbn/core/server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
|
@ -15,7 +15,7 @@ import { DEFAULT_SPACE_ID } from '../../common/constants';
|
|||
interface Deps {
|
||||
getSavedObjects: () => Promise<Pick<SavedObjectsServiceStart, 'createInternalRepository'>>;
|
||||
logger: Logger;
|
||||
solution?: OnBoardingDefaultSolution;
|
||||
solution?: SolutionId;
|
||||
}
|
||||
|
||||
export async function createDefaultSpace({ getSavedObjects, logger, solution }: Deps) {
|
||||
|
|
|
@ -19,9 +19,9 @@ import {
|
|||
timer,
|
||||
} from 'rxjs';
|
||||
|
||||
import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common';
|
||||
import type { CoreSetup, Logger, SavedObjectsServiceStart, ServiceStatus } from '@kbn/core/server';
|
||||
import { ServiceStatusLevels } from '@kbn/core/server';
|
||||
import type { SolutionId } from '@kbn/core-chrome-browser';
|
||||
import type { ILicense } from '@kbn/licensing-plugin/server';
|
||||
|
||||
import { createDefaultSpace } from './create_default_space';
|
||||
|
@ -33,7 +33,7 @@ interface Deps {
|
|||
license$: Observable<ILicense>;
|
||||
spacesLicense: SpacesLicense;
|
||||
logger: Logger;
|
||||
solution?: OnBoardingDefaultSolution;
|
||||
solution?: SolutionId;
|
||||
}
|
||||
|
||||
export const RETRY_SCALE_DURATION = 100;
|
||||
|
|
|
@ -52,7 +52,8 @@
|
|||
"@kbn/core-logging-browser-mocks",
|
||||
"@kbn/core-http-router-server-mocks",
|
||||
"@kbn/core-application-browser-mocks",
|
||||
"@kbn/ui-theme"
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/core-chrome-browser"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue