[Core] Deprecate nav link status (#176383)

This commit is contained in:
Sébastien Loix 2024-02-16 18:06:33 +00:00 committed by GitHub
parent 1f93119b86
commit 9db8d2558c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
116 changed files with 640 additions and 988 deletions

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { BfetchPublicSetup, BfetchPublicStart } from '@kbn/bfetch-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { mount } from './mount';
@ -40,7 +40,7 @@ export class BfetchExplorerPlugin implements Plugin {
core.application.register({
id: 'bfetch-explorer',
title: 'bfetch explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core, explorer),
});

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { AppNavLinkStatus } from '@kbn/core-application-browser';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { StartDeps, SetupDeps } from './types';
@ -23,7 +22,7 @@ export class ContentManagementExamplesPlugin
core.application.register({
id: `contentManagementExamples`,
title: `Content Management Examples`,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp } = await import('./examples');
const [coreStart, deps] = await core.getStartServices();

View file

@ -18,7 +18,6 @@
"@kbn/core",
"@kbn/developer-examples-plugin",
"@kbn/content-management-plugin",
"@kbn/core-application-browser",
"@kbn/shared-ux-link-redirect-app",
"@kbn/content-management-table-list-view",
"@kbn/content-management-table-list-view-table",

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
@ -35,7 +29,7 @@ export class ControlsExamplePlugin
core.application.register({
id: PLUGIN_ID,
title: 'Controls examples',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
@ -25,7 +25,7 @@ export class DataViewFieldEditorPlugin implements Plugin<void, void, SetupDeps,
core.application.register({
id: 'dataViewFieldEditorExample',
title: 'Data view field editor example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -14,13 +14,7 @@ import {
EuiWrappingPopover,
IconType,
} from '@elastic/eui';
import {
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
SimpleSavedObject,
} from '@kbn/core/public';
import { CoreSetup, CoreStart, Plugin, SimpleSavedObject } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
import { noop } from 'lodash';
@ -50,7 +44,7 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount() {
plugins.discover?.locator?.navigate(
{ profile: 'customization-examples' },

View file

@ -7,7 +7,7 @@
*/
import { EmbeddableExamplesStart } from '@kbn/embeddable-examples-plugin/public/plugin';
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { UiActionsService } from '@kbn/ui-actions-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { Start as InspectorStart } from '@kbn/inspector-plugin/public';
@ -30,7 +30,7 @@ export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDep
core.application.register({
id: 'embeddableExplorer',
title: 'Embeddable explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public';
import { Setup as InspectorSetup, Start as InspectorStart } from '@kbn/inspector-plugin/public';
@ -47,7 +47,7 @@ export class ExpressionsExplorerPlugin implements Plugin<void, void, SetupDeps,
core.application.register({
id: 'expressionsExplorer',
title: 'Expressions Explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -8,13 +8,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { KBN_FIELD_TYPES } from '@kbn/field-types';
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import {
@ -77,7 +71,7 @@ export class FieldFormatsExamplePlugin implements Plugin<void, void, SetupDeps,
core.application.register({
id: 'fieldFormatsExample',
title: 'Field formats example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount({ element }: AppMountParameters) {
const [, plugins] = await core.getStartServices();
ReactDOM.render(

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { AppNavLinkStatus } from '@kbn/core-application-browser';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME, exampleFileKind, MyImageMetadata } from '../common';
import { FilesExamplePluginsStart, FilesExamplePluginsSetup } from './types';
@ -32,7 +31,7 @@ export class FilesExamplePlugin
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -18,7 +18,6 @@
"@kbn/core",
"@kbn/files-plugin",
"@kbn/shared-ux-file-types",
"@kbn/core-application-browser",
"@kbn/shared-ux-file-context",
"@kbn/shared-ux-file-image",
"@kbn/shared-ux-file-upload",

View file

@ -7,7 +7,7 @@
*/
import { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public';
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { HelloLocator, HelloLocatorDefinition } from './locator';
interface SetupDeps {
@ -31,7 +31,7 @@ export class LocatorExamplesPlugin
core.application.register({
id: 'locatorExamples',
title: 'Access links examples',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp } = await import('./app');
return renderApp(

View file

@ -7,7 +7,7 @@
*/
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
interface SetupDeps {
@ -24,7 +24,7 @@ export class LocatorExplorerPlugin implements Plugin<void, void, SetupDeps, Star
core.application.register({
id: 'locatorExplorer',
title: 'Locator explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp } = await import('./app');
return renderApp(

View file

@ -9,7 +9,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import type { ExpressionsServiceSetup } from '@kbn/expressions-plugin/common';
import { ExpressionsServiceFork } from '@kbn/expressions-plugin/common/service/expressions_fork';
import { AppMountParameters, AppNavLinkStatus, CoreSetup, Plugin } from '@kbn/core/public';
import { AppMountParameters, CoreSetup, Plugin } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { App, ExpressionsContext } from './app';
import { countEvent, getEvents, pluck } from './functions';
@ -33,7 +33,7 @@ export class PartialResultsExamplePlugin implements Plugin<void, void, SetupDeps
application.register({
id: 'partialResultsExample',
title: 'Partial Results Example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async ({ element }: AppMountParameters) => {
ReactDOM.render(
<ExpressionsContext.Provider value={expressionsStart}>

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
@ -41,7 +35,7 @@ export class PortableDashboardsExamplePlugin
core.application.register({
id: PLUGIN_ID,
title: 'Portable dashboard examples',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { AppMountParameters, AppNavLinkStatus, CoreSetup, Plugin } from '@kbn/core/public';
import { AppMountParameters, CoreSetup, Plugin } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import image from './resizable_layout_examples.png';
@ -22,7 +22,7 @@ export class ResizableLayoutExamplesPlugin implements Plugin {
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params: AppMountParameters) => {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants';
import { mount } from './mount';
@ -26,7 +26,7 @@ export class ResponseStreamPlugin implements Plugin {
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
});

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
CoreStart,
Plugin,
CoreSetup,
AppMountParameters,
AppNavLinkStatus,
} from '@kbn/core/public';
import { CoreStart, Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { getServices } from './services';
@ -25,7 +19,7 @@ export class RoutingExamplePlugin implements Plugin<{}, {}, SetupDeps, {}> {
core.application.register({
id: 'routingExample',
title: 'Routing',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart] = await core.getStartServices();
const startServices = getServices(coreStart);

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
AppMountParameters,
CoreSetup,
CoreStart,
Plugin,
AppNavLinkStatus,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { AppPluginSetupDependencies, AppPluginStartDependencies } from './types';
import { MetricsTracking } from './services';
import { PLUGIN_NAME } from '../common';
@ -33,7 +27,7 @@ export class ScreenshotModeExamplePlugin implements Plugin<void, void> {
core.application.register({
id: 'screenshotModeExample',
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import {
AppPluginSetupDependencies,
AppPluginStartDependencies,
@ -40,7 +34,7 @@ export class SearchExamplesPlugin
core.application.register({
id: 'searchExamples',
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params: AppMountParameters) => {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { AppMountParameters, CoreSetup, Plugin, AppNavLinkStatus } from '@kbn/core/public';
import { AppMountParameters, CoreSetup, Plugin } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { AppPluginDependencies } from './with_data_services/types';
import image from './state_sync.png';
@ -37,7 +37,7 @@ export class StateContainersExamplesPlugin implements Plugin {
core.application.register({
id: 'stateContainersExampleBrowserHistory',
title: examples.stateContainersExampleBrowserHistory.title,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp, History } = await import('./todo/app');
const [coreStart] = await core.getStartServices();
@ -54,7 +54,7 @@ export class StateContainersExamplesPlugin implements Plugin {
core.application.register({
id: 'stateContainersExampleHashHistory',
title: examples.stateContainersExampleHashHistory.title,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp, History } = await import('./todo/app');
const [coreStart] = await core.getStartServices();
@ -72,7 +72,7 @@ export class StateContainersExamplesPlugin implements Plugin {
core.application.register({
id: 'stateContainersExampleWithDataServices',
title: examples.stateContainersExampleWithDataServices.title,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const { renderApp } = await import('./with_data_services/application');
const [coreStart, depsStart] = await core.getStartServices();

View file

@ -7,7 +7,7 @@
*/
import { UiActionsStart, UiActionsSetup } from '@kbn/ui-actions-plugin/public';
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import {
PHONE_TRIGGER,
@ -64,7 +64,7 @@ export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps
core.application.register({
id: 'uiActionsExplorer',
title: 'Ui Actions Explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import {
AppPluginSetupDependencies,
AppPluginStartDependencies,
@ -39,7 +33,7 @@ export class UnifiedFieldListExamplesPlugin
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params: AppMountParameters) => {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -20,4 +20,6 @@ export {
parseAppUrl,
relativeToAbsolute,
removeSlashes,
DEFAULT_APP_VISIBILITY,
DEFAULT_LINK_VISIBILITY,
} from './src/utils';

View file

@ -30,12 +30,12 @@ import { ApplicationService } from './application_service';
import {
App,
AppDeepLink,
AppNavLinkStatus,
AppStatus,
AppUpdater,
PublicAppInfo,
} from '@kbn/core-application-browser';
import { act } from 'react-dom/test-utils';
import { DEFAULT_APP_VISIBILITY } from './utils';
const createApp = (props: Partial<App>): App => {
return {
@ -109,7 +109,17 @@ describe('#setup()', () => {
setup.register(pluginId, createApp({ id: 'app1', updater$ }));
setup.register(
pluginId,
createApp({ id: 'app2', deepLinks: [{ id: 'subapp1', title: 'Subapp', path: '/subapp' }] })
createApp({
id: 'app2',
deepLinks: [
{
id: 'subapp1',
title: 'Subapp',
path: '/subapp',
visibleIn: undefined, // not specified
},
],
})
);
const { applications$ } = await service.start(startDeps);
@ -118,18 +128,18 @@ describe('#setup()', () => {
expect(applications.get('app1')).toEqual(
expect.objectContaining({
id: 'app1',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
})
);
expect(applications.get('app2')).toEqual(
expect.objectContaining({
id: 'app2',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
deepLinks: [
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: ['globalSearch'], // if not specified, deep links are visible in globalSearch
}),
],
})
@ -147,20 +157,23 @@ describe('#setup()', () => {
expect(applications.get('app1')).toEqual(
expect.objectContaining({
id: 'app1',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
status: AppStatus.inaccessible,
defaultPath: 'foo/bar',
tooltip: 'App inaccessible due to reason',
deepLinks: [
expect.objectContaining({ id: 'subapp2', title: 'Subapp 2', path: '/subapp2' }),
],
deepLinks: [], // deep links are removed when the app is inaccessible
})
);
expect(applications.get('app2')).toEqual(
expect.objectContaining({
id: 'app2',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
deepLinks: [
expect.objectContaining({
visibleIn: ['globalSearch'],
}),
],
})
);
});
@ -212,7 +225,7 @@ describe('#setup()', () => {
if (app.id === 'app1') {
return {
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
tooltip: 'App inaccessible due to reason',
};
}
@ -228,7 +241,7 @@ describe('#setup()', () => {
expect(applications.get('app1')).toEqual(
expect.objectContaining({
id: 'app1',
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
status: AppStatus.inaccessible,
tooltip: 'App inaccessible due to reason',
})
@ -236,7 +249,7 @@ describe('#setup()', () => {
expect(applications.get('app2')).toEqual(
expect.objectContaining({
id: 'app2',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
tooltip: 'App accessible',
})
@ -248,7 +261,7 @@ describe('#setup()', () => {
const pluginId = Symbol('plugin');
const appStatusUpdater$ = new BehaviorSubject<AppUpdater>((app) => ({
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
}));
setup.register(pluginId, createApp({ id: 'app1', updater$: appStatusUpdater$ }));
setup.register(pluginId, createApp({ id: 'app2' }));
@ -263,7 +276,7 @@ describe('#setup()', () => {
}
return {
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
})
);
@ -275,7 +288,7 @@ describe('#setup()', () => {
expect(applications.get('app1')).toEqual(
expect.objectContaining({
id: 'app1',
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
status: AppStatus.inaccessible,
tooltip: 'App inaccessible due to reason',
})
@ -284,7 +297,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app2',
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
})
);
});
@ -298,7 +311,7 @@ describe('#setup()', () => {
new BehaviorSubject<AppUpdater>((app) => {
return {
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: ['globalSearch'], // passing "globalSearch" but as the app is inaccessible it will be removed
};
})
);
@ -306,7 +319,7 @@ describe('#setup()', () => {
new BehaviorSubject<AppUpdater>((app) => {
return {
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.default,
visibleIn: DEFAULT_APP_VISIBILITY,
};
})
);
@ -318,7 +331,7 @@ describe('#setup()', () => {
expect(applications.get('app1')).toEqual(
expect.objectContaining({
id: 'app1',
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
status: AppStatus.inaccessible,
})
);
@ -333,7 +346,7 @@ describe('#setup()', () => {
const statusUpdater = new BehaviorSubject<AppUpdater>((app) => {
return {
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
};
});
setup.registerAppUpdater(statusUpdater);
@ -348,14 +361,14 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app1',
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
})
);
statusUpdater.next((app) => {
return {
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
});
@ -363,7 +376,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app1',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
})
);
});
@ -408,15 +421,13 @@ describe('#setup()', () => {
{
id: 'foo',
title: 'Foo',
searchable: true,
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['globalSearch'],
path: '/foo',
},
{
id: 'bar',
title: 'Bar',
searchable: false,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
path: '/bar',
},
];
@ -434,18 +445,16 @@ describe('#setup()', () => {
deepLinks: [],
id: 'foo',
keywords: [],
navLinkStatus: 1,
visibleIn: ['globalSearch'],
path: '/foo',
searchable: true,
title: 'Foo',
},
{
deepLinks: [],
id: 'bar',
keywords: [],
navLinkStatus: 3,
visibleIn: [],
path: '/bar',
searchable: false,
title: 'Bar',
},
]);
@ -455,8 +464,7 @@ describe('#setup()', () => {
{
id: 'bar',
title: 'Bar',
searchable: false,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
path: '/bar',
},
],
@ -469,9 +477,8 @@ describe('#setup()', () => {
deepLinks: [],
id: 'bar',
keywords: [],
navLinkStatus: 3,
visibleIn: [],
path: '/bar',
searchable: false,
title: 'Bar',
},
]);
@ -548,7 +555,7 @@ describe('#start()', () => {
expect.objectContaining({
appRoute: '/app/app1',
id: 'app1',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
})
);
@ -556,7 +563,7 @@ describe('#start()', () => {
expect.objectContaining({
appRoute: '/app/app2',
id: 'app2',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
status: AppStatus.accessible,
})
);

View file

@ -29,7 +29,7 @@ import type {
NavigateToUrlOptions,
} from '@kbn/core-application-browser';
import { CapabilitiesService } from '@kbn/core-capabilities-browser-internal';
import { AppStatus, AppNavLinkStatus } from '@kbn/core-application-browser';
import { AppStatus } from '@kbn/core-application-browser';
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
import { AppRouter } from './ui';
import type { InternalApplicationSetup, InternalApplicationStart, Mounter } from './types';
@ -206,7 +206,6 @@ export class ApplicationService {
this.apps.set(app.id, {
...appProps,
status: app.status ?? AppStatus.accessible,
navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default,
deepLinks: populateDeepLinkDefaults(appProps.deepLinks),
});
if (updater$) {
@ -468,10 +467,6 @@ const updateStatus = (app: App, statusUpdaters: AppUpdaterWrapper[]): App => {
changes.status ?? AppStatus.accessible,
fields.status ?? AppStatus.accessible
),
navLinkStatus: Math.max(
changes.navLinkStatus ?? AppNavLinkStatus.default,
fields.navLinkStatus ?? AppNavLinkStatus.default
),
...(fields.deepLinks ? { deepLinks: populateDeepLinkDefaults(fields.deepLinks) } : {}),
};
}
@ -489,7 +484,7 @@ const populateDeepLinkDefaults = (deepLinks?: AppDeepLink[]): AppDeepLink[] => {
}
return deepLinks.map((deepLink) => ({
...deepLink,
navLinkStatus: deepLink.navLinkStatus ?? AppNavLinkStatus.default,
visibleIn: deepLink.visibleIn ?? ['globalSearch'], // by default, deepLinks are only visible in global search.
deepLinks: populateDeepLinkDefaults(deepLink.deepLinks),
}));
};

View file

@ -0,0 +1,13 @@
/*
* 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 type { AppDeepLinkLocations } from '@kbn/core-application-browser/src/application';
export const DEFAULT_APP_VISIBILITY: AppDeepLinkLocations[] = ['globalSearch', 'sideNav'];
export const DEFAULT_LINK_VISIBILITY: AppDeepLinkLocations[] = ['globalSearch'];

View file

@ -7,7 +7,7 @@
*/
import { of } from 'rxjs';
import { App, AppDeepLink, AppNavLinkStatus, AppStatus } from '@kbn/core-application-browser';
import { App, AppDeepLink, AppStatus } from '@kbn/core-application-browser';
import { getAppInfo } from './get_app_info';
describe('getAppInfo', () => {
@ -17,8 +17,6 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
appRoute: `/app/some-id`,
...props,
});
@ -27,8 +25,7 @@ describe('getAppInfo', () => {
id: 'some-deep-link-id',
title: 'my deep link',
path: '/my-deep-link',
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
visibleIn: ['globalSearch'],
deepLinks: [],
keywords: [],
...props,
@ -42,14 +39,19 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch', 'sideNav'],
appRoute: `/app/some-id`,
keywords: [],
deepLinks: [],
});
});
it('does not return any deepLinks if the app is inaccessible', () => {
const app = createApp({ status: AppStatus.inaccessible, deepLinks: [createDeepLink()] });
const info = getAppInfo(app);
expect(info.deepLinks).toEqual([]);
});
it('populates default values for nested deepLinks', () => {
const app = createApp({
deepLinks: [
@ -66,16 +68,14 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch', 'sideNav'],
appRoute: `/app/some-id`,
keywords: [],
deepLinks: [
{
id: 'sub-id',
title: 'sub-title',
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'], // default visibleIn added
keywords: [],
deepLinks: [
{
@ -83,8 +83,7 @@ describe('getAppInfo', () => {
title: 'sub-sub-title',
path: '/sub-sub',
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
deepLinks: [], // default empty array added
},
],
@ -93,92 +92,41 @@ describe('getAppInfo', () => {
});
});
it('computes the navLinkStatus depending on the app status', () => {
it('computes the visibleIn depending on the app status', () => {
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.default,
visibleIn: ['globalSearch', 'sideNav'],
status: AppStatus.inaccessible,
})
)
).toEqual(
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
})
);
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.default,
visibleIn: ['globalSearch', 'sideNav'],
status: AppStatus.accessible,
})
)
).toEqual(
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.visible,
})
);
});
it('computes the searchable flag depending on the navLinkStatus when needed', () => {
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.default,
searchable: undefined,
})
)
).toEqual(
expect.objectContaining({
searchable: true,
visibleIn: ['globalSearch', 'sideNav'],
})
);
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.visible,
searchable: undefined,
// status is not set, default to accessible
visibleIn: ['globalSearch', 'sideNav'],
})
)
).toEqual(
expect.objectContaining({
searchable: true,
})
);
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.disabled,
searchable: undefined,
})
)
).toEqual(
expect.objectContaining({
searchable: false,
})
);
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.hidden,
searchable: undefined,
})
)
).toEqual(
expect.objectContaining({
searchable: false,
})
);
expect(
getAppInfo(
createApp({
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
})
)
).toEqual(
expect.objectContaining({
searchable: true,
visibleIn: ['globalSearch', 'sideNav'],
})
);
});
@ -209,8 +157,7 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch', 'sideNav'],
appRoute: `/app/some-id`,
keywords: [],
order: 3,
@ -218,16 +165,14 @@ describe('getAppInfo', () => {
{
id: 'sub-id',
title: 'sub-title',
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
order: 2,
keywords: [],
deepLinks: [
{
id: 'sub-sub-id',
title: 'sub-sub-title',
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
order: 1,
path: '/sub-sub',
keywords: ['sub sub'],
@ -239,13 +184,13 @@ describe('getAppInfo', () => {
});
});
it('computes the deepLinks navLinkStatus when needed', () => {
it('computes the deepLinks visibleIn when needed', () => {
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['globalSearch', 'sideNav'],
}),
],
})
@ -254,7 +199,7 @@ describe('getAppInfo', () => {
expect.objectContaining({
deepLinks: [
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['globalSearch', 'sideNav'],
}),
],
})
@ -262,100 +207,14 @@ describe('getAppInfo', () => {
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: AppNavLinkStatus.default,
}),
],
deepLinks: [createDeepLink({ visibleIn: undefined })],
})
)
).toEqual(
expect.objectContaining({
deepLinks: [
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.hidden,
}),
],
})
);
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: undefined,
}),
],
})
)
).toEqual(
expect.objectContaining({
deepLinks: [
expect.objectContaining({
navLinkStatus: AppNavLinkStatus.hidden,
}),
],
})
);
});
it('computes the deepLinks searchable depending on the navLinkStatus when needed', () => {
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: AppNavLinkStatus.default,
searchable: undefined,
}),
],
})
)
).toEqual(
expect.objectContaining({
deepLinks: [
expect.objectContaining({
searchable: true,
}),
],
})
);
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: AppNavLinkStatus.hidden,
searchable: undefined,
}),
],
})
)
).toEqual(
expect.objectContaining({
deepLinks: [
expect.objectContaining({
searchable: false,
}),
],
})
);
expect(
getAppInfo(
createApp({
deepLinks: [
createDeepLink({
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
}),
],
})
)
).toEqual(
expect.objectContaining({
deepLinks: [
expect.objectContaining({
searchable: true,
visibleIn: ['globalSearch'],
}),
],
})

View file

@ -7,31 +7,23 @@
*/
import {
AppNavLinkStatus,
AppStatus,
type App,
AppStatus,
type AppDeepLink,
type PublicAppInfo,
type PublicAppDeepLinkInfo,
} from '@kbn/core-application-browser';
import { DEFAULT_APP_VISIBILITY, DEFAULT_LINK_VISIBILITY } from './constants';
export function getAppInfo(app: App): PublicAppInfo {
const { updater$, mount, navLinkStatus = AppNavLinkStatus.default, ...infos } = app;
const { updater$, mount, visibleIn = DEFAULT_APP_VISIBILITY, ...infos } = app;
return {
...infos,
status: app.status!,
navLinkStatus:
navLinkStatus === AppNavLinkStatus.default
? app.status === AppStatus.inaccessible
? AppNavLinkStatus.hidden
: AppNavLinkStatus.visible
: navLinkStatus,
searchable:
app.searchable ??
(navLinkStatus === AppNavLinkStatus.default || navLinkStatus === AppNavLinkStatus.visible),
status: app.status ?? AppStatus.accessible,
visibleIn: app.status === AppStatus.inaccessible ? [] : visibleIn,
appRoute: app.appRoute!,
keywords: app.keywords ?? [],
deepLinks: getDeepLinkInfos(app.deepLinks),
deepLinks: app.status === AppStatus.inaccessible ? [] : getDeepLinkInfos(app.deepLinks),
};
}
@ -39,16 +31,11 @@ function getDeepLinkInfos(deepLinks?: AppDeepLink[]): PublicAppDeepLinkInfo[] {
if (!deepLinks) return [];
return deepLinks.map(
({ navLinkStatus = AppNavLinkStatus.default, ...rawDeepLink }): PublicAppDeepLinkInfo => {
({ visibleIn = DEFAULT_LINK_VISIBILITY, ...rawDeepLink }): PublicAppDeepLinkInfo => {
return {
...rawDeepLink,
keywords: rawDeepLink.keywords ?? [],
navLinkStatus:
navLinkStatus === AppNavLinkStatus.default ? AppNavLinkStatus.hidden : navLinkStatus,
searchable:
rawDeepLink.searchable ??
(navLinkStatus === AppNavLinkStatus.default ||
navLinkStatus === AppNavLinkStatus.visible),
visibleIn,
deepLinks: getDeepLinkInfos(rawDeepLink.deepLinks),
};
}

View file

@ -12,3 +12,4 @@ export { parseAppUrl } from './parse_app_url';
export { relativeToAbsolute } from './relative_to_absolute';
export { removeSlashes } from './remove_slashes';
export { getLocationObservable } from './get_location_observable';
export { DEFAULT_APP_VISIBILITY, DEFAULT_LINK_VISIBILITY } from './constants';

View file

@ -18,13 +18,14 @@ export type { AppMount, AppMountParameters, AppUnmount } from './src/app_mount';
export type {
App,
AppDeepLink,
AppDeepLinkLocations,
PublicAppInfo,
AppNavOptions,
PublicAppDeepLinkInfo,
AppUpdater,
AppUpdatableFields,
} from './src/application';
export { AppNavLinkStatus, AppStatus } from './src/application';
export { AppStatus } from './src/application';
export type {
ApplicationSetup,
ApplicationStart,

View file

@ -27,31 +27,6 @@ export enum AppStatus {
inaccessible = 1,
}
/**
* Status of the application's navLink.
*
* @public
*/
export enum AppNavLinkStatus {
/**
* The application navLink will be `visible` if the application's {@link AppStatus} is set to `accessible`
* and `hidden` if the application status is set to `inaccessible`.
*/
default = 0,
/**
* The application navLink is visible and clickable in the navigation bar.
*/
visible = 1,
/**
* The application navLink is visible but inactive and not clickable in the navigation bar.
*/
disabled = 2,
/**
* The application navLink does not appear in the navigation bar.
*/
hidden = 3,
}
/**
* App navigation menu options
* @public
@ -93,7 +68,7 @@ export type AppUpdater = (app: App) => Partial<AppUpdatableFields> | undefined;
*/
export type AppUpdatableFields = Pick<
App,
'status' | 'navLinkStatus' | 'searchable' | 'tooltip' | 'defaultPath' | 'deepLinks'
'status' | 'visibleIn' | 'tooltip' | 'defaultPath' | 'deepLinks'
>;
/**
@ -126,17 +101,20 @@ export interface App<HistoryLocationState = unknown> extends AppNavOptions {
status?: AppStatus;
/**
* The initial status of the application's navLink.
* Defaulting to `visible` if `status` is `accessible` and `hidden` if status is `inaccessible`
* See {@link AppNavLinkStatus}
* Optional list of locations where the app is visible.
*
* Accepts the following values:
* - "globalSearch": the link will appear in the global search bar
* - "home": the link will appear on the Kibana home page
* - "kibanaOverview": the link will appear in the Kibana overview page
* - "sideNav": the link will appear in the side navigation.
* Note: "sideNav" will be deprecated when we change the navigation to "solutions" style.
*
* @default ['globalSearch', 'sideNav']
* unless the status is marked as `inaccessible`.
* @note Set to `[]` (empty array) to hide this link
*/
navLinkStatus?: AppNavLinkStatus;
/**
* The initial flag to determine if the application is searchable in the global search.
* Defaulting to `true` if `navLinkStatus` is `visible` or omitted.
*/
searchable?: boolean;
visibleIn?: AppDeepLinkLocations[];
/**
* Allow to define the default path a user should be directed to when navigating to the app.
@ -172,7 +150,7 @@ export interface App<HistoryLocationState = unknown> extends AppNavOptions {
* start() {
* // later, when the navlink needs to be updated
* appUpdater.next(() => {
* navLinkStatus: AppNavLinkStatus.disabled,
* visibleIn: ['globalSearch'],
* })
* }
* ```
@ -268,16 +246,15 @@ export interface App<HistoryLocationState = unknown> extends AppNavOptions {
*
* @public
*/
export type PublicAppDeepLinkInfo = Omit<
AppDeepLink,
'deepLinks' | 'keywords' | 'navLinkStatus' | 'searchable'
> & {
export type PublicAppDeepLinkInfo = Omit<AppDeepLink, 'deepLinks' | 'keywords' | 'visibleIn'> & {
deepLinks: PublicAppDeepLinkInfo[];
keywords: string[];
navLinkStatus: AppNavLinkStatus;
searchable: boolean;
visibleIn: AppDeepLinkLocations[];
};
/** The places in the UI where a deepLink can be shown */
export type AppDeepLinkLocations = 'globalSearch' | 'sideNav' | 'home' | 'kibanaOverview';
/**
* Input type for registering secondary in-app locations for an application.
*
@ -293,10 +270,10 @@ export type AppDeepLink<Id extends string = string> = {
title: string;
/** Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. */
keywords?: string[];
/** Optional status of the chrome navigation, defaults to `hidden` */
navLinkStatus?: AppNavLinkStatus;
/** Optional flag to determine if the link is searchable in the global search. Defaulting to `true` if `navLinkStatus` is `visible` or omitted */
searchable?: boolean;
/**
* Optional list of locations where the deepLink is visible. By default the deepLink is visible in "globalSearch".
*/
visibleIn?: AppDeepLinkLocations[];
/**
* Optional category to use instead of the parent app category.
* This property is added to customize the way a deep link is rendered in the global search.
@ -326,13 +303,12 @@ export type AppDeepLink<Id extends string = string> = {
*/
export type PublicAppInfo = Omit<
App,
'mount' | 'updater$' | 'keywords' | 'deepLinks' | 'searchable'
'mount' | 'updater$' | 'keywords' | 'deepLinks' | 'visibleIn'
> & {
// remove optional on fields populated with default values
status: AppStatus;
navLinkStatus: AppNavLinkStatus;
appRoute: string;
keywords: string[];
deepLinks: PublicAppDeepLinkInfo[];
searchable: boolean;
visibleIn: AppDeepLinkLocations[];
};

View file

@ -13,7 +13,7 @@ import type { DocLinksStart } from '@kbn/core-doc-links-browser';
import type { InternalHttpSetup, InternalHttpStart } from '@kbn/core-http-browser-internal';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
import type { NotificationsSetup, NotificationsStart } from '@kbn/core-notifications-browser';
import { AppNavLinkStatus, type AppMountParameters } from '@kbn/core-application-browser';
import { type AppMountParameters } from '@kbn/core-application-browser';
import type {
InternalApplicationSetup,
InternalApplicationStart,
@ -49,7 +49,7 @@ export class CoreAppsService {
application.register(this.coreContext.coreId, {
id: 'error',
title: 'App Error',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount(params: AppMountParameters) {
// Do not use an async import here in order to ensure that network failures
// cannot prevent the error UI from displaying. This UI is tiny so an async
@ -66,7 +66,7 @@ export class CoreAppsService {
title: 'Server Status',
appRoute: '/status',
chromeless: true,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount(params: AppMountParameters) {
return renderStatusApp(params, { http, notifications });
},

View file

@ -6,13 +6,13 @@
* Side Public License, v 1.
*/
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, lastValueFrom } from 'rxjs';
import { take, map, takeLast } from 'rxjs/operators';
import type { App } from '@kbn/core-application-browser';
import { type App, AppStatus } from '@kbn/core-application-browser';
import { NavLinksService } from './nav_links_service';
const availableApps = new Map([
['app1', { id: 'app1', order: 0, title: 'App 1', icon: 'app1' }],
const availableApps: ReadonlyMap<string, App> = new Map([
['app1', { id: 'app1', order: 0, title: 'App 1', icon: 'app1', mount: () => () => undefined }],
[
'app2',
{
@ -20,12 +20,14 @@ const availableApps = new Map([
order: -10,
title: 'App 2',
euiIconType: 'canvasApp',
mount: () => () => undefined,
deepLinks: [
{
id: 'deepApp1',
order: 50,
title: 'Deep App 1',
path: '/deepapp1',
visibleIn: ['sideNav'],
deepLinks: [
{
id: 'deepApp2',
@ -38,7 +40,42 @@ const availableApps = new Map([
],
},
],
['chromelessApp', { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }],
[
'chromelessApp',
{
id: 'chromelessApp',
order: 20,
title: 'Chromless App',
chromeless: true,
mount: () => () => undefined,
},
],
[
'inaccessibleApp', // inaccessible app
{
id: 'inaccessibleApp',
order: 30,
title: 'App inaccessible',
mount: () => () => undefined,
status: AppStatus.inaccessible,
deepLinks: [
{
id: 'deepInaccessibleApp1',
order: 50,
title: 'Deep App 3',
path: '/deepapp3',
deepLinks: [
{
id: 'deepInaccessibleApp2',
order: 40,
title: 'Deep App 3',
path: '/deepapp3',
},
],
},
],
},
],
]);
const mockHttp = {
@ -55,7 +92,7 @@ describe('NavLinksService', () => {
beforeEach(() => {
service = new NavLinksService();
mockAppService = {
applications$: new BehaviorSubject<ReadonlyMap<string, App>>(availableApps as any),
applications$: new BehaviorSubject<ReadonlyMap<string, App>>(availableApps),
};
start = service.start({ application: mockAppService, http: mockHttp });
});
@ -63,25 +100,34 @@ describe('NavLinksService', () => {
describe('#getNavLinks$()', () => {
it('does not include `chromeless` applications', async () => {
expect(
await start
.getNavLinks$()
.pipe(
await lastValueFrom(
start.getNavLinks$().pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
)
).not.toContain('chromelessApp');
});
it('does not include `inaccesible` applications', async () => {
expect(
await lastValueFrom(
start.getNavLinks$().pipe(
take(1),
map((links) => links.map((l) => l.id))
)
)
).not.toContain('inaccessibleApp');
});
it('sorts navlinks by `order` property', async () => {
expect(
await start
.getNavLinks$()
.pipe(
await lastValueFrom(
start.getNavLinks$().pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
)
).toEqual(['app2', 'app1', 'app2:deepApp2', 'app2:deepApp1']);
});
@ -94,7 +140,7 @@ describe('NavLinksService', () => {
});
it('completes when service is stopped', async () => {
const last$ = start.getNavLinks$().pipe(takeLast(1)).toPromise();
const last$ = lastValueFrom(start.getNavLinks$().pipe(takeLast(1)));
service.stop();
await expect(last$).resolves.toBeInstanceOf(Array);
});
@ -108,6 +154,10 @@ describe('NavLinksService', () => {
it('returns undefined if it does not exist', () => {
expect(start.get('phony')).toBeUndefined();
});
it('returns undefined if app is inaccessible', () => {
expect(start.get('inaccessibleApp')).toBeUndefined();
});
});
describe('#getAll()', () => {
@ -133,15 +183,15 @@ describe('NavLinksService', () => {
describe('#enableForcedAppSwitcherNavigation()', () => {
it('flips #getForceAppSwitcherNavigation$()', async () => {
await expect(start.getForceAppSwitcherNavigation$().pipe(take(1)).toPromise()).resolves.toBe(
false
);
await expect(
lastValueFrom(start.getForceAppSwitcherNavigation$().pipe(take(1)))
).resolves.toBe(false);
start.enableForcedAppSwitcherNavigation();
await expect(start.getForceAppSwitcherNavigation$().pipe(take(1)).toPromise()).resolves.toBe(
true
);
await expect(
lastValueFrom(start.getForceAppSwitcherNavigation$().pipe(take(1)))
).resolves.toBe(true);
});
});
});

View file

@ -34,10 +34,11 @@ export class NavLinksService {
[...apps]
.filter(([, app]) => !app.chromeless)
.reduce((navLinks: Array<[string, NavLinkWrapper]>, [appId, app]) => {
navLinks.push(
[appId, toNavLink(app, http.basePath)],
...toNavDeepLinks(app, app.deepLinks, http.basePath)
);
const navLink = toNavLink(app, http.basePath);
if (navLink) {
navLinks.push([appId, navLink]);
}
navLinks.push(...toNavDeepLinks(app, app.deepLinks, http.basePath));
return navLinks;
}, [])
);
@ -100,7 +101,10 @@ function toNavDeepLinks(
return deepLinks.reduce((navDeepLinks: Array<[string, NavLinkWrapper]>, deepLink) => {
const id = `${app.id}:${deepLink.id}`;
if (deepLink.path) {
navDeepLinks.push([id, toNavLink(app, basePath, { ...deepLink, id })]);
const navDeepLink = toNavLink(app, basePath, { ...deepLink, id });
if (navDeepLink) {
navDeepLinks.push([id, navDeepLink]);
}
}
navDeepLinks.push(...toNavDeepLinks(app, deepLink.deepLinks, basePath));
return navDeepLinks;

View file

@ -7,7 +7,6 @@
*/
import {
AppNavLinkStatus,
AppStatus,
type PublicAppInfo,
type PublicAppDeepLinkInfo,
@ -19,8 +18,7 @@ const app = (props: Partial<PublicAppInfo> = {}): PublicAppInfo => ({
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
visibleIn: ['globalSearch'],
appRoute: `/app/some-id`,
keywords: [],
deepLinks: [],
@ -31,8 +29,7 @@ const deepLink = (props: Partial<PublicAppDeepLinkInfo> = {}): PublicAppDeepLink
id: 'some-deep-link-id',
title: 'my deep link',
path: '/my-deep-link',
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
visibleIn: ['globalSearch'],
deepLinks: [],
keywords: [],
...props,
@ -49,16 +46,18 @@ describe('toNavLink', () => {
order: 12,
tooltip: 'tooltip',
euiIconType: 'my-icon',
visibleIn: ['sideNav'],
}),
basePath
);
expect(link.properties).toEqual(
expect(link?.properties).toEqual(
expect.objectContaining({
id: 'id',
title: 'title',
order: 12,
tooltip: 'tooltip',
euiIconType: 'my-icon',
visibleIn: ['sideNav'],
})
);
});
@ -70,7 +69,7 @@ describe('toNavLink', () => {
}),
basePath
);
expect(link.properties.baseUrl).toEqual('http://localhost/base-path/my-route/my-path');
expect(link?.properties.baseUrl).toEqual('http://localhost/base-path/my-route/my-path');
});
it('generates the `url` property', () => {
@ -80,7 +79,7 @@ describe('toNavLink', () => {
}),
basePath
);
expect(link.properties.url).toEqual('/base-path/my-route/my-path');
expect(link?.properties.url).toEqual('/base-path/my-route/my-path');
link = toNavLink(
app({
@ -89,136 +88,28 @@ describe('toNavLink', () => {
}),
basePath
);
expect(link.properties.url).toEqual('/base-path/my-route/my-path/some/default/path');
expect(link?.properties.url).toEqual('/base-path/my-route/my-path/some/default/path');
});
it('uses the application status when the navLinkStatus is set to default', () => {
it('does not return navLink if the app status is inaccessible', () => {
expect(
toNavLink(
app({
navLinkStatus: AppNavLinkStatus.default,
status: AppStatus.accessible,
}),
basePath
).properties
).toEqual(
expect.objectContaining({
disabled: false,
hidden: false,
})
);
expect(
toNavLink(
app({
navLinkStatus: AppNavLinkStatus.default,
status: AppStatus.inaccessible,
}),
basePath
).properties
).toEqual(
expect.objectContaining({
disabled: false,
hidden: true,
})
);
});
it('uses the navLinkStatus of the application to set the hidden and disabled properties', () => {
expect(
toNavLink(
app({
navLinkStatus: AppNavLinkStatus.visible,
}),
basePath
).properties
).toEqual(
expect.objectContaining({
disabled: false,
hidden: false,
})
);
expect(
toNavLink(
app({
navLinkStatus: AppNavLinkStatus.hidden,
}),
basePath
).properties
).toEqual(
expect.objectContaining({
disabled: false,
hidden: true,
})
);
expect(
toNavLink(
app({
navLinkStatus: AppNavLinkStatus.disabled,
}),
basePath
).properties
).toEqual(
expect.objectContaining({
disabled: true,
hidden: false,
})
);
)
).toBe(null);
});
describe('deepLink parameter', () => {
it('should be hidden and not disabled by default', () => {
expect(toNavLink(app(), basePath, deepLink()).properties).toEqual(
expect.objectContaining({
disabled: false,
hidden: true,
})
);
});
it('should not be hidden when navLinkStatus is visible', () => {
expect(
toNavLink(
app(),
basePath,
deepLink({
navLinkStatus: AppNavLinkStatus.visible,
})
).properties
).toEqual(
expect.objectContaining({
disabled: false,
hidden: false,
})
);
});
it('should be disabled when navLinkStatus is disabled', () => {
expect(
toNavLink(
app(),
basePath,
deepLink({
navLinkStatus: AppNavLinkStatus.disabled,
})
).properties
).toEqual(
expect.objectContaining({
disabled: true,
hidden: false,
})
);
});
it('should have href, baseUrl and url containing the path', () => {
const testApp = app({
appRoute: '/app/app-id',
defaultPath: '/default-path',
});
expect(toNavLink(testApp, basePath).properties).toEqual(
expect(toNavLink(testApp, basePath)?.properties).toEqual(
expect.objectContaining({
baseUrl: 'http://localhost/base-path/app/app-id',
url: '/base-path/app/app-id/default-path',
@ -234,7 +125,7 @@ describe('toNavLink', () => {
id: 'deep-link-id',
path: '/my-deep-link',
})
).properties
)?.properties
).toEqual(
expect.objectContaining({
baseUrl: 'http://localhost/base-path/app/app-id',
@ -245,10 +136,10 @@ describe('toNavLink', () => {
});
it('should use the main app category', () => {
expect(toNavLink(app(), basePath, deepLink()).properties.category).toBeUndefined();
expect(toNavLink(app(), basePath, deepLink())?.properties.category).toBeUndefined();
const category = { id: 'some-category', label: 'some category' };
expect(toNavLink(app({ category }), basePath, deepLink()).properties.category).toEqual(
expect(toNavLink(app({ category }), basePath, deepLink())?.properties.category).toEqual(
category
);
});

View file

@ -8,10 +8,9 @@
import type { IBasePath } from '@kbn/core-http-browser';
import {
AppNavLinkStatus,
AppStatus,
type PublicAppInfo,
type PublicAppDeepLinkInfo,
AppStatus,
} from '@kbn/core-application-browser';
import { appendAppPath } from '@kbn/core-application-browser-internal';
import { NavLinkWrapper } from './nav_link';
@ -20,36 +19,23 @@ export function toNavLink(
app: PublicAppInfo,
basePath: IBasePath,
deepLink?: PublicAppDeepLinkInfo
): NavLinkWrapper {
): NavLinkWrapper | null {
const relativeBaseUrl = basePath.prepend(app.appRoute!);
const url = appendAppPath(relativeBaseUrl, deepLink?.path || app.defaultPath);
const href = relativeToAbsolute(url);
const baseUrl = relativeToAbsolute(relativeBaseUrl);
if (app.status === AppStatus.inaccessible) return null;
return new NavLinkWrapper({
...(deepLink || app),
...(app.category ? { category: app.category } : {}), // deepLinks use the main app category
hidden: deepLink ? isDeepNavLinkHidden(deepLink) : isAppNavLinkHidden(app),
disabled: (deepLink?.navLinkStatus ?? app.navLinkStatus) === AppNavLinkStatus.disabled,
baseUrl,
href,
url,
});
}
function isAppNavLinkHidden(app: PublicAppInfo) {
return app.navLinkStatus === AppNavLinkStatus.default
? app.status === AppStatus.inaccessible
: app.navLinkStatus === AppNavLinkStatus.hidden;
}
function isDeepNavLinkHidden(deepLink: PublicAppDeepLinkInfo) {
return (
deepLink.navLinkStatus === AppNavLinkStatus.default ||
deepLink.navLinkStatus === AppNavLinkStatus.hidden
);
}
/**
* @param {string} url - a relative or root relative url. If a relative path is given then the
* absolute url returned will depend on the current page where this function is called from. For example

View file

@ -28,6 +28,7 @@ const getNavLink = (partial: Partial<ChromeNavLink> = {}): ChromeNavLink => ({
baseUrl: '/app',
url: `/app/${partial.id ?? 'kibana'}`,
href: `/app/${partial.id ?? 'kibana'}`,
visibleIn: ['globalSearch'],
...partial,
});
@ -236,6 +237,7 @@ describe('initNavigation()', () => {
id: 'foo',
title: 'FOO',
url: '/app/foo',
visibleIn: ['globalSearch'],
},
href: '/app/foo',
id: 'foo',
@ -302,6 +304,9 @@ describe('initNavigation()', () => {
"id": "discover",
"title": "DISCOVER",
"url": "/app/discover",
"visibleIn": Array [
"globalSearch",
],
},
"href": "/app/discover",
"id": "discover",
@ -317,6 +322,9 @@ describe('initNavigation()', () => {
"id": "dashboards",
"title": "DASHBOARDS",
"url": "/app/dashboards",
"visibleIn": Array [
"globalSearch",
],
},
"href": "/app/dashboards",
"id": "dashboards",
@ -332,6 +340,9 @@ describe('initNavigation()', () => {
"id": "visualize",
"title": "VISUALIZE",
"url": "/app/visualize",
"visibleIn": Array [
"globalSearch",
],
},
"href": "/app/visualize",
"id": "visualize",
@ -792,6 +803,7 @@ describe('getActiveNodes$()', () => {
baseUrl: '/app',
url: '/app/item1',
href: '/app/item1',
visibleIn: ['globalSearch'],
},
},
],
@ -848,6 +860,7 @@ describe('getActiveNodes$()', () => {
baseUrl: '/app',
url: '/app/item1',
href: '/app/item1',
visibleIn: ['globalSearch'],
},
getIsActive: expect.any(Function),
},

View file

@ -15,6 +15,7 @@ const getDeepLink = (id: string, path: string, title = ''): ChromeNavLink => ({
href: `http://mocked/kibana/foo/${path}`,
title,
baseUrl: '',
visibleIn: ['globalSearch'],
});
describe('flattenNav', () => {

View file

@ -233,8 +233,6 @@ function getNodeStatus(
if (!hasUserAccessToCloudLink()) return 'remove';
}
if (deepLink && deepLink.hidden) return 'hidden';
return sideNavStatus ?? 'visible';
}

View file

@ -18,6 +18,8 @@ import { CollapsibleNav } from './collapsible_nav';
const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES;
const visibleIn = ['globalSearch' as const, 'sideNav' as const];
function mockLink({ title = 'discover', category }: Partial<ChromeNavLink>) {
return {
title,
@ -28,6 +30,7 @@ function mockLink({ title = 'discover', category }: Partial<ChromeNavLink>) {
url: '/',
isActive: true,
'data-test-subj': title,
visibleIn,
};
}

View file

@ -115,7 +115,7 @@ export function CollapsibleNav({
allLinks.filter(
(link) =>
// Filterting out hidden links,
!link.hidden &&
link.visibleIn.includes('sideNav') &&
// and non-data overview pages
!overviewIDsToHide.includes(link.id)
),

View file

@ -60,7 +60,14 @@ describe('Header', () => {
const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]);
const isLocked$ = new BehaviorSubject(false);
const navLinks$ = new BehaviorSubject([
{ id: 'kibana', title: 'kibana', baseUrl: '', href: '', url: '' },
{
id: 'kibana',
title: 'kibana',
baseUrl: '',
href: '',
url: '',
visibleIn: ['globalSearch' as const],
},
]);
const headerBanner$ = new BehaviorSubject(undefined);
const customNavLink$ = new BehaviorSubject({
@ -69,6 +76,7 @@ describe('Header', () => {
baseUrl: '',
url: '',
href: '',
visibleIn: ['globalSearch' as const],
});
const recentlyAccessed$ = new BehaviorSubject([
{ link: '', label: 'dashboard', id: 'dashboard' },

View file

@ -44,12 +44,6 @@ function onClick(
return;
}
const navLink = navLinks.find((item) => item.href === anchor.href);
if (navLink && navLink.disabled) {
event.preventDefault();
return;
}
if (event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
return;
}

View file

@ -43,7 +43,7 @@ export function createEuiListItem({
externalLink = false,
iconProps,
}: Props): EuiListGroupItemProps {
const { href, id, title, disabled, euiIconType, icon, tooltip, url } = link;
const { href, id, title, euiIconType, icon, tooltip, url } = link;
return {
label: tooltip ?? title,
@ -64,7 +64,6 @@ export function createEuiListItem({
}
},
isActive: !externalLink && appId === id,
isDisabled: disabled,
'data-test-subj': dataTestSubj,
...(basePath && {
iconType: euiIconType,
@ -81,7 +80,7 @@ export function createEuiButtonItem({
navigateToUrl,
dataTestSubj,
}: Omit<Props, 'appId' | 'basePath'>) {
const { href, disabled, url, id } = link;
const { href, url, id } = link;
return {
href,
@ -93,7 +92,6 @@ export function createEuiButtonItem({
event.preventDefault();
navigateToUrl(url);
},
isDisabled: disabled,
'data-test-subj': dataTestSubj || `collapsibleNavAppButton-${id}`,
};
}

View file

@ -8,6 +8,7 @@
import type { Observable } from 'rxjs';
import type { AppCategory } from '@kbn/core-application-common';
import type { AppDeepLinkLocations } from '@kbn/core-application-browser';
/**
* @public
@ -66,18 +67,18 @@ export interface ChromeNavLink {
readonly href: string;
/**
* Disables a link from being clickable.
* List of locations where the nav link should be visible.
*
* @internalRemarks
* This is only used by the ML and Graph plugins currently. They use this field
* to disable the nav link when the license is expired.
* Accepts the following values:
* - "globalSearch": the link will appear in the global search bar
* - "home": the link will appear on the Kibana home page
* - "kibanaOverview": the link will appear in the Kibana overview page
* - "sideNav": the link will appear in the side navigation.
* Note: "sideNav" will be deprecated when we change the navigation to "solutions" style.
*
* @default ['globalSearch']
*/
readonly disabled?: boolean;
/**
* Hides a link from the navigation.
*/
readonly hidden?: boolean;
readonly visibleIn: AppDeepLinkLocations[];
}
/**

View file

@ -19,7 +19,8 @@
"@kbn/deeplinks-ml",
"@kbn/deeplinks-management",
"@kbn/deeplinks-search",
"@kbn/deeplinks-observability"
"@kbn/deeplinks-observability",
"@kbn/core-application-browser"
],
"exclude": [
"target/**/*",

View file

@ -7,7 +7,6 @@
*/
import type { PluginInitializer } from '@kbn/core-plugins-browser';
import { AppNavLinkStatus } from '@kbn/core-application-browser';
import React from 'react';
import ReactDOM from 'react-dom';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
@ -39,7 +38,7 @@ export const plugin: PluginInitializer<
title: 'Mock IDP',
chromeless: true,
appRoute: MOCK_IDP_LOGIN_PATH,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params) => {
const [[coreStart], { LoginPage }] = await Promise.all([
coreSetup.getStartServices(),

View file

@ -12,7 +12,6 @@
],
"kbn_references": [
"@kbn/config-schema",
"@kbn/core-application-browser",
"@kbn/core-i18n-browser",
"@kbn/core-lifecycle-browser",
"@kbn/core-notifications-browser",

View file

@ -35,6 +35,8 @@ describe('Active node', () => {
return activeNodes$;
};
const visibleIn = ['globalSearch' as const];
const navigationBody: ChromeProjectNavigationNode[] = [
{
id: 'group1',
@ -51,6 +53,7 @@ describe('Active node', () => {
baseUrl: '',
url: '',
href: '',
visibleIn,
},
},
{
@ -63,6 +66,7 @@ describe('Active node', () => {
baseUrl: '',
url: '',
href: '',
visibleIn,
},
},
],

View file

@ -195,7 +195,7 @@ const nodeToEuiCollapsibleNavProps = (
const dataTestSubj = getTestSubj(navNode, isSelected);
let spaceBefore = navNode.spaceBefore;
if (spaceBefore === undefined && treeDepth === 1 && hasChildren) {
if (spaceBefore === undefined && treeDepth === 1 && hasChildren && !isItem) {
// For groups at level 1 that don't have a space specified we default to add a "m"
// space. For all other groups, unless specified, there is no vertical space.
spaceBefore = DEFAULT_SPACE_BETWEEN_LEVEL_1_GROUPS;
@ -309,7 +309,7 @@ const nodeToEuiCollapsibleNavProps = (
const hasVisibleChildren = (items?.length ?? 0) > 0;
const isVisible = isItem || hasVisibleChildren;
if (isVisible && spaceBefore) {
if (isVisible && Boolean(spaceBefore)) {
items.unshift({
renderItem: () => <EuiSpacer size={spaceBefore!} />,
});

View file

@ -109,11 +109,12 @@ export type {
TelemetryCounterType,
} from '@kbn/analytics-client';
export { AppNavLinkStatus, AppStatus } from '@kbn/core-application-browser';
export { AppStatus } from '@kbn/core-application-browser';
export type {
ApplicationSetup,
ApplicationStart,
App,
AppDeepLinkLocations,
AppMount,
AppUnmount,
AppMountParameters,

View file

@ -9,11 +9,10 @@
// TODO: https://github.com/elastic/kibana/issues/110892
/* eslint-disable @kbn/eslint/no_export_all */
import { PluginInitializerContext } from '@kbn/core/public';
import { DevToolsPlugin } from './plugin';
export * from './plugin';
export * from '../common/constants';
export function plugin(initializerContext: PluginInitializerContext) {
return new DevToolsPlugin(initializerContext);
export function plugin() {
return new DevToolsPlugin();
}

View file

@ -7,19 +7,14 @@
*/
import { BehaviorSubject } from 'rxjs';
import { Plugin, CoreSetup, AppMountParameters, AppDeepLink } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters, AppDeepLink, AppStatus } from '@kbn/core/public';
import { AppUpdater } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { sortBy } from 'lodash';
import {
PluginInitializerContext,
AppNavLinkStatus,
DEFAULT_APP_CATEGORIES,
} from '@kbn/core/public';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { UrlForwardingSetup } from '@kbn/url-forwarding-plugin/public';
import { deepLinkIds as devtoolsDeeplinkIds } from '@kbn/deeplinks-devtools';
import { ConfigSchema } from './types';
import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool';
import { DocTitleService, BreadcrumbService } from './services';
@ -50,7 +45,7 @@ export class DevToolsPlugin implements Plugin<DevToolsSetup, void> {
return sortBy([...this.devTools.values()], 'order');
}
constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {}
constructor() {}
public setup(coreSetup: CoreSetup, { urlForwarding }: { urlForwarding: UrlForwardingSetup }) {
const { application: applicationSetup, getStartServices } = coreSetup;
@ -112,12 +107,8 @@ export class DevToolsPlugin implements Plugin<DevToolsSetup, void> {
public start() {
if (this.getSortedDevTools().length === 0) {
this.appStateUpdater.next(() => ({ navLinkStatus: AppNavLinkStatus.hidden }));
this.appStateUpdater.next(() => ({ status: AppStatus.inaccessible }));
} else {
const config = this.initializerContext.config.get();
const navLinkStatus =
AppNavLinkStatus[config.deeplinks.navLinkStatus as keyof typeof AppNavLinkStatus];
this.appStateUpdater.next(() => {
const deepLinks: AppDeepLink[] = [...this.devTools.values()]
.filter(
@ -125,11 +116,10 @@ export class DevToolsPlugin implements Plugin<DevToolsSetup, void> {
(tool) => !tool.enableRouting && !tool.isDisabled() && typeof tool.title === 'string'
)
.map((tool) => {
const deepLink = {
const deepLink: AppDeepLink = {
id: tool.id,
title: tool.title as string,
path: `#/${tool.id}`,
navLinkStatus,
};
if (!devtoolsDeeplinkIds.some((id) => id === deepLink.id)) {
throw new Error('Deeplink must be registered in package.');
@ -139,7 +129,6 @@ export class DevToolsPlugin implements Plugin<DevToolsSetup, void> {
return {
deepLinks,
navLinkStatus,
};
});
}

View file

@ -6,10 +6,8 @@
* Side Public License, v 1.
*/
import { AppNavLinkStatus } from '@kbn/core/public';
export interface ConfigSchema {
deeplinks: {
navLinkStatus: keyof typeof AppNavLinkStatus;
navLinkStatus: 'default' | 'visible';
};
}

View file

@ -296,6 +296,7 @@ export class DiscoverPlugin
euiIconType: 'logoKibana',
defaultPath: '#/',
category: DEFAULT_APP_CATEGORIES.kibana,
visibleIn: ['globalSearch', 'sideNav', 'kibanaOverview'],
mount: async (params: AppMountParameters) => {
const [coreStart, discoverStartPlugins] = await core.getStartServices();
setScopedHistory(params.history);

View file

@ -37,7 +37,11 @@ export const renderApp = async (
const navLinksSubscription = chrome.navLinks.getNavLinks$().subscribe((navLinks) => {
const solutions = featureCatalogue
.getSolutions()
.filter(({ id }) => navLinks.find(({ category, hidden }) => !hidden && category?.id === id));
.filter(({ id }) =>
navLinks.find(
({ visibleIn, category }) => visibleIn.includes('home') && category?.id === id
)
);
render(
<RedirectAppLinks

View file

@ -19,7 +19,6 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants';
@ -77,7 +76,7 @@ export class HomePublicPlugin
core.application.register({
id: PLUGIN_ID,
title: 'Home',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params: AppMountParameters) => {
const trackUiMetric = usageCollection
? usageCollection.reportUiCounter.bind(usageCollection, 'Kibana_home')

View file

@ -34,7 +34,11 @@ export const renderApp = (
const solutions = home.featureCatalogue
.getSolutions()
.filter(({ id }) => id !== 'kibana')
.filter(({ id }) => navLinks.find(({ category, hidden }) => !hidden && category?.id === id));
.filter(({ id }) =>
navLinks.find(
({ category, visibleIn }) => visibleIn.includes('kibanaOverview') && category?.id === id
)
);
ReactDOM.render(
<I18nProvider>

View file

@ -16,7 +16,6 @@ import {
Plugin,
DEFAULT_APP_CATEGORIES,
AppStatus,
AppNavLinkStatus,
} from '@kbn/core/public';
import {
KibanaOverviewPluginSetup,
@ -49,7 +48,8 @@ export class KibanaOverviewPlugin
map((navLinks) => {
const hasKibanaApp = Boolean(
navLinks.find(
({ id, category, hidden }) => !hidden && category?.id === 'kibana' && id !== PLUGIN_ID
({ id, category, visibleIn }) =>
visibleIn.length > 0 && category?.id === 'kibana' && id !== PLUGIN_ID
)
);
@ -58,11 +58,7 @@ export class KibanaOverviewPlugin
distinct(),
map((hasKibanaApp) => {
return () => {
if (!hasKibanaApp) {
return { status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.hidden };
} else {
return { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default };
}
return { status: hasKibanaApp ? AppStatus.accessible : AppStatus.inaccessible };
};
})
);
@ -76,6 +72,7 @@ export class KibanaOverviewPlugin
order: 1,
updater$: appUpdater$,
appRoute: PLUGIN_PATH,
visibleIn: ['globalSearch', 'home', 'sideNav'],
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -20,7 +20,6 @@ import {
AppMountParameters,
AppUpdater,
AppStatus,
AppNavLinkStatus,
AppDeepLink,
} from '@kbn/core/public';
import { ConfigSchema, ManagementSetup, ManagementStart, NavigationCardsSubject } from './types';
@ -55,21 +54,15 @@ export class ManagementPlugin
private readonly managementSections = new ManagementSectionsService();
private readonly appUpdater = new BehaviorSubject<AppUpdater>(() => {
const config = this.initializerContext.config.get();
const navLinkStatus =
AppNavLinkStatus[config.deeplinks.navLinkStatus as keyof typeof AppNavLinkStatus];
const deepLinks: AppDeepLink[] = Object.values(this.managementSections.definedSections).map(
(section: ManagementSection) => ({
id: section.id,
title: section.title,
navLinkStatus,
deepLinks: section.getAppsEnabled().map((mgmtApp) => ({
id: mgmtApp.id,
title: mgmtApp.title,
path: mgmtApp.basePath,
keywords: mgmtApp.keywords,
navLinkStatus,
})),
})
);
@ -161,7 +154,7 @@ export class ManagementPlugin
this.appUpdater.next(() => {
return {
status: AppStatus.inaccessible,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
});
}

View file

@ -11,7 +11,6 @@ import { ScopedHistory, Capabilities } from '@kbn/core/public';
import type { LocatorPublic } from '@kbn/share-plugin/common';
import { ChromeBreadcrumb, CoreTheme } from '@kbn/core/public';
import type { CardsNavigationComponentProps } from '@kbn/management-cards-navigation';
import { AppNavLinkStatus } from '@kbn/core/public';
import { ManagementSection, RegisterManagementSectionArgs } from './utils';
import type { ManagementAppLocatorParams } from '../common/locator';
@ -101,6 +100,6 @@ export interface AppDependencies {
export interface ConfigSchema {
deeplinks: {
navLinkStatus: keyof typeof AppNavLinkStatus;
navLinkStatus: 'default' | 'visible';
};
}

View file

@ -7,7 +7,6 @@
*/
import { App, AppMountParameters, CoreSetup } from '@kbn/core/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import { navigateToLegacyKibanaUrl } from './navigate_to_legacy_kibana_url';
import { ForwardDefinition, UrlForwardingStart } from '../plugin';
@ -19,7 +18,7 @@ export const createLegacyUrlForwardApp = (
chromeless: true,
title: 'Legacy URL migration',
appRoute: '/app/kibana#/',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const hash = params.history.location.hash.substr(1);

View file

@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
import { DEFAULT_APP_VISIBILITY } from '@kbn/core-application-browser-internal';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '@kbn/core/public';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
export class CorePluginDeepLinksPlugin
implements Plugin<CorePluginDeepLinksPluginSetup, CorePluginDeepLinksPluginStart>
@ -19,19 +20,19 @@ export class CorePluginDeepLinksPlugin
appRoute: '/app/dl',
defaultPath: '/home',
category: DEFAULT_APP_CATEGORIES.security,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
deepLinks: [
{
id: 'home',
title: 'DL Home',
path: '/home',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
},
{
id: 'pageA',
title: 'DL page A',
path: '/page-a',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
},
{
id: 'sectionOne',
@ -41,7 +42,7 @@ export class CorePluginDeepLinksPlugin
id: 'pageB',
title: 'DL page B',
path: '/page-b',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: DEFAULT_APP_VISIBILITY,
},
],
},

View file

@ -14,6 +14,7 @@
],
"kbn_references": [
"@kbn/core",
"@kbn/shared-ux-router"
"@kbn/shared-ux-router",
"@kbn/core-application-browser-internal"
]
}

View file

@ -8,7 +8,7 @@
import Url from 'url';
import expect from '@kbn/expect';
import { AppNavLinkStatus, AppStatus, AppUpdatableFields } from '@kbn/core-application-browser';
import { AppStatus, AppUpdatableFields } from '@kbn/core-application-browser';
import { PluginFunctionalProviderContext } from '../../services';
import '@kbn/core-app-status-plugin/public/types';
@ -47,16 +47,15 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
await PageObjects.common.navigateToApp('app_status_start');
});
it('can change the navLink status at runtime', async () => {
it('can change the visibleIn array at runtime', async () => {
await setAppStatus({
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: ['sideNav'],
});
let link = await appsMenu.getLink('App Status');
expect(link).not.to.eql(undefined);
expect(link!.disabled).to.eql(true);
await setAppStatus({
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
});
link = await appsMenu.getLink('App Status');
expect(link).to.eql(undefined);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import {
@ -42,7 +42,7 @@ export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamp
core.application.register({
id: 'AlertingExample',
title: 'Alerting Example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./application');

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
@ -27,7 +27,7 @@ export class EmbeddedLensExamplePlugin
core.application.register({
id: 'embedded_lens_example',
title: 'Embedded Lens example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ObservabilityPublicStart } from '@kbn/observability-plugin/public';
import type { ExploratoryViewPublicStart } from '@kbn/exploratory-view-plugin/public';
@ -29,7 +29,7 @@ export class ExploratoryViewExamplePlugin
core.application.register({
id: 'exploratory_view_example',
title: 'Observability Exploratory View example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
order: 1000,
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '@kbn/core/public';
import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import {
@ -38,7 +38,7 @@ export class GenAiStreamingResponseExamplePlugin
core.application.register({
id: 'GenAiStreamingResponseExample',
title: 'OpenAI Streaming Response Example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./application');

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
@ -30,7 +30,7 @@ export class LensInlineEditingPlugin
core.application.register({
id: 'lens_embeddable_inline_editing_example',
title: 'Lens inline editing embeddable',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
});

View file

@ -5,13 +5,7 @@
* 2.0.
*/
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '@kbn/core/public';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME, ReportingExampleLocatorDefinition } from '../common';
import { SetupDeps, StartDeps, MyForwardableState } from './types';
@ -20,7 +14,7 @@ export class ReportingExamplePlugin implements Plugin<void, void, {}, {}> {
core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');

View file

@ -9,7 +9,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import type { AppMountParameters, CoreSetup, Plugin } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import { App, HttpContext } from './app';
@ -25,7 +24,7 @@ export class ScreenshottingExamplePlugin implements Plugin<void, void> {
application.register({
id: APPLICATION_ID,
title: APPLICATION_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async ({ element }: AppMountParameters) => {
const [{ http }] = await getStartServices();

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
@ -30,7 +30,7 @@ export class TestingEmbeddedLensPlugin
core.application.register({
id: 'testing_embedded_lens',
title: 'Embedded Lens testing playground',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
});

View file

@ -6,7 +6,7 @@
*/
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
import {
DateHistogramIndexPatternColumn,
@ -95,7 +95,7 @@ export class EmbeddedLensExamplePlugin
core.application.register({
id: 'third_party_lens_navigation_prompt',
title: 'Third party Lens navigation prompt',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: (params) => {
(async () => {
const [, { lens: lensStart, dataViews }] = await core.getStartServices();

View file

@ -7,7 +7,7 @@
import { ExpressionsSetup } from '@kbn/expressions-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup } from '@kbn/core/public';
import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
import { LensPublicSetup, LensPublicStart } from '@kbn/lens-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
@ -90,7 +90,7 @@ export class EmbeddedLensExamplePlugin
core.application.register({
id: 'third_party_lens_vis_example',
title: 'Third party Lens vis example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: ({ history }) => {
(async () => {
const [coreStart, { lens: lensStart, dataViews }] = await core.getStartServices();

View file

@ -6,13 +6,7 @@
*/
import React from 'react';
import {
Plugin,
CoreSetup,
AppMountParameters,
AppNavLinkStatus,
CoreStart,
} from '@kbn/core/public';
import { Plugin, CoreSetup, AppMountParameters, CoreStart } from '@kbn/core/public';
import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
@ -54,7 +48,7 @@ export class TriggersActionsUiExamplePlugin
core.application.register({
id: 'triggersActionsUiExample',
title: 'Triggers Actions UI Example',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
// category set as cases expects the label to exist
category: {
id: 'fakeId',

View file

@ -7,7 +7,7 @@
import { createElement as h } from 'react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { Plugin, CoreSetup, CoreStart, AppNavLinkStatus } from '@kbn/core/public';
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
import {
AdvancedUiActionsSetup,
@ -130,7 +130,7 @@ export class UiActionsEnhancedExamplesPlugin
core.application.register({
id: 'ui_actions_enhanced-explorer',
title: 'UI Actions Enhanced Explorer',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: mount(core),
});

View file

@ -12,7 +12,6 @@ import type {
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
DEFAULT_APP_CATEGORIES,
@ -361,7 +360,6 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
appRoute: '/app/apm',
icon: 'plugins/apm/public/icon.svg',
category: DEFAULT_APP_CATEGORIES.observability,
navLinkStatus: AppNavLinkStatus.visible,
deepLinks: [
{
id: 'service-groups-list',
@ -372,33 +370,26 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
id: 'services',
title: servicesTitle,
path: '/services',
navLinkStatus: config.serverless.enabled
? AppNavLinkStatus.visible
: AppNavLinkStatus.default,
},
{
id: 'traces',
title: tracesTitle,
path: '/traces',
navLinkStatus: config.serverless.enabled
? AppNavLinkStatus.visible
: AppNavLinkStatus.default,
},
{ id: 'service-map', title: serviceMapTitle, path: '/service-map' },
{
id: 'dependencies',
title: dependenciesTitle,
path: '/dependencies/inventory',
navLinkStatus: config.serverless.enabled
? AppNavLinkStatus.visible
: AppNavLinkStatus.default,
},
{ id: 'settings', title: apmSettingsTitle, path: '/settings' },
{
id: 'storage-explorer',
title: apmStorageExplorerTitle,
path: '/storage-explorer',
searchable: featureFlags.storageExplorerAvailable,
visibleIn: featureFlags.storageExplorerAvailable
? ['globalSearch']
: [],
},
{ id: 'tutorial', title: apmTutorialTitle, path: '/tutorial' },
],

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { AppNavLinkStatus } from '@kbn/core/public';
import { getCasesDeepLinks } from './deep_links';
describe('getCasesDeepLinks', () => {
@ -55,10 +54,10 @@ describe('getCasesDeepLinks', () => {
const deepLinks = getCasesDeepLinks({
extend: {
cases: {
searchable: false,
visibleIn: [],
},
cases_create: {
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
},
cases_configure: {
order: 8002,
@ -70,13 +69,13 @@ describe('getCasesDeepLinks', () => {
id: 'cases',
path: '/cases',
title: 'Cases',
searchable: false,
visibleIn: [],
deepLinks: [
{
id: 'cases_create',
path: '/cases/create',
title: 'Create',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
},
{
id: 'cases_configure',

View file

@ -16,7 +16,6 @@ import {
Plugin,
PluginInitializerContext,
DEFAULT_APP_CATEGORIES,
AppNavLinkStatus,
} from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
@ -67,9 +66,9 @@ export interface PluginsStart {
guidedOnboarding: GuidedOnboardingPluginStart;
lens: LensPublicStart;
licensing: LicensingPluginStart;
ml: MlPluginStart;
security: SecurityPluginStart;
share: SharePluginStart;
ml: MlPluginStart;
}
export interface ESConfig {
@ -78,7 +77,6 @@ export interface ESConfig {
export class EnterpriseSearchPlugin implements Plugin {
private config: ClientConfigType;
private esConfig: ESConfig;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<ClientConfigType>();
@ -86,6 +84,7 @@ export class EnterpriseSearchPlugin implements Plugin {
}
private data: ClientData = {} as ClientData;
private esConfig: ESConfig;
private async getInitialData(http: HttpSetup) {
try {
@ -167,6 +166,7 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData);
},
title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE,
visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'],
});
core.application.register({
@ -252,8 +252,8 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(EnterpriseSearchAISearch, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.hidden,
title: AI_SEARCH_PLUGIN.NAV_TITLE,
visibleIn: [],
});
core.application.register({
@ -274,8 +274,6 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(Applications, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
title: APPLICATIONS_PLUGIN.NAV_TITLE,
});
@ -297,8 +295,6 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(Analytics, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.default,
searchable: true,
title: ANALYTICS_PLUGIN.NAME,
});
@ -320,8 +316,8 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(SearchExperiences, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.hidden,
title: SEARCH_EXPERIENCES_PLUGIN.NAME,
visibleIn: [],
});
if (config.canDeployEntSearch) {
@ -343,8 +339,8 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(AppSearch, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.hidden,
title: APP_SEARCH_PLUGIN.NAME,
visibleIn: [],
});
core.application.register({
@ -368,8 +364,8 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(WorkplaceSearch, kibanaDeps, pluginData);
},
navLinkStatus: AppNavLinkStatus.hidden,
title: WORKPLACE_SEARCH_PLUGIN.NAME,
visibleIn: [],
});
}

View file

@ -6,7 +6,6 @@
*/
import { i18n } from '@kbn/i18n';
import { AppNavLinkStatus } from '@kbn/core-application-browser';
import { BehaviorSubject } from 'rxjs';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import {
@ -114,7 +113,7 @@ export class Plugin
title: i18n.translate('xpack.exploratoryView.appTitle', {
defaultMessage: 'Exploratory View',
}),
searchable: false,
visibleIn: [],
updater$: appUpdater$,
keywords: [
'observability',
@ -136,10 +135,6 @@ export class Plugin
}
public start(coreStart: CoreStart, pluginsStart: ExploratoryViewPublicPluginsStart) {
this.appUpdater$.next(() => ({
navLinkStatus: AppNavLinkStatus.hidden,
}));
return {
createExploratoryViewUrl,
getAppDataView: getAppDataView(pluginsStart.dataViews),

View file

@ -36,7 +36,6 @@
"@kbn/share-plugin",
"@kbn/charts-plugin",
"@kbn/shared-ux-router",
"@kbn/core-application-browser",
"@kbn/observability-shared-plugin",
"@kbn/core-ui-settings-browser-mocks",
"@kbn/observability-ai-assistant-plugin",

View file

@ -13,7 +13,7 @@ import type {
Plugin,
PluginInitializerContext,
} from '@kbn/core/public';
import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
@ -248,7 +248,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
core.application.register({
id: 'ingestManager',
category: DEFAULT_APP_CATEGORIES.management,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
title: i18n.translate('xpack.fleet.oldAppTitle', { defaultMessage: 'Ingest Manager' }),
async mount(params: AppMountParameters) {
const [coreStart] = await core.getStartServices();

View file

@ -9,7 +9,7 @@ import { getAppResultsMock } from './application.test.mocks';
import { EMPTY, of } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { ApplicationStart, AppNavLinkStatus, AppStatus, PublicAppInfo } from '@kbn/core/public';
import { ApplicationStart, AppStatus, PublicAppInfo } from '@kbn/core/public';
import {
GlobalSearchProviderFindOptions,
GlobalSearchProviderResult,
@ -27,8 +27,7 @@ const createApp = (props: Partial<PublicAppInfo> = {}): PublicAppInfo => ({
title: 'App 1',
appRoute: '/app/app1',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch'],
chromeless: false,
keywords: props.keywords || [],
deepLinks: [],
@ -167,13 +166,13 @@ describe('applicationResultProvider', () => {
it('does not ignore apps with non-visible navlink', async () => {
application.applications$ = of(
createAppMap([
createApp({ id: 'app1', title: 'App 1', navLinkStatus: AppNavLinkStatus.visible }),
createApp({ id: 'app1', title: 'App 1', visibleIn: ['globalSearch'] }),
createApp({
id: 'disabled',
title: 'disabled',
navLinkStatus: AppNavLinkStatus.disabled,
visibleIn: [],
}),
createApp({ id: 'hidden', title: 'hidden', navLinkStatus: AppNavLinkStatus.hidden }),
createApp({ id: 'hidden', title: 'hidden', visibleIn: [] }),
])
);
const provider = createApplicationResultProvider(Promise.resolve(application));

View file

@ -5,12 +5,7 @@
* 2.0.
*/
import {
AppNavLinkStatus,
AppStatus,
PublicAppInfo,
DEFAULT_APP_CATEGORIES,
} from '@kbn/core/public';
import { AppStatus, PublicAppInfo, DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import {
AppLink,
appToResult,
@ -24,8 +19,7 @@ const createApp = (props: Partial<PublicAppInfo> = {}): PublicAppInfo => ({
title: 'App 1',
appRoute: '/app/app1',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch'],
chromeless: false,
keywords: [],
deepLinks: [],
@ -48,7 +42,7 @@ describe('getAppResults', () => {
createApp({
id: 'dashboard_not_searchable',
title: 'dashboard not searchable',
searchable: false,
visibleIn: [],
}),
];
@ -68,8 +62,7 @@ describe('getAppResults', () => {
path: '/sub1',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
{
id: 'sub2',
@ -82,13 +75,11 @@ describe('getAppResults', () => {
path: '/sub2/sub1',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
],
keywords: [],
navLinkStatus: AppNavLinkStatus.visible,
searchable: false,
visibleIn: [],
},
],
keywords: [],
@ -116,8 +107,7 @@ describe('getAppResults', () => {
path: '/sub-observability',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
{
id: 'sub-security',
@ -125,8 +115,7 @@ describe('getAppResults', () => {
path: '/sub-security',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.visible,
searchable: true,
visibleIn: ['globalSearch'],
euiIconType: 'logoSecurity',
category: DEFAULT_APP_CATEGORIES.security,
},
@ -164,8 +153,7 @@ describe('getAppResults', () => {
path: '/sub1',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
],
keywords: [],
@ -173,7 +161,7 @@ describe('getAppResults', () => {
createApp({
id: 'AppNotSearchable',
title: 'App 1 not searchable',
searchable: false,
visibleIn: [],
}),
];
@ -197,8 +185,7 @@ describe('getAppResults', () => {
path: '/sub1',
deepLinks: [],
keywords: [],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
{
id: 'sub2',
@ -211,13 +198,11 @@ describe('getAppResults', () => {
path: '/sub2/sub1',
deepLinks: [],
keywords: ['TwoOne'],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
],
keywords: ['two'],
navLinkStatus: AppNavLinkStatus.hidden,
searchable: true,
visibleIn: ['globalSearch'],
},
],
keywords: [],

View file

@ -33,7 +33,7 @@ export const getAppResults = (
.flatMap((app) =>
term.length > 0
? flattenDeepLinks(app)
: app.searchable
: app.visibleIn.includes('globalSearch')
? [
{
id: app.id,
@ -122,7 +122,7 @@ export const appToResult = (appLink: AppLink, score: number): GlobalSearchProvid
const flattenDeepLinks = (app: PublicAppInfo, deepLink?: PublicAppDeepLinkInfo): AppLink[] => {
if (!deepLink) {
return [
...(app.searchable
...(app.visibleIn.includes('globalSearch')
? [
{
id: app.id,
@ -137,7 +137,7 @@ const flattenDeepLinks = (app: PublicAppInfo, deepLink?: PublicAppDeepLinkInfo):
];
}
return [
...(deepLink.path && deepLink.searchable
...(deepLink.path && deepLink.visibleIn.includes('globalSearch')
? [
{
...deepLink,

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import { BehaviorSubject } from 'rxjs';
import { SpacesApi } from '@kbn/spaces-plugin/public';
import {
AppNavLinkStatus,
AppStatus,
AppUpdater,
CoreSetup,
CoreStart,
@ -143,11 +143,11 @@ export class GraphPlugin
const licenseInformation = checkLicense(license);
this.appUpdater$.next(() => ({
navLinkStatus: licenseInformation.showAppLink
status: licenseInformation.showAppLink
? licenseInformation.enableAppLink
? AppNavLinkStatus.visible
: AppNavLinkStatus.disabled
: AppNavLinkStatus.hidden,
? AppStatus.accessible
: AppStatus.inaccessible
: AppStatus.inaccessible,
tooltip: licenseInformation.showAppLink ? licenseInformation.message : undefined,
}));

View file

@ -10,9 +10,9 @@ import {
type AppUpdater,
type CoreStart,
type AppDeepLink,
AppNavLinkStatus,
DEFAULT_APP_CATEGORIES,
PluginInitializerContext,
AppDeepLinkLocations,
} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public';
@ -249,9 +249,7 @@ export class Plugin implements InfraClientPluginClass {
hostsEnabled: boolean;
metricsExplorerEnabled: boolean;
}): AppDeepLink[] => {
const serverlessNavLinkStatus = this.isServerlessEnv
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden;
const visibleIn: AppDeepLinkLocations[] = this.isServerlessEnv ? ['globalSearch'] : [];
return [
{
@ -260,7 +258,7 @@ export class Plugin implements InfraClientPluginClass {
defaultMessage: 'Inventory',
}),
path: '/inventory',
navLinkStatus: serverlessNavLinkStatus,
visibleIn,
},
...(hostsEnabled
? [
@ -270,7 +268,7 @@ export class Plugin implements InfraClientPluginClass {
defaultMessage: 'Hosts',
}),
path: '/hosts',
navLinkStatus: serverlessNavLinkStatus,
visibleIn,
},
]
: []),
@ -330,7 +328,7 @@ export class Plugin implements InfraClientPluginClass {
id: 'infra',
appRoute: '/app/infra',
title: 'infra',
navLinkStatus: 3,
visibleIn: [],
mount: async (params: AppMountParameters) => {
const { renderApp } = await import('./apps/legacy_app');

View file

@ -42,7 +42,6 @@ import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public';
import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import {
UiActionsStart,
ACTION_VISUALIZE_FIELD,
@ -451,7 +450,7 @@ export class LensPlugin {
core.application.register({
id: APP_ID,
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
mount: async (params: AppMountParameters) => {
const { core: coreStart, plugins: deps } = startServices();

View file

@ -96,7 +96,7 @@ class MyPlugin {
const showLinks = hasRequiredLicense && license.getFeature('name').isAvailable;
appUpdater$.next(() => {
navLinkStatus: showLinks ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden
status: showLinks ? AppStatus.accessible : AppStatus.inaccessible,
});
})
}

View file

@ -231,7 +231,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
registerMapExtension,
registerCasesAttachments,
} = await import('./register_helper');
registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, !this.isServerless);
registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, this.isServerless);
if (
pluginsSetup.triggersActionsUi &&

View file

@ -16,7 +16,7 @@ export function registerSearchLinks(
appUpdater: BehaviorSubject<AppUpdater>,
isFullLicense: boolean,
mlCapabilities: MlCapabilities,
showMLNavMenu: boolean
isServerless: boolean
) {
appUpdater.next(() => ({
keywords: [
@ -24,6 +24,6 @@ export function registerSearchLinks(
defaultMessage: 'ML',
}),
],
deepLinks: getDeepLinks(isFullLicense, mlCapabilities, showMLNavMenu),
deepLinks: getDeepLinks(isFullLicense, mlCapabilities, isServerless),
}));
}

View file

@ -8,50 +8,37 @@
import { i18n } from '@kbn/i18n';
import type { LinkId } from '@kbn/deeplinks-ml';
import { type AppDeepLink, AppNavLinkStatus } from '@kbn/core/public';
import { type AppDeepLink } from '@kbn/core/public';
import { ML_PAGES } from '../../../common/constants/locator';
import type { MlCapabilities } from '../../shared';
function createDeepLinks(
mlCapabilities: MlCapabilities,
isFullLicense: boolean,
showMLNavMenu: boolean
isServerless: boolean
) {
function getNavStatus(
visible: boolean,
showInServerless: boolean = true
): AppNavLinkStatus | undefined {
if (showMLNavMenu === false) {
// in serverless the status needs to be "visible" rather than "default"
// for the links to appear in the nav menu.
return showInServerless && visible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden;
}
return visible ? AppNavLinkStatus.default : AppNavLinkStatus.hidden;
}
return {
getOverviewLinkDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled || mlCapabilities.isDFAEnabled);
getOverviewLinkDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.isADEnabled && !mlCapabilities.isDFAEnabled) return null;
return {
id: 'overview',
title: i18n.translate('xpack.ml.deepLink.overview', {
defaultMessage: 'Overview',
}),
path: `/${ML_PAGES.OVERVIEW}`,
navLinkStatus,
};
},
getAnomalyDetectionDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled);
getAnomalyDetectionDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.isADEnabled) return null;
return {
id: 'anomalyDetection',
title: i18n.translate('xpack.ml.deepLink.anomalyDetection', {
defaultMessage: 'Anomaly Detection',
}),
path: `/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`,
navLinkStatus,
deepLinks: [
{
id: 'anomalyExplorer',
@ -59,7 +46,6 @@ function createDeepLinks(
defaultMessage: 'Anomaly explorer',
}),
path: `/${ML_PAGES.ANOMALY_EXPLORER}`,
navLinkStatus,
},
{
id: 'singleMetricViewer',
@ -67,21 +53,20 @@ function createDeepLinks(
defaultMessage: 'Single metric viewer',
}),
path: `/${ML_PAGES.SINGLE_METRIC_VIEWER}`,
navLinkStatus,
},
],
};
},
getDataFrameAnalyticsDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(mlCapabilities.isDFAEnabled);
getDataFrameAnalyticsDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.isDFAEnabled) return null;
return {
id: 'dataFrameAnalytics',
title: i18n.translate('xpack.ml.deepLink.dataFrameAnalytics', {
defaultMessage: 'Data Frame Analytics',
}),
path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`,
navLinkStatus,
deepLinks: [
{
id: 'resultExplorer',
@ -89,7 +74,6 @@ function createDeepLinks(
defaultMessage: 'Results explorer',
}),
path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`,
navLinkStatus,
},
{
id: 'analyticsMap',
@ -97,67 +81,65 @@ function createDeepLinks(
defaultMessage: 'Analytics map',
}),
path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_MAP}`,
navLinkStatus,
},
],
};
},
getModelManagementDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(
mlCapabilities.isDFAEnabled || mlCapabilities.isNLPEnabled
);
getModelManagementDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.isDFAEnabled && !mlCapabilities.isNLPEnabled) return null;
const deepLinks: Array<AppDeepLink<LinkId>> = [
{
id: 'nodesOverview',
title: i18n.translate('xpack.ml.deepLink.trainedModels', {
defaultMessage: 'Trained Models',
}),
path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`,
},
];
if (!isServerless) {
deepLinks.push({
id: 'nodes',
title: i18n.translate('xpack.ml.deepLink.nodes', {
defaultMessage: 'Nodes',
}),
path: `/${ML_PAGES.NODES}`,
});
}
return {
id: 'modelManagement',
title: i18n.translate('xpack.ml.deepLink.modelManagement', {
defaultMessage: 'Model Management',
}),
path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`,
navLinkStatus,
deepLinks: [
{
id: 'nodesOverview',
title: i18n.translate('xpack.ml.deepLink.trainedModels', {
defaultMessage: 'Trained Models',
}),
path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`,
navLinkStatus,
},
{
id: 'nodes',
title: i18n.translate('xpack.ml.deepLink.nodes', {
defaultMessage: 'Nodes',
}),
path: `/${ML_PAGES.NODES}`,
navLinkStatus: getNavStatus(
mlCapabilities.isDFAEnabled || mlCapabilities.isNLPEnabled,
false
),
},
],
deepLinks,
};
},
getMemoryUsageDeepLink: (): AppDeepLink<LinkId> => {
getMemoryUsageDeepLink: (): AppDeepLink<LinkId> | null => {
if (!isFullLicense) return null;
return {
id: 'memoryUsage',
title: i18n.translate('xpack.ml.deepLink.memoryUsage', {
defaultMessage: 'Memory Usage',
}),
path: `/${ML_PAGES.MEMORY_USAGE}`,
navLinkStatus: getNavStatus(isFullLicense, true),
};
},
getSettingsDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled);
getSettingsDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.isADEnabled) return null;
return {
id: 'settings',
title: i18n.translate('xpack.ml.deepLink.settings', {
defaultMessage: 'Settings',
}),
path: `/${ML_PAGES.SETTINGS}`,
navLinkStatus,
deepLinks: [
{
id: 'calendarSettings',
@ -165,7 +147,6 @@ function createDeepLinks(
defaultMessage: 'Calendars',
}),
path: `/${ML_PAGES.CALENDARS_MANAGE}`,
navLinkStatus,
},
{
id: 'filterListsSettings',
@ -173,14 +154,14 @@ function createDeepLinks(
defaultMessage: 'Filter Lists',
}),
path: `/${ML_PAGES.SETTINGS}`, // Link to settings page as read only users cannot view filter lists.
navLinkStatus,
},
],
};
},
getAiopsDeepLink: (): AppDeepLink<LinkId> => {
const navLinkStatus = getNavStatus(mlCapabilities.canUseAiops);
getAiopsDeepLink: (): AppDeepLink<LinkId> | null => {
if (!mlCapabilities.canUseAiops) return null;
return {
id: 'aiOps',
title: i18n.translate('xpack.ml.deepLink.aiOps', {
@ -188,7 +169,6 @@ function createDeepLinks(
}),
// Default to the index select page for log rate analysis since we don't have an AIops overview page
path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`,
navLinkStatus,
deepLinks: [
{
id: 'logRateAnalysis',
@ -196,7 +176,6 @@ function createDeepLinks(
defaultMessage: 'Log Rate Analysis',
}),
path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`,
navLinkStatus,
},
{
id: 'logPatternAnalysis',
@ -204,7 +183,6 @@ function createDeepLinks(
defaultMessage: 'Log Pattern Analysis',
}),
path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT}`,
navLinkStatus,
},
{
id: 'changePointDetections',
@ -212,20 +190,20 @@ function createDeepLinks(
defaultMessage: 'Change Point Detection',
}),
path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT}`,
navLinkStatus,
},
],
};
},
getNotificationsDeepLink: (): AppDeepLink<LinkId> => {
getNotificationsDeepLink: (): AppDeepLink<LinkId> | null => {
if (!isFullLicense) return null;
return {
id: 'notifications',
title: i18n.translate('xpack.ml.deepLink.notifications', {
defaultMessage: 'Notifications',
}),
path: `/${ML_PAGES.NOTIFICATIONS}`,
navLinkStatus: getNavStatus(isFullLicense),
};
},
@ -236,7 +214,6 @@ function createDeepLinks(
defaultMessage: 'Data Visualizer',
}),
path: `/${ML_PAGES.DATA_VISUALIZER}`,
navLinkStatus: getNavStatus(true),
};
},
@ -248,7 +225,6 @@ function createDeepLinks(
}),
keywords: ['CSV', 'JSON'],
path: `/${ML_PAGES.DATA_VISUALIZER_FILE}`,
navLinkStatus: getNavStatus(true),
};
},
@ -259,7 +235,6 @@ function createDeepLinks(
defaultMessage: 'Index Data Visualizer',
}),
path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_SELECT}`,
navLinkStatus: getNavStatus(true),
};
},
@ -270,7 +245,6 @@ function createDeepLinks(
defaultMessage: 'ES|QL Data Visualizer',
}),
path: `/${ML_PAGES.DATA_VISUALIZER_ESQL}`,
navLinkStatus: getNavStatus(true),
};
},
@ -281,7 +255,6 @@ function createDeepLinks(
defaultMessage: 'Data Drift',
}),
path: `/${ML_PAGES.DATA_DRIFT_INDEX_SELECT}`,
navLinkStatus: getNavStatus(true),
};
},
};
@ -290,8 +263,10 @@ function createDeepLinks(
export function getDeepLinks(
isFullLicense: boolean,
mlCapabilities: MlCapabilities,
showMLNavMenu: boolean
) {
const links = createDeepLinks(mlCapabilities, isFullLicense, showMLNavMenu);
return Object.values(links).map((link) => link());
isServerless: boolean
): Array<AppDeepLink<LinkId>> {
const links = createDeepLinks(mlCapabilities, isFullLicense, isServerless);
return Object.values(links)
.map((link) => link())
.filter((link): link is AppDeepLink<LinkId> => link !== null);
}

View file

@ -14,7 +14,6 @@ import {
App,
AppDeepLink,
AppMountParameters,
AppNavLinkStatus,
AppUpdater,
CoreSetup,
CoreStart,
@ -197,7 +196,7 @@ export class Plugin
}),
order: 8001,
path: ALERTS_PATH,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
deepLinks: [
{
id: 'rules',
@ -205,7 +204,7 @@ export class Plugin
defaultMessage: 'Rules',
}),
path: RULES_PATH,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
},
],
},
@ -214,7 +213,7 @@ export class Plugin
title: i18n.translate('xpack.observability.slosLinkTitle', {
defaultMessage: 'SLOs',
}),
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
order: 8002,
path: SLOS_PATH,
},
@ -223,15 +222,13 @@ export class Plugin
extend: {
[CasesDeepLinkId.cases]: {
order: 8003,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
},
[CasesDeepLinkId.casesCreate]: {
navLinkStatus: AppNavLinkStatus.hidden,
searchable: false,
visibleIn: [],
},
[CasesDeepLinkId.casesConfigure]: {
navLinkStatus: AppNavLinkStatus.hidden,
searchable: false,
visibleIn: [],
},
},
}),
@ -316,7 +313,9 @@ export class Plugin
'user',
'experience',
],
searchable: !Boolean(pluginsSetup.serverless),
visibleIn: Boolean(pluginsSetup.serverless)
? ['home', 'kibanaOverview']
: ['globalSearch', 'home', 'kibanaOverview', 'sideNav'],
};
coreSetup.application.register(app);
@ -437,7 +436,7 @@ export class Plugin
//
// See https://github.com/elastic/kibana/issues/103325.
const otherLinks: NavigationEntry[] = deepLinks
.filter((link) => link.navLinkStatus === AppNavLinkStatus.visible)
.filter((link) => (link.visibleIn ?? []).length > 0)
.map((link) => ({
app: observabilityAppId,
label: link.title,

View file

@ -7,7 +7,6 @@
import React, { ComponentType, lazy, Ref } from 'react';
import ReactDOM from 'react-dom';
import {
AppNavLinkStatus,
DEFAULT_APP_CATEGORIES,
type AppMountParameters,
type CoreSetup,
@ -59,7 +58,7 @@ export class ObservabilityAIAssistantPlugin
euiIconType: 'logoObservability',
appRoute: '/app/observabilityAIAssistant',
category: DEFAULT_APP_CATEGORIES.observability,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
deepLinks: [
{
id: 'conversations',

View file

@ -682,7 +682,9 @@ describe('[Logs onboarding] System logs', () => {
});
});
describe('when integration installation succeed', () => {
// Skpping this test because it's failing in the CI
// https://github.com/elastic/kibana/issues/176995
xdescribe('when integration installation succeed', () => {
beforeEach(() => {
cy.deleteIntegration('system');
cy.intercept('GET', '/api/fleet/epm/packages/system').as(

View file

@ -17,7 +17,6 @@ import {
DEFAULT_APP_CATEGORIES,
Plugin,
PluginInitializerContext,
AppNavLinkStatus,
} from '@kbn/core/public';
import {
DataPublicPluginSetup,
@ -71,7 +70,6 @@ export class ObservabilityOnboardingPlugin
const config = this.ctx.config.get<ObservabilityOnboardingConfig>();
const {
ui: { enabled: isObservabilityOnboardingUiEnabled },
serverless: { enabled: isServerlessEnabled },
} = config;
const pluginSetupDeps = plugins;
@ -80,9 +78,6 @@ export class ObservabilityOnboardingPlugin
// and go to /app/observabilityOnboarding
if (isObservabilityOnboardingUiEnabled) {
core.application.register({
navLinkStatus: isServerlessEnabled
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
id: PLUGIN_ID,
title: 'Observability Onboarding',
order: 8500,
@ -110,6 +105,7 @@ export class ObservabilityOnboardingPlugin
config,
});
},
visibleIn: [],
});
}

View file

@ -6,7 +6,7 @@
*/
import { Subject } from 'rxjs';
import { App, AppDeepLink, ApplicationStart, AppNavLinkStatus, AppUpdater } from '@kbn/core/public';
import { App, AppDeepLink, ApplicationStart, AppUpdater } from '@kbn/core/public';
import { casesFeatureId, sloFeatureId } from '../../common';
import { updateGlobalNavigation } from './update_global_navigation';
@ -29,7 +29,7 @@ describe('updateGlobalNavigation', () => {
expect(callback).toHaveBeenCalledWith({
deepLinks,
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
});
});
});
@ -49,7 +49,7 @@ describe('updateGlobalNavigation', () => {
expect(callback).toHaveBeenCalledWith({
deepLinks,
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
@ -65,7 +65,7 @@ describe('updateGlobalNavigation', () => {
title: 'Cases',
order: 8003,
path: '/cases',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [], // no visibility set
};
const deepLinks = [caseRoute];
@ -81,10 +81,10 @@ describe('updateGlobalNavigation', () => {
deepLinks: [
{
...caseRoute,
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch'], // visibility set
},
],
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
});
@ -101,7 +101,7 @@ describe('updateGlobalNavigation', () => {
title: 'Cases',
order: 8003,
path: '/cases',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
const deepLinks = [caseRoute];
@ -114,13 +114,8 @@ describe('updateGlobalNavigation', () => {
updateGlobalNavigation({ capabilities, deepLinks, updater$ });
expect(callback).toHaveBeenCalledWith({
deepLinks: [
{
...caseRoute,
navLinkStatus: AppNavLinkStatus.hidden,
},
],
navLinkStatus: AppNavLinkStatus.visible,
deepLinks: [], // Deeplink has been filtered out
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
});
@ -138,7 +133,7 @@ describe('updateGlobalNavigation', () => {
title: 'Alerts',
order: 8001,
path: '/alerts',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
},
];
const callback = jest.fn();
@ -155,10 +150,10 @@ describe('updateGlobalNavigation', () => {
title: 'Alerts',
order: 8001,
path: '/alerts',
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch'],
},
],
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
});
@ -173,7 +168,7 @@ describe('updateGlobalNavigation', () => {
title: 'SLOs',
order: 8002,
path: '/slos',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
const deepLinks = [sloRoute];
@ -186,13 +181,8 @@ describe('updateGlobalNavigation', () => {
updateGlobalNavigation({ capabilities, deepLinks, updater$ });
expect(callback).toHaveBeenCalledWith({
deepLinks: [
{
...sloRoute,
navLinkStatus: AppNavLinkStatus.hidden,
},
],
navLinkStatus: AppNavLinkStatus.visible,
deepLinks: [], // Deeplink has been filtered out
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
@ -209,7 +199,7 @@ describe('updateGlobalNavigation', () => {
title: 'SLOs',
order: 8002,
path: '/slos',
navLinkStatus: AppNavLinkStatus.hidden,
visibleIn: [],
};
const deepLinks = [sloRoute];
@ -225,10 +215,10 @@ describe('updateGlobalNavigation', () => {
deepLinks: [
{
...sloRoute,
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch'],
},
],
navLinkStatus: AppNavLinkStatus.visible,
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
});

View file

@ -6,7 +6,7 @@
*/
import { Subject } from 'rxjs';
import { AppNavLinkStatus, AppUpdater, ApplicationStart, AppDeepLink } from '@kbn/core/public';
import { AppUpdater, ApplicationStart, AppDeepLink } from '@kbn/core/public';
import { CasesDeepLinkId } from '@kbn/cases-plugin/public';
import { casesFeatureId, sloFeatureId } from '../../common';
@ -27,43 +27,52 @@ export function updateGlobalNavigation({
uptime,
}).some((visible) => visible);
const updatedDeepLinks = deepLinks.map((link) => {
switch (link.id) {
case CasesDeepLinkId.cases:
return {
...link,
navLinkStatus:
capabilities[casesFeatureId].read_cases && someVisible
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
};
case 'alerts':
return {
...link,
navLinkStatus: someVisible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden,
};
case 'rules':
return {
...link,
navLinkStatus: someVisible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden,
};
case 'slos':
return {
...link,
navLinkStatus: !!capabilities[sloFeatureId]?.read
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
};
default:
return link;
}
});
const updatedDeepLinks = deepLinks
.map((link) => {
switch (link.id) {
case CasesDeepLinkId.cases:
if (capabilities[casesFeatureId].read_cases && someVisible) {
return {
...link,
visibleIn: ['sideNav', 'globalSearch'],
};
}
return null;
case 'alerts':
if (someVisible) {
return {
...link,
visibleIn: ['sideNav', 'globalSearch'],
};
}
return null;
case 'rules':
if (someVisible) {
return {
...link,
visibleIn: ['sideNav', 'globalSearch'],
};
}
return null;
case 'slos':
if (!!capabilities[sloFeatureId]?.read) {
return {
...link,
visibleIn: ['sideNav', 'globalSearch'],
};
}
return null;
default:
return link;
}
})
.filter((link): link is AppDeepLink => link !== null);
updater$.next(() => ({
deepLinks: updatedDeepLinks,
navLinkStatus:
visibleIn:
someVisible || !!capabilities[sloFeatureId]?.read
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
? ['sideNav', 'globalSearch', 'home', 'kibanaOverview']
: [],
}));
}

Some files were not shown because too many files have changed in this diff Show more