Management - New platform api (#52579)

* implement management new platform api
This commit is contained in:
Matthew Kime 2020-01-08 17:43:10 -06:00 committed by GitHub
parent e1e1d964c6
commit 9282f19bf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1296 additions and 212 deletions

View file

@ -28,7 +28,8 @@ import { I18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
import appTemplate from './app.html';
import landingTemplate from './landing.html';
import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management';
import { management, MANAGEMENT_BREADCRUMB } from 'ui/management';
import { ManagementSidebarNav } from '../../../../../plugins/management/public';
import {
FeatureCatalogueRegistryProvider,
FeatureCatalogueCategory,
@ -42,6 +43,7 @@ import {
EuiIcon,
EuiHorizontalRule,
} from '@elastic/eui';
import { npStart } from 'ui/new_platform';
const SIDENAV_ID = 'management-sidenav';
const LANDING_ID = 'management-landing';
@ -102,7 +104,7 @@ export function updateLandingPage(version) {
);
}
export function updateSidebar(items, id) {
export function updateSidebar(legacySections, id) {
const node = document.getElementById(SIDENAV_ID);
if (!node) {
return;
@ -110,7 +112,12 @@ export function updateSidebar(items, id) {
render(
<I18nContext>
<SidebarNav sections={items} selectedId={id} className="mgtSideNav" />
<ManagementSidebarNav
getSections={npStart.plugins.management.sections.getSectionsEnabled}
legacySections={legacySections}
selectedId={id}
className="mgtSideNav"
/>
</I18nContext>,
node
);

View file

@ -20,6 +20,7 @@
@import './saved_objects/index';
@import './share/index';
@import './style_compile/index';
@import '../../../plugins/management/public/components/index';
// The following are prefixed with "vis"

View file

@ -1 +0,0 @@
@import './components/index';

View file

@ -1,24 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Management filters and filters and maps section objects into SidebarNav items 1`] = `
Array [
Object {
"data-test-subj": "activeSection",
"href": undefined,
"icon": null,
"id": "activeSection",
"isSelected": false,
"items": Array [
Object {
"data-test-subj": "item",
"href": undefined,
"icon": null,
"id": "item",
"isSelected": false,
"name": "item",
},
],
"name": "activeSection",
},
]
`;

View file

@ -1,107 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EuiIcon, EuiSideNav, IconType, EuiScreenReaderOnly } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { IndexedArray } from 'ui/indexed_array';
interface Subsection {
disabled: boolean;
visible: boolean;
id: string;
display: string;
url?: string;
icon?: IconType;
}
interface Section extends Subsection {
visibleItems: IndexedArray<Subsection>;
}
const sectionVisible = (section: Subsection) => !section.disabled && section.visible;
const sectionToNav = (selectedId: string) => ({ display, id, url, icon }: Subsection) => ({
id,
name: display,
icon: icon ? <EuiIcon type={icon} /> : null,
isSelected: selectedId === id,
href: url,
'data-test-subj': id,
});
export const sideNavItems = (sections: Section[], selectedId: string) =>
sections
.filter(sectionVisible)
.filter(section => section.visibleItems.filter(sectionVisible).length)
.map(section => ({
items: section.visibleItems.filter(sectionVisible).map(sectionToNav(selectedId)),
...sectionToNav(selectedId)(section),
}));
interface SidebarNavProps {
sections: Section[];
selectedId: string;
}
interface SidebarNavState {
isSideNavOpenOnMobile: boolean;
}
export class SidebarNav extends React.Component<SidebarNavProps, SidebarNavState> {
constructor(props: SidebarNavProps) {
super(props);
this.state = {
isSideNavOpenOnMobile: false,
};
}
public render() {
const HEADER_ID = 'management-nav-header';
return (
<>
<EuiScreenReaderOnly>
<h2 id={HEADER_ID}>
{i18n.translate('common.ui.management.nav.label', {
defaultMessage: 'Management',
})}
</h2>
</EuiScreenReaderOnly>
<EuiSideNav
aria-labelledby={HEADER_ID}
mobileTitle={this.renderMobileTitle()}
isOpenOnMobile={this.state.isSideNavOpenOnMobile}
toggleOpenOnMobile={this.toggleOpenOnMobile}
items={sideNavItems(this.props.sections, this.props.selectedId)}
className="mgtSideBarNav"
/>
</>
);
}
private renderMobileTitle() {
return <FormattedMessage id="common.ui.management.nav.menu" defaultMessage="Management menu" />;
}
private toggleOpenOnMobile = () => {
this.setState({
isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile,
});
};
}

View file

@ -23,8 +23,6 @@ export {
PAGE_FOOTER_COMPONENT,
} from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry';
export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry';
export { SidebarNav } from './components';
export { MANAGEMENT_BREADCRUMB } from './breadcrumbs';
import { npStart } from 'ui/new_platform';
export const management = npStart.plugins.management.legacy;

View file

@ -3,5 +3,5 @@
"version": "kibana",
"server": false,
"ui": true,
"requiredPlugins": []
"requiredPlugins": ["kibana_legacy"]
}

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Management app can mount and unmount 1`] = `
<div>
<div>
Test App - Hello world!
</div>
</div>
`;
exports[`Management app can mount and unmount 2`] = `<div />`;

View file

@ -0,0 +1 @@
@import './management_sidebar_nav/index';

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { ManagementSidebarNav } from './management_sidebar_nav';
export { ManagementChrome } from './management_chrome';

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { SidebarNav } from './sidebar_nav';
export { ManagementChrome } from './management_chrome';

View file

@ -0,0 +1,57 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as React from 'react';
import { EuiPage, EuiPageBody } from '@elastic/eui';
import { I18nProvider } from '@kbn/i18n/react';
import { ManagementSidebarNav } from '../management_sidebar_nav';
import { LegacySection } from '../../types';
import { ManagementSection } from '../../management_section';
interface Props {
getSections: () => ManagementSection[];
legacySections: LegacySection[];
selectedId: string;
onMounted: (element: HTMLDivElement) => void;
}
export class ManagementChrome extends React.Component<Props> {
private container = React.createRef<HTMLDivElement>();
componentDidMount() {
if (this.container.current) {
this.props.onMounted(this.container.current);
}
}
render() {
return (
<I18nProvider>
<EuiPage>
<ManagementSidebarNav
getSections={this.props.getSections}
legacySections={this.props.legacySections}
selectedId={this.props.selectedId}
/>
<EuiPageBody>
<div ref={this.container} />
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
}
}

View file

@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Management adds legacy apps to existing SidebarNav sections 1`] = `
Array [
Object {
"data-test-subj": "activeSection",
"icon": null,
"id": "activeSection",
"items": Array [
Object {
"data-test-subj": "item",
"href": undefined,
"id": "item",
"isSelected": false,
"name": "item",
"order": undefined,
},
],
"name": "activeSection",
"order": 10,
},
Object {
"data-test-subj": "no-active-items",
"icon": null,
"id": "no-active-items",
"items": Array [
Object {
"data-test-subj": "disabled",
"href": undefined,
"id": "disabled",
"isSelected": false,
"name": "disabled",
"order": undefined,
},
Object {
"data-test-subj": "notVisible",
"href": undefined,
"id": "notVisible",
"isSelected": false,
"name": "notVisible",
"order": undefined,
},
],
"name": "No active items",
"order": 10,
},
]
`;
exports[`Management maps legacy sections and apps into SidebarNav items 1`] = `
Array [
Object {
"data-test-subj": "no-active-items",
"icon": null,
"id": "no-active-items",
"items": Array [
Object {
"data-test-subj": "disabled",
"href": undefined,
"id": "disabled",
"isSelected": false,
"name": "disabled",
"order": undefined,
},
Object {
"data-test-subj": "notVisible",
"href": undefined,
"id": "notVisible",
"isSelected": false,
"name": "notVisible",
"order": undefined,
},
],
"name": "No active items",
"order": 10,
},
Object {
"data-test-subj": "activeSection",
"icon": null,
"id": "activeSection",
"items": Array [
Object {
"data-test-subj": "item",
"href": undefined,
"id": "item",
"isSelected": false,
"name": "item",
"order": undefined,
},
],
"name": "activeSection",
"order": 10,
},
]
`;

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { ManagementSidebarNav } from './management_sidebar_nav';

View file

@ -17,8 +17,8 @@
* under the License.
*/
import { IndexedArray } from '../../indexed_array';
import { sideNavItems } from '../components/sidebar_nav';
import { IndexedArray } from '../../../../../legacy/ui/public/indexed_array';
import { mergeLegacyItems } from './management_sidebar_nav';
const toIndexedArray = (initialSet: any[]) =>
new IndexedArray({
@ -30,30 +30,33 @@ const toIndexedArray = (initialSet: any[]) =>
const activeProps = { visible: true, disabled: false };
const disabledProps = { visible: true, disabled: true };
const notVisibleProps = { visible: false, disabled: false };
const visibleItem = { display: 'item', id: 'item', ...activeProps };
const notVisibleSection = {
display: 'Not visible',
id: 'not-visible',
order: 10,
visibleItems: toIndexedArray([visibleItem]),
...notVisibleProps,
};
const disabledSection = {
display: 'Disabled',
id: 'disabled',
order: 10,
visibleItems: toIndexedArray([visibleItem]),
...disabledProps,
};
const noItemsSection = {
display: 'No items',
id: 'no-items',
order: 10,
visibleItems: toIndexedArray([]),
...activeProps,
};
const noActiveItemsSection = {
display: 'No active items',
id: 'no-active-items',
order: 10,
visibleItems: toIndexedArray([
{ display: 'disabled', id: 'disabled', ...disabledProps },
{ display: 'notVisible', id: 'notVisible', ...notVisibleProps },
@ -63,6 +66,7 @@ const noActiveItemsSection = {
const activeSection = {
display: 'activeSection',
id: 'activeSection',
order: 10,
visibleItems: toIndexedArray([visibleItem]),
...activeProps,
};
@ -76,7 +80,19 @@ const managementSections = [
];
describe('Management', () => {
it('filters and filters and maps section objects into SidebarNav items', () => {
expect(sideNavItems(managementSections, 'active-item-id')).toMatchSnapshot();
it('maps legacy sections and apps into SidebarNav items', () => {
expect(mergeLegacyItems([], managementSections, 'active-item-id')).toMatchSnapshot();
});
it('adds legacy apps to existing SidebarNav sections', () => {
const navSection = {
'data-test-subj': 'activeSection',
icon: null,
id: 'activeSection',
items: [],
name: 'activeSection',
order: 10,
};
expect(mergeLegacyItems([navSection], managementSections, 'active-item-id')).toMatchSnapshot();
});
});

View file

@ -0,0 +1,200 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
EuiIcon,
// @ts-ignore
EuiSideNav,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { LegacySection, LegacyApp } from '../../types';
import { ManagementApp } from '../../management_app';
import { ManagementSection } from '../../management_section';
interface NavApp {
id: string;
name: string;
[key: string]: unknown;
order: number; // only needed while merging platform and legacy
}
interface NavSection extends NavApp {
items: NavApp[];
}
interface ManagementSidebarNavProps {
getSections: () => ManagementSection[];
legacySections: LegacySection[];
selectedId: string;
}
interface ManagementSidebarNavState {
isSideNavOpenOnMobile: boolean;
}
const managementSectionOrAppToNav = (appOrSection: ManagementApp | ManagementSection) => ({
id: appOrSection.id,
name: appOrSection.title,
'data-test-subj': appOrSection.id,
order: appOrSection.order,
});
const managementSectionToNavSection = (section: ManagementSection) => {
const iconType = section.euiIconType
? section.euiIconType
: section.icon
? section.icon
: 'empty';
return {
icon: <EuiIcon type={iconType} size="m" />,
...managementSectionOrAppToNav(section),
};
};
const managementAppToNavItem = (selectedId?: string, parentId?: string) => (
app: ManagementApp
) => ({
isSelected: selectedId === app.id,
href: `#/management/${parentId}/${app.id}`,
...managementSectionOrAppToNav(app),
});
const legacySectionToNavSection = (section: LegacySection) => ({
name: section.display,
id: section.id,
icon: section.icon ? <EuiIcon type={section.icon} /> : null,
items: [],
'data-test-subj': section.id,
// @ts-ignore
order: section.order,
});
const legacyAppToNavItem = (app: LegacyApp, selectedId: string) => ({
isSelected: selectedId === app.id,
name: app.display,
id: app.id,
href: app.url,
'data-test-subj': app.id,
// @ts-ignore
order: app.order,
});
const sectionVisible = (section: LegacySection | LegacyApp) => !section.disabled && section.visible;
const sideNavItems = (sections: ManagementSection[], selectedId: string) =>
sections.map(section => ({
items: section.getAppsEnabled().map(managementAppToNavItem(selectedId, section.id)),
...managementSectionToNavSection(section),
}));
const findOrAddSection = (navItems: NavSection[], legacySection: LegacySection): NavSection => {
const foundSection = navItems.find(sec => sec.id === legacySection.id);
if (foundSection) {
return foundSection;
} else {
const newSection = legacySectionToNavSection(legacySection);
navItems.push(newSection);
navItems.sort((a: NavSection, b: NavSection) => a.order - b.order); // only needed while merging platform and legacy
return newSection;
}
};
export const mergeLegacyItems = (
navItems: NavSection[],
legacySections: LegacySection[],
selectedId: string
) => {
const filteredLegacySections = legacySections
.filter(sectionVisible)
.filter(section => section.visibleItems.length);
filteredLegacySections.forEach(legacySection => {
const section = findOrAddSection(navItems, legacySection);
legacySection.visibleItems.forEach(app => {
section.items.push(legacyAppToNavItem(app, selectedId));
return section.items.sort((a, b) => a.order - b.order);
});
});
return navItems;
};
const sectionsToItems = (
sections: ManagementSection[],
legacySections: LegacySection[],
selectedId: string
) => {
const navItems = sideNavItems(sections, selectedId);
return mergeLegacyItems(navItems, legacySections, selectedId);
};
export class ManagementSidebarNav extends React.Component<
ManagementSidebarNavProps,
ManagementSidebarNavState
> {
constructor(props: ManagementSidebarNavProps) {
super(props);
this.state = {
isSideNavOpenOnMobile: false,
};
}
public render() {
const HEADER_ID = 'management-nav-header';
return (
<>
<EuiScreenReaderOnly>
<h2 id={HEADER_ID}>
{i18n.translate('management.nav.label', {
defaultMessage: 'Management',
})}
</h2>
</EuiScreenReaderOnly>
<EuiSideNav
aria-labelledby={HEADER_ID}
mobileTitle={this.renderMobileTitle()}
isOpenOnMobile={this.state.isSideNavOpenOnMobile}
toggleOpenOnMobile={this.toggleOpenOnMobile}
items={sectionsToItems(
this.props.getSections(),
this.props.legacySections,
this.props.selectedId
)}
className="mgtSideBarNav"
/>
</>
);
}
private renderMobileTitle() {
return <FormattedMessage id="management.nav.menu" defaultMessage="Management menu" />;
}
private toggleOpenOnMobile = () => {
this.setState({
isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile,
});
};
}

View file

@ -24,4 +24,7 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new ManagementPlugin();
}
export { ManagementStart } from './types';
export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types';
export { ManagementApp } from './management_app';
export { ManagementSection } from './management_section';
export { ManagementSidebarNav } from './components'; // for use in legacy management apps

View file

@ -17,4 +17,5 @@
* under the License.
*/
export { management } from './sections_register';
export { LegacyManagementAdapter } from './sections_register';
export { LegacyManagementSection } from './section';

View file

@ -22,7 +22,7 @@ import { IndexedArray } from '../../../../legacy/ui/public/indexed_array';
const listeners = [];
export class ManagementSection {
export class LegacyManagementSection {
/**
* @param {string} id
* @param {object} options
@ -83,7 +83,11 @@ export class ManagementSection {
*/
register(id, options = {}) {
const item = new ManagementSection(id, assign(options, { parent: this }), this.capabilities);
const item = new LegacyManagementSection(
id,
assign(options, { parent: this }),
this.capabilities
);
if (this.hasItem(id)) {
throw new Error(`'${id}' is already registered`);

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ManagementSection } from './section';
import { LegacyManagementSection } from './section';
import { IndexedArray } from '../../../../legacy/ui/public/indexed_array';
const capabilitiesMock = {
@ -29,42 +29,42 @@ const capabilitiesMock = {
describe('ManagementSection', () => {
describe('constructor', () => {
it('defaults display to id', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.display).toBe('kibana');
});
it('defaults visible to true', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.visible).toBe(true);
});
it('defaults disabled to false', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.disabled).toBe(false);
});
it('defaults tooltip to empty string', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.tooltip).toBe('');
});
it('defaults url to empty string', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.url).toBe('');
});
it('exposes items', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.items).toHaveLength(0);
});
it('exposes visibleItems', () => {
const section = new ManagementSection('kibana', {}, capabilitiesMock);
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
expect(section.visibleItems).toHaveLength(0);
});
it('assigns all options', () => {
const section = new ManagementSection(
const section = new LegacyManagementSection(
'kibana',
{ description: 'test', url: 'foobar' },
capabilitiesMock
@ -78,11 +78,11 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
});
it('returns a ManagementSection', () => {
expect(section.register('about')).toBeInstanceOf(ManagementSection);
expect(section.register('about')).toBeInstanceOf(LegacyManagementSection);
});
it('provides a reference to the parent', () => {
@ -93,7 +93,7 @@ describe('ManagementSection', () => {
section.register('about', { description: 'test' });
expect(section.items).toHaveLength(1);
expect(section.items[0]).toBeInstanceOf(ManagementSection);
expect(section.items[0]).toBeInstanceOf(LegacyManagementSection);
expect(section.items[0].id).toBe('about');
});
@ -126,7 +126,7 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
section.register('about');
});
@ -157,12 +157,12 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
section.register('about');
});
it('returns registered section', () => {
expect(section.getSection('about')).toBeInstanceOf(ManagementSection);
expect(section.getSection('about')).toBeInstanceOf(LegacyManagementSection);
});
it('returns undefined if un-registered', () => {
@ -171,7 +171,7 @@ describe('ManagementSection', () => {
it('returns sub-sections specified via a /-separated path', () => {
section.getSection('about').register('time');
expect(section.getSection('about/time')).toBeInstanceOf(ManagementSection);
expect(section.getSection('about/time')).toBeInstanceOf(LegacyManagementSection);
expect(section.getSection('about/time')).toBe(section.getSection('about').getSection('time'));
});
@ -184,7 +184,7 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
section.register('three', { order: 3 });
section.register('one', { order: 1 });
@ -214,7 +214,7 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
});
it('hide sets visible to false', () => {
@ -233,7 +233,7 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
});
it('disable sets disabled to true', () => {
@ -251,7 +251,7 @@ describe('ManagementSection', () => {
let section;
beforeEach(() => {
section = new ManagementSection('kibana', {}, capabilitiesMock);
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
section.register('three', { order: 3 });
section.register('one', { order: 1 });

View file

@ -17,44 +17,48 @@
* under the License.
*/
import { ManagementSection } from './section';
import { LegacyManagementSection } from './section';
import { i18n } from '@kbn/i18n';
export const management = capabilities => {
const main = new ManagementSection(
'management',
{
display: i18n.translate('management.displayName', {
defaultMessage: 'Management',
export class LegacyManagementAdapter {
main = undefined;
init = capabilities => {
this.main = new LegacyManagementSection(
'management',
{
display: i18n.translate('management.displayName', {
defaultMessage: 'Management',
}),
},
capabilities
);
this.main.register('data', {
display: i18n.translate('management.connectDataDisplayName', {
defaultMessage: 'Connect Data',
}),
},
capabilities
);
order: 0,
});
main.register('data', {
display: i18n.translate('management.connectDataDisplayName', {
defaultMessage: 'Connect Data',
}),
order: 0,
});
this.main.register('elasticsearch', {
display: 'Elasticsearch',
order: 20,
icon: 'logoElasticsearch',
});
main.register('elasticsearch', {
display: 'Elasticsearch',
order: 20,
icon: 'logoElasticsearch',
});
this.main.register('kibana', {
display: 'Kibana',
order: 30,
icon: 'logoKibana',
});
main.register('kibana', {
display: 'Kibana',
order: 30,
icon: 'logoKibana',
});
this.main.register('logstash', {
display: 'Logstash',
order: 30,
icon: 'logoLogstash',
});
main.register('logstash', {
display: 'Logstash',
order: 30,
icon: 'logoLogstash',
});
return main;
};
return this.main;
};
getManagement = () => this.main;
}

View file

@ -0,0 +1,66 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { coreMock } from '../../../core/public/mocks';
import { ManagementApp } from './management_app';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
function createTestApp() {
const legacySection = new LegacyManagementSection('legacy');
return new ManagementApp(
{
id: 'test-app',
title: 'Test App',
basePath: '',
mount(params) {
params.setBreadcrumbs([{ text: 'Test App' }]);
ReactDOM.render(<div>Test App - Hello world!</div>, params.element);
return () => {
ReactDOM.unmountComponentAtNode(params.element);
};
},
},
() => [],
jest.fn(),
() => legacySection,
coreMock.createSetup().getStartServices
);
}
test('Management app can mount and unmount', async () => {
const testApp = createTestApp();
const container = document.createElement('div');
document.body.appendChild(container);
const unmount = testApp.mount({ element: container, basePath: '', setBreadcrumbs: jest.fn() });
expect(container).toMatchSnapshot();
(await unmount)();
expect(container).toMatchSnapshot();
});
test('Enabled by default, can disable', () => {
const testApp = createTestApp();
expect(testApp.enabled).toBe(true);
testApp.disable();
expect(testApp.enabled).toBe(false);
});

View file

@ -0,0 +1,102 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as React from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { CreateManagementApp, ManagementSectionMount, Unmount } from './types';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { ManagementChrome } from './components';
import { ManagementSection } from './management_section';
import { ChromeBreadcrumb, CoreSetup } from '../../../core/public/';
export class ManagementApp {
readonly id: string;
readonly title: string;
readonly basePath: string;
readonly order: number;
readonly mount: ManagementSectionMount;
protected enabledStatus: boolean = true;
constructor(
{ id, title, basePath, order = 100, mount }: CreateManagementApp,
getSections: () => ManagementSection[],
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagementSections: () => LegacyManagementSection,
getStartServices: CoreSetup['getStartServices']
) {
this.id = id;
this.title = title;
this.basePath = basePath;
this.order = order;
this.mount = mount;
registerLegacyApp({
id: basePath.substr(1), // get rid of initial slash
title,
mount: async ({}, params) => {
let appUnmount: Unmount;
async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) {
const [coreStart] = await getStartServices();
coreStart.chrome.setBreadcrumbs([
{
text: i18n.translate('management.breadcrumb', {
defaultMessage: 'Management',
}),
href: '#/management',
},
...crumbs,
]);
}
ReactDOM.render(
<ManagementChrome
getSections={getSections}
selectedId={id}
legacySections={getLegacyManagementSections().items}
onMounted={async element => {
appUnmount = await mount({
basePath,
element,
setBreadcrumbs,
});
}}
/>,
params.element
);
return async () => {
appUnmount();
ReactDOM.unmountComponentAtNode(params.element);
};
},
});
}
public enable() {
this.enabledStatus = true;
}
public disable() {
this.enabledStatus = false;
}
public get enabled() {
return this.enabledStatus;
}
}

View file

@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ManagementSection } from './management_section';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { coreMock } from '../../../core/public/mocks';
function createSection(registerLegacyApp: () => void) {
const legacySection = new LegacyManagementSection('legacy');
const getLegacySection = () => legacySection;
const getManagementSections: () => ManagementSection[] = () => [];
const testSectionConfig = { id: 'test-section', title: 'Test Section' };
return new ManagementSection(
testSectionConfig,
getManagementSections,
registerLegacyApp,
getLegacySection,
coreMock.createSetup().getStartServices
);
}
test('cannot register two apps with the same id', () => {
const registerLegacyApp = jest.fn();
const section = createSection(registerLegacyApp);
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
section.registerApp(testAppConfig);
expect(registerLegacyApp).toHaveBeenCalled();
expect(section.apps.length).toEqual(1);
expect(() => {
section.registerApp(testAppConfig);
}).toThrow();
});
test('can enable and disable apps', () => {
const registerLegacyApp = jest.fn();
const section = createSection(registerLegacyApp);
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
const app = section.registerApp(testAppConfig);
expect(section.getAppsEnabled().length).toEqual(1);
app.disable();
expect(section.getAppsEnabled().length).toEqual(0);
});

View file

@ -0,0 +1,78 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { CreateSection, RegisterManagementAppArgs } from './types';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
import { CoreSetup } from '../../../core/public';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { ManagementApp } from './management_app';
export class ManagementSection {
public readonly id: string = '';
public readonly title: string = '';
public readonly apps: ManagementApp[] = [];
public readonly order: number;
public readonly euiIconType?: string;
public readonly icon?: string;
private readonly getSections: () => ManagementSection[];
private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp'];
private readonly getLegacyManagementSection: () => LegacyManagementSection;
private readonly getStartServices: CoreSetup['getStartServices'];
constructor(
{ id, title, order = 100, euiIconType, icon }: CreateSection,
getSections: () => ManagementSection[],
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagementSection: () => ManagementSection,
getStartServices: CoreSetup['getStartServices']
) {
this.id = id;
this.title = title;
this.order = order;
this.euiIconType = euiIconType;
this.icon = icon;
this.getSections = getSections;
this.registerLegacyApp = registerLegacyApp;
this.getLegacyManagementSection = getLegacyManagementSection;
this.getStartServices = getStartServices;
}
registerApp({ id, title, order, mount }: RegisterManagementAppArgs) {
if (this.getApp(id)) {
throw new Error(`Management app already registered - id: ${id}, title: ${title}`);
}
const app = new ManagementApp(
{ id, title, order, mount, basePath: `/management/${this.id}/${id}` },
this.getSections,
this.registerLegacyApp,
this.getLegacyManagementSection,
this.getStartServices
);
this.apps.push(app);
return app;
}
getApp(id: ManagementApp['id']) {
return this.apps.find(app => app.id === id);
}
getAppsEnabled() {
return this.apps.filter(app => app.enabled).sort((a, b) => a.order - b.order);
}
}

View file

@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ManagementService } from './management_service';
import { coreMock } from '../../../core/public/mocks';
const mockKibanaLegacy = { registerLegacyApp: () => {}, forwardApp: () => {} };
test('Provides default sections', () => {
const service = new ManagementService().setup(
mockKibanaLegacy,
() => {},
coreMock.createSetup().getStartServices
);
expect(service.getAllSections().length).toEqual(3);
expect(service.getSection('kibana')).not.toBeUndefined();
expect(service.getSection('logstash')).not.toBeUndefined();
expect(service.getSection('elasticsearch')).not.toBeUndefined();
});
test('Register section, enable and disable', () => {
const service = new ManagementService().setup(
mockKibanaLegacy,
() => {},
coreMock.createSetup().getStartServices
);
const testSection = service.register({ id: 'test-section', title: 'Test Section' });
expect(service.getSection('test-section')).not.toBeUndefined();
const testApp = testSection.registerApp({
id: 'test-app',
title: 'Test App',
mount: () => () => {},
});
expect(testSection.getApp('test-app')).not.toBeUndefined();
expect(service.getSectionsEnabled().length).toEqual(1);
testApp.disable();
expect(service.getSectionsEnabled().length).toEqual(0);
});

View file

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ManagementSection } from './management_section';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { CreateSection } from './types';
import { CoreSetup, CoreStart } from '../../../core/public';
export class ManagementService {
private sections: ManagementSection[] = [];
private register(
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagement: () => LegacyManagementSection,
getStartServices: CoreSetup['getStartServices']
) {
return (section: CreateSection) => {
if (this.getSection(section.id)) {
throw Error(`ManagementSection '${section.id}' already registered`);
}
const newSection = new ManagementSection(
section,
this.getSectionsEnabled.bind(this),
registerLegacyApp,
getLegacyManagement,
getStartServices
);
this.sections.push(newSection);
return newSection;
};
}
private getSection(sectionId: ManagementSection['id']) {
return this.sections.find(section => section.id === sectionId);
}
private getAllSections() {
return this.sections;
}
private getSectionsEnabled() {
return this.sections
.filter(section => section.getAppsEnabled().length > 0)
.sort((a, b) => a.order - b.order);
}
private sharedInterface = {
getSection: this.getSection.bind(this),
getSectionsEnabled: this.getSectionsEnabled.bind(this),
getAllSections: this.getAllSections.bind(this),
};
public setup(
kibanaLegacy: KibanaLegacySetup,
getLegacyManagement: () => LegacyManagementSection,
getStartServices: CoreSetup['getStartServices']
) {
const register = this.register.bind(this)(
kibanaLegacy.registerLegacyApp,
getLegacyManagement,
getStartServices
);
register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' });
register({ id: 'logstash', title: 'Logstash', order: 30, euiIconType: 'logoLogstash' });
register({
id: 'elasticsearch',
title: 'Elasticsearch',
order: 20,
euiIconType: 'logoElasticsearch',
});
return {
register,
...this.sharedInterface,
};
}
public start(navigateToApp: CoreStart['application']['navigateToApp']) {
return {
navigateToApp, // apps are currently registered as top level apps but this may change in the future
...this.sharedInterface,
};
}
}

View file

@ -18,18 +18,30 @@
*/
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import { ManagementStart } from './types';
import { ManagementSetup, ManagementStart } from './types';
import { ManagementService } from './management_service';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
// @ts-ignore
import { management } from './legacy';
import { LegacyManagementAdapter } from './legacy';
export class ManagementPlugin implements Plugin<{}, ManagementStart> {
public setup(core: CoreSetup) {
return {};
export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart> {
private managementSections = new ManagementService();
private legacyManagement = new LegacyManagementAdapter();
public setup(core: CoreSetup, { kibana_legacy }: { kibana_legacy: KibanaLegacySetup }) {
return {
sections: this.managementSections.setup(
kibana_legacy,
this.legacyManagement.getManagement,
core.getStartServices
),
};
}
public start(core: CoreStart) {
return {
legacy: management(core.application.capabilities),
sections: this.managementSections.start(core.application.navigateToApp),
legacy: this.legacyManagement.init(core.application.capabilities),
};
}
}

View file

@ -17,6 +17,82 @@
* under the License.
*/
import { IconType } from '@elastic/eui';
import { ManagementApp } from './management_app';
import { ManagementSection } from './management_section';
import { ChromeBreadcrumb, ApplicationStart } from '../../../core/public/';
export interface ManagementSetup {
sections: SectionsServiceSetup;
}
export interface ManagementStart {
sections: SectionsServiceStart;
legacy: any;
}
interface SectionsServiceSetup {
getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined;
getAllSections: () => ManagementSection[];
register: RegisterSection;
}
interface SectionsServiceStart {
getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined;
getAllSections: () => ManagementSection[];
navigateToApp: ApplicationStart['navigateToApp'];
}
export interface CreateSection {
id: string;
title: string;
order?: number;
euiIconType?: string; // takes precedence over `icon` property.
icon?: string; // URL to image file; fallback if no `euiIconType`
}
export type RegisterSection = (section: CreateSection) => ManagementSection;
export interface RegisterManagementAppArgs {
id: string;
title: string;
mount: ManagementSectionMount;
order?: number;
}
export type RegisterManagementApp = (managementApp: RegisterManagementAppArgs) => ManagementApp;
export type Unmount = () => Promise<void> | void;
interface ManagementAppMountParams {
basePath: string; // base path for setting up your router
element: HTMLElement; // element the section should render into
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
}
export type ManagementSectionMount = (
params: ManagementAppMountParams
) => Unmount | Promise<Unmount>;
export interface CreateManagementApp {
id: string;
title: string;
basePath: string;
order?: number;
mount: ManagementSectionMount;
}
export interface LegacySection extends LegacyApp {
visibleItems: LegacyApp[];
}
export interface LegacyApp {
disabled: boolean;
visible: boolean;
id: string;
display: string;
url?: string;
euiIconType?: IconType;
icon?: string;
order: number;
}

View file

@ -37,6 +37,7 @@ export default async function({ readConfigFile }) {
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/embeddable_explorer'),
require.resolve('./test_suites/core_plugins'),
require.resolve('./test_suites/management'),
],
services: {
...functionalConfig.get('services'),

View file

@ -0,0 +1,9 @@
{
"id": "management_test_plugin",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["management_test_plugin"],
"server": false,
"ui": true,
"requiredPlugins": ["management"]
}

View file

@ -0,0 +1,17 @@
{
"name": "management_test_plugin",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/management_test_plugin",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.7.2"
}
}

View file

@ -0,0 +1,28 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { PluginInitializer } from 'kibana/public';
import {
ManagementTestPlugin,
ManagementTestPluginSetup,
ManagementTestPluginStart,
} from './plugin';
export const plugin: PluginInitializer<ManagementTestPluginSetup, ManagementTestPluginStart> = () =>
new ManagementTestPlugin();

View file

@ -0,0 +1,73 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom';
import { CoreSetup, Plugin } from 'kibana/public';
import { ManagementSetup } from '../../../../../src/plugins/management/public';
export class ManagementTestPlugin
implements Plugin<ManagementTestPluginSetup, ManagementTestPluginStart> {
public setup(core: CoreSetup, { management }: { management: ManagementSetup }) {
const testSection = management.sections.register({
id: 'test-section',
title: 'Test Section',
euiIconType: 'logoKibana',
order: 25,
});
testSection!.registerApp({
id: 'test-management',
title: 'Management Test',
mount(params) {
params.setBreadcrumbs([{ text: 'Management Test' }]);
ReactDOM.render(
<Router>
<h1 data-test-subj="test-management-header">Hello from management test plugin</h1>
<Switch>
<Route exact path={`${params.basePath}`}>
<Link to={`${params.basePath}/one`} data-test-subj="test-management-link-one">
Link to /one
</Link>
</Route>
<Route path={`${params.basePath}/one`}>
<Link to={`${params.basePath}`} data-test-subj="test-management-link-basepath">
Link to basePath
</Link>
</Route>
</Switch>
</Router>,
params.element
);
return () => {
ReactDOM.unmountComponentAtNode(params.element);
};
},
});
return {};
}
public start() {}
public stop() {}
}
export type ManagementTestPluginSetup = ReturnType<ManagementTestPlugin['setup']>;
export type ManagementTestPluginStart = ReturnType<ManagementTestPlugin['start']>;

View file

@ -0,0 +1,14 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"../../../../typings/**/*",
],
"exclude": []
}

View file

@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default function({ loadTestFile }) {
describe('management plugin', () => {
loadTestFile(require.resolve('./management_plugin'));
});
}

View file

@ -0,0 +1,40 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default function({ getService, getPageObjects }) {
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
describe('management plugin', function describeIndexTests() {
before(async () => {
await PageObjects.common.navigateToActualUrl('kibana', 'management');
});
it('should be able to navigate to management test app', async () => {
await testSubjects.click('test-management');
await testSubjects.existOrFail('test-management-header');
});
it('should be able to navigate within management test app', async () => {
await testSubjects.click('test-management-link-one');
await testSubjects.click('test-management-link-basepath');
await testSubjects.existOrFail('test-management-link-one');
});
});
}

View file

@ -34,7 +34,7 @@ declare module '@elastic/eui' {
items: Array<{
id: string;
name: string;
onClick: () => void;
onClick?: () => void;
}>;
}>;
mobileTitle?: React.ReactNode;

View file

@ -6,6 +6,12 @@
import { ManagementService } from '.';
const mockSections = {
getSection: jest.fn(),
getAllSections: jest.fn(),
navigateToApp: jest.fn(),
};
describe('ManagementService', () => {
describe('#start', () => {
it('registers the spaces management page under the kibana section', () => {
@ -18,6 +24,7 @@ describe('ManagementService', () => {
legacy: {
getSection: jest.fn().mockReturnValue(mockKibanaSection),
},
sections: mockSections,
};
const deps = {
@ -49,6 +56,7 @@ describe('ManagementService', () => {
legacy: {
getSection: jest.fn().mockReturnValue(mockKibanaSection),
},
sections: mockSections,
};
const deps = {
@ -66,6 +74,7 @@ describe('ManagementService', () => {
legacy: {
getSection: jest.fn().mockReturnValue(undefined),
},
sections: mockSections,
};
const deps = {
@ -94,6 +103,7 @@ describe('ManagementService', () => {
legacy: {
getSection: jest.fn().mockReturnValue(mockKibanaSection),
},
sections: mockSections,
};
const deps = {

View file

@ -441,7 +441,9 @@
"common.ui.flotCharts.tueLabel": "火",
"common.ui.flotCharts.wedLabel": "水",
"common.ui.management.breadcrumb": "管理",
"common.ui.management.nav.menu": "管理メニュー",
"management.connectDataDisplayName": "データに接続",
"management.displayName": "管理",
"management.nav.menu": "管理メニュー",
"common.ui.modals.cancelButtonLabel": "キャンセル",
"common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}",
"common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。",

View file

@ -441,7 +441,9 @@
"common.ui.flotCharts.tueLabel": "周二",
"common.ui.flotCharts.wedLabel": "周三",
"common.ui.management.breadcrumb": "管理",
"common.ui.management.nav.menu": "管理菜单",
"management.connectDataDisplayName": "连接数据",
"management.displayName": "管理",
"management.nav.menu": "管理菜单",
"common.ui.modals.cancelButtonLabel": "取消",
"common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}{errMessage}",
"common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。",