From 430f2a635f4cd9131075e755e18d94780c6273ed Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Tue, 20 Aug 2019 17:03:17 -0600 Subject: [PATCH 1/8] Initial draft for management RFC. --- rfcs/text/0006_management_section_service.md | 284 +++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 rfcs/text/0006_management_section_service.md diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md new file mode 100644 index 0000000000000..41a033df36a06 --- /dev/null +++ b/rfcs/text/0006_management_section_service.md @@ -0,0 +1,284 @@ +- Start Date: 2019-08-20 +- RFC PR: TBD +- Kibana Issue: #43499 + +# Summary +Management is one of the four primary "domains" covered by @elastic/kibana-app-arch (along with Data, Embeddables, and Visualizations). There are two main purposes for this service: + +1. Own the management "framework" -- the UI that displays the management sidebar nav, the landing page, and handles rendering each of the sections +2. Expose a registry for other plugins to add their own registry sections to the UI and add nested links to them in the sidebar. + +The purpose of this RFC is to consider item 2 above -- the service for registering sections to the nav & loading them up. + +# Motivation + +## Why now? +The main driver for considering this now is that the Management API moving to the new platform is going to block other teams from completing migration, so we need to have an answer to what the new platform version of the API looks like as soon as possible in `7.x`. + +## Why not just keep the current API and redesign later? +The answer to that has to do with the items that are currently used in the management implementation which must be removed in order to migrate to NP: the framework currently registers a `uiExport`, and relies on `IndexedArray`, `uiRegistry`, and `ui/routes`. + +This means that we will basically need to rebuild the service anyway in order to migrate to the new platform. So if we are going to invest that time, we might as well invest it in building the API the way we want it to be longer term, rather than creating more work for ourselves later. + +## Technical goals +- Remove another usage of `IndexedArray` & `uiRegistry` (required for migration) +- Remove dependency on `ui/routes` (required for migration) +- Remove management section `uiExport` (required for migration) +- Simple API that is designed in keeping with new platform principles + - This includes being rendering-framework-agnostic... You should be able to build your management section UI however you'd like +- Clear separation of app/UI code and service code, even if both live within the same plugin +- Flexibility to potentially support alternate layouts in the future (see mockups in [reference section](#reference) below) + +# Basic example +This API is influenced heavily by the [application service mounting RFC](https://github.com/elastic/kibana/blob/master/rfcs/text/0004_application_service_mounting.md). The intent is to make the experience consistent with that service; the Management section is basically one big app with a bunch of registered "subapps". + +```ts +// my_plugin/public/plugin.ts + +export class MyPlugin { + setup(core, { management }) { + management.sections.register({ + id: 'my-section', + title: 'My Main Section', // display name + description: 'hello', // not used in current UI, but possibly in future + order: 10, + euiIconType: 'iconName', + }); + management.sections.registerLink('my-section', { + id: 'my-link', + title: 'My Link', // display name + description: 'hello', // not used in current UI, but possibly in future + order: 20, + async mount(context, params) { + const { renderApp } = await import('./my-section'); + return renderApp(context, params); + } + }); + } +} + +// my_plugin/public/my-section.tsx + +export function renderApp(context, { basePath, element }) { + ReactDOM.render( + // `basePath` would be `/app/management/my-section/my-link` + , + element + ); + + // return value must be a function that unmounts (just like Core Application Service) + return () => ReactDOM.unmountComponentAtNode(element); +} +``` + +# Detailed design + +```ts +interface ManagementSetup { + sections: SectionsService; +} + +interface SectionsService { + register: Register; + registerLink: RegisterLink; + getAvailable: () => Section[]; // filtered based on capabilities + get: (id: string) => Section | undefined; + getLink: (id: string) => Link | undefined; +} + +type Register = ({ + id: string; + title: string; + description: string; // not used in current UI, but possibly in future + order?: number; + euiIconType?: string; // takes precedence over `icon` property. + icon?: string; // URL to image file; fallback if no `euiIconType` +}) => Section | Error; + +type RegisterLink = (sectionId: string, { + id: string; + title: string; + description: string; // not used in current UI, but possibly in future + order?: number; + mount: ManagementSectionMount; +}) => Link | Error; + +type Unmount = () => Promise | void; + +type ManagementSectionMount = ( + context: AppMountContext, // provided by core.ApplicationService + params: MountParams, +) => Unmount | Promise; + +interface MountParams { + basePath: string; // base path for setting up your router + element: HTMLElement; // element the section should render into +} + +interface Section { + id: string; + title: string; + description: string; + baseBath: string; + links: Link[]; + order?: number; + euiIconType?: string; + icon?: string; + destroy: () => Promise | void; // de-registers and destroys all links +} + +interface Link { + id: string; + title: string; + description: string; + basePath: string; + sectionId: string; + order?: number; + destroy: () => Promise | void; // de-registers & calls unmount() +} +``` + +# Legacy service (what this would be replacing) + +Example of how this looks today: +```js +// myplugin/index +new Kibana.Plugin({ + uiExports: { + managementSections: ['myplugin/management'], + } +}); + +// myplugin/public/management +import { management } from 'ui/management'; + +// completely new section +const newSection = management.register('mypluginsection', { + name: 'mypluginsection', + order: 10, + display: 'My Plugin', + icon: 'iconName', +}); +newSection.register('mypluginlink', { + name: 'mypluginlink', + order: 10, + display: 'My sublink', + url: `#/management/myplugin`, +}); + +// new link in existing section +const kibanaSection = management.getSection('kibana'); +kibanaSection.register('mypluginlink', { + name: 'mypluginlink', + order: 10, + display: 'My sublink', + url: `#/management/myplugin`, +}); + +// use ui/routes to render component +import routes from 'ui/routes'; + +const renderReact = (elem) => { + render(, elem); +}; + +routes.when('management/myplugin', { + controller($scope, $http, kbnUrl) { + $scope.$on('$destroy', () => { + const elem = document.getElementById('usersReactRoot'); + if (elem) unmountComponentAtNode(elem); + }); + $scope.$$postDigest(() => { + const elem = document.getElementById('usersReactRoot'); + const changeUrl = (url) => { + kbnUrl.change(url); + $scope.$apply(); + }; + renderReact(elem, $http, changeUrl); + }); + }, +}); +``` +Current public contracts owned by the legacy service: +```js +// ui/management/index +interface API { + PAGE_TITLE_COMPONENT: string; // actually related to advanced settings? + PAGE_SUBTITLE_COMPONENT: string; // actually related to advanced settings? + PAGE_FOOTER_COMPONENT: string; // actually related to advanced settings? + SidebarNav: React.SFC; + registerSettingsComponent: ( + id: string, + component: string | React.SFC, + allowOverride: boolean + ) => void; + management: new ManagementSection(); + MANAGEMENT_BREADCRUMB: { + text: string; + href: string; + }; +} + +// ui/management/section +class ManagementSection { + get visibleItems, + addListener: (fn: function) => void, + register: (id: string, options: Options) => ManagementSection | Error, + deregister: (id: string) => void, + hasItem: (id: string) => boolean, + getSection: (id: string) => ManagementSection, + hide: () => void, + show: () => void, + disable: () => void, + enable: () => void, +} + +interface Options { + order: number | null; + display: string | null; // defaults to id + url: string | null; // defaults to '' + visible: boolean | null; // defaults to true + disabled: boolean | null; // defaults to false + tooltip: string | null; // defaults to '' + icon: string | null; // defaults to '' +} +``` + +# Notes + +- The hide/show/disable/enable options were dropped with the assumption that we will be working with uiCapabilities to determine this instead... so people shouldn't need to manage it manually as they can look up a pre-filtered list of sections. +- This was updated to add flexibility for custom (non-EUI) icons as outlined in #32661. Much like the Core Application Service, you either choose an EUI icon, or provide a URL to an icon. + +# Drawbacks + +- This removes the ability to infinitely nest sections within each other by making a distinction between a section header and a nav link. + - So far we didn't seem to be using this feature anyway, but would like feedback on any use cases for it. + +# Unresolved questions + +- Do individual links within a section need the ability to specify icons? Currently only the section headers have icons. +- Do we need a field for a description? Currently there is no place for it in the UI, but it was added based on the mocks from the [reference section](#reference). +- From a product perspective, what does the future of the Management section look like? There has been recent discussion around making Global vs Space-specific management areas (see links in [reference section](#reference)); if this is a feature we want in an `8.0` timeframe, we will probably need to resolve this question before moving forward. If this is something we plan for `8.x` and beyond, the current proposal remains mostly unaffected and we can add that as + +# Reference + +- Issues about Global vs Spaces-based management sections: https://github.com/elastic/kibana/issues/37285 https://github.com/elastic/kibana/issues/37283 +- Mockups related to above issues: https://marvelapp.com/52b8616/screen/57582729 + +# Alternatives + +An alternative design would be making everything React-specific and simply requiring consumers of the service to provide a React component to render when a route is hit, or giving them a react-router instance to work with. + +This would require slightly less work for folks using the service as it would eliminate the need for a `mount` function. However, it comes at the cost of forcing folks into a specific rendering framework, which ultimately provides less flexibility. + +# Adoption strategy + +Our strategy for implementing this should be to build the service entirely in the new platform in a `management` plugin, so that plugins can gradually cut over to the new service as they prepare to migrate to the new platform. + +One thing we would need to figure out is how to bridge the gap between the new plugin and the legacy `ui/management` service. Ideally we would find a way to integrate the two, such that the management nav could display items registered via both services. This is a strategy we'd need to work out in more detail as we got closer to implementation. + +# How we teach this + +The hope is that this will already feel familiar to Kibana application developers, as most will have already been exposed to the Core Application Service and how it handles mounting. + +A guide could also be added to the "Management" section of the Kibana docs (the legacy service is not even formally documented). From 206df1b672c02eba42879ec18200ff3679b6eef2 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Fri, 6 Sep 2019 11:17:31 -0600 Subject: [PATCH 2/8] Incorporate feedback. --- rfcs/text/0006_management_section_service.md | 85 ++++++++++++-------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index 41a033df36a06..e1f2f9883a7a7 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -37,14 +37,15 @@ This API is influenced heavily by the [application service mounting RFC](https:/ export class MyPlugin { setup(core, { management }) { - management.sections.register({ + // Registering a new link to a new section + const mySection = management.sections.register({ id: 'my-section', title: 'My Main Section', // display name description: 'hello', // not used in current UI, but possibly in future order: 10, euiIconType: 'iconName', }); - management.sections.registerLink('my-section', { + mySection.registerLink({ id: 'my-link', title: 'My Link', // display name description: 'hello', // not used in current UI, but possibly in future @@ -54,15 +55,26 @@ export class MyPlugin { return renderApp(context, params); } }); + + // Registering a new link to an existing section + const kibanaSection = management.sections.get('kibana'); + kibanaSection.registerLink({ id: 'my-kibana-section-link', ... }); + } + + start(core, { management }) { + // access all registered sections, filtered based on capabilities + management.sections.getAvailable(); + // automatically navigate to any section link by id + management.sections.navigateToSectionLink('my-kibana-section-link'); } } // my_plugin/public/my-section.tsx -export function renderApp(context, { basePath, element }) { +export function renderApp(context, { sectionBasePath, element }) { ReactDOM.render( - // `basePath` would be `/app/management/my-section/my-link` - , + // `sectionBasePath` would be `/app/management/my-section/my-link` + , element ); @@ -75,67 +87,75 @@ export function renderApp(context, { basePath, element }) { ```ts interface ManagementSetup { - sections: SectionsService; + sections: SectionsServiceSetup; +} + +interface ManagementStart { + sections: SectionsServiceStart; } -interface SectionsService { - register: Register; - registerLink: RegisterLink; +interface SectionsServiceSetup { + get: (sectionId: string) => Section; getAvailable: () => Section[]; // filtered based on capabilities - get: (id: string) => Section | undefined; - getLink: (id: string) => Link | undefined; + register: (RegisterParams) => Section | Error; } - -type Register = ({ + +interface SectionsServiceStart { + getAvailable: () => Array>; // filtered based on capabilities + // uses `core.application.navigateToApp` under the hood, automatically prepending the `path` for the link + navigateToSectionLink: (linkId: string, options?: { path?: string; state?: any }) => void; +} + +interface RegisterParams { id: string; title: string; description: string; // not used in current UI, but possibly in future order?: number; euiIconType?: string; // takes precedence over `icon` property. icon?: string; // URL to image file; fallback if no `euiIconType` -}) => Section | Error; +} -type RegisterLink = (sectionId: string, { +interface RegisterLinkParams { id: string; title: string; description: string; // not used in current UI, but possibly in future order?: number; mount: ManagementSectionMount; -}) => Link | Error; +} type Unmount = () => Promise | void; +interface MountParams { + sectionBasePath: string; // base path for setting up your router + element: HTMLElement; // element the section should render into +} + type ManagementSectionMount = ( context: AppMountContext, // provided by core.ApplicationService params: MountParams, ) => Unmount | Promise; - -interface MountParams { - basePath: string; // base path for setting up your router - element: HTMLElement; // element the section should render into + +interface Link { + id: string; + title: string; + description: string; + basePath: string; + sectionId: string; + order?: number; + destroy: () => Promise | void; // de-registers & calls unmount() } - + interface Section { id: string; title: string; description: string; - baseBath: string; links: Link[]; + registerLink: (RegisterLinkParams) => Link | Error; order?: number; euiIconType?: string; icon?: string; destroy: () => Promise | void; // de-registers and destroys all links } - -interface Link { - id: string; - title: string; - description: string; - basePath: string; - sectionId: string; - order?: number; - destroy: () => Promise | void; // de-registers & calls unmount() -} ``` # Legacy service (what this would be replacing) @@ -256,7 +276,6 @@ interface Options { # Unresolved questions -- Do individual links within a section need the ability to specify icons? Currently only the section headers have icons. - Do we need a field for a description? Currently there is no place for it in the UI, but it was added based on the mocks from the [reference section](#reference). - From a product perspective, what does the future of the Management section look like? There has been recent discussion around making Global vs Space-specific management areas (see links in [reference section](#reference)); if this is a feature we want in an `8.0` timeframe, we will probably need to resolve this question before moving forward. If this is something we plan for `8.x` and beyond, the current proposal remains mostly unaffected and we can add that as From 9af20283819bb3cb69dcc351763b7ff307db9e17 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Fri, 6 Sep 2019 14:11:41 -0600 Subject: [PATCH 3/8] Cleanup TS & add React example. --- rfcs/text/0006_management_section_service.md | 68 ++++++++++++++++---- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index e1f2f9883a7a7..d62638c7e6f17 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -82,6 +82,46 @@ export function renderApp(context, { sectionBasePath, element }) { return () => ReactDOM.unmountComponentAtNode(element); } ``` + +We can also create a utility in `kibana_react` to make it easy for folks to `mount` a React app: +```ts +// src/plugins/kibana_react/public/mount_with_react.tsx +import { KibanaContextProvider } from './context'; + +export const mountWithReact = ( + Component: React.ComponentType<{ basename: string }>, + context: AppMountContext, + params: ManagementSectionMountParams, +) => { + ReactDOM.render( + ( + + + + ), + params.element + ); + + return () => ReactDOM.unmountComponentAtNode(params.element); +} + +// my_plugin/public/plugin.ts +import { mountWithReact } from 'src/plugins/kibana_react/public'; + +export class MyPlugin { + setup(core, { management }) { + const kibanaSection = management.sections.get('kibana'); + kibanaSection.registerLink({ + id: 'my-other-kibana-section-link', + ..., + async mount(context, params) { + const { MySection } = await import('./components/my-section'); + return mountWithReact(MySection, context, params); + } + }); + } +} +``` # Detailed design @@ -97,7 +137,7 @@ interface ManagementStart { interface SectionsServiceSetup { get: (sectionId: string) => Section; getAvailable: () => Section[]; // filtered based on capabilities - register: (RegisterParams) => Section | Error; + register: Register; } interface SectionsServiceStart { @@ -106,33 +146,33 @@ interface SectionsServiceStart { navigateToSectionLink: (linkId: string, options?: { path?: string; state?: any }) => void; } -interface RegisterParams { - id: string; - title: string; - description: string; // not used in current UI, but possibly in future - order?: number; - euiIconType?: string; // takes precedence over `icon` property. - icon?: string; // URL to image file; fallback if no `euiIconType` -} +type Register = ( + id: string, + title: string, + description: string, // not used in current UI, but possibly in future + order?: number, + euiIconType?: string, // takes precedence over `icon` property. + icon?: string, // URL to image file; fallback if no `euiIconType` +) => Section | Error; -interface RegisterLinkParams { +type RegisterLink = ( id: string; title: string; description: string; // not used in current UI, but possibly in future order?: number; mount: ManagementSectionMount; -} +) => Link | Error; type Unmount = () => Promise | void; -interface MountParams { +interface ManagementSectionMountParams { sectionBasePath: string; // base path for setting up your router element: HTMLElement; // element the section should render into } type ManagementSectionMount = ( context: AppMountContext, // provided by core.ApplicationService - params: MountParams, + params: ManagementSectionMountParams, ) => Unmount | Promise; interface Link { @@ -150,7 +190,7 @@ interface Section { title: string; description: string; links: Link[]; - registerLink: (RegisterLinkParams) => Link | Error; + registerLink: RegisterLink; order?: number; euiIconType?: string; icon?: string; From 2fa673b41c4538d3deb606fd449489db5519e26c Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Sep 2019 15:52:07 -0600 Subject: [PATCH 4/8] Remove `| Error`. --- rfcs/text/0006_management_section_service.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index d62638c7e6f17..7b5b5726997ec 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -153,7 +153,7 @@ type Register = ( order?: number, euiIconType?: string, // takes precedence over `icon` property. icon?: string, // URL to image file; fallback if no `euiIconType` -) => Section | Error; +) => Section; type RegisterLink = ( id: string; @@ -161,7 +161,7 @@ type RegisterLink = ( description: string; // not used in current UI, but possibly in future order?: number; mount: ManagementSectionMount; -) => Link | Error; +) => Link; type Unmount = () => Promise | void; @@ -283,7 +283,7 @@ interface API { class ManagementSection { get visibleItems, addListener: (fn: function) => void, - register: (id: string, options: Options) => ManagementSection | Error, + register: (id: string, options: Options) => ManagementSection, deregister: (id: string) => void, hasItem: (id: string) => boolean, getSection: (id: string) => ManagementSection, From d26edd09df0c6d6ac0580be43d4bc14ba7c25640 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Sep 2019 15:52:58 -0600 Subject: [PATCH 5/8] Remove `destroy()`. --- rfcs/text/0006_management_section_service.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index 7b5b5726997ec..eca4ee2d485c6 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -182,7 +182,6 @@ interface Link { basePath: string; sectionId: string; order?: number; - destroy: () => Promise | void; // de-registers & calls unmount() } interface Section { @@ -194,7 +193,6 @@ interface Section { order?: number; euiIconType?: string; icon?: string; - destroy: () => Promise | void; // de-registers and destroys all links } ``` From 021dcc2bdc81bbd09afbaca5221f394082df36a4 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Sep 2019 15:59:59 -0600 Subject: [PATCH 6/8] Add missing links. --- rfcs/text/0006_management_section_service.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index eca4ee2d485c6..6a0a57699de3e 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -1,6 +1,6 @@ - Start Date: 2019-08-20 - RFC PR: TBD -- Kibana Issue: #43499 +- Kibana Issue: [#43499](https://github.com/elastic/kibana/issues/43499) # Summary Management is one of the four primary "domains" covered by @elastic/kibana-app-arch (along with Data, Embeddables, and Visualizations). There are two main purposes for this service: @@ -305,7 +305,7 @@ interface Options { # Notes - The hide/show/disable/enable options were dropped with the assumption that we will be working with uiCapabilities to determine this instead... so people shouldn't need to manage it manually as they can look up a pre-filtered list of sections. -- This was updated to add flexibility for custom (non-EUI) icons as outlined in #32661. Much like the Core Application Service, you either choose an EUI icon, or provide a URL to an icon. +- This was updated to add flexibility for custom (non-EUI) icons as outlined in [#32661](https://github.com/elastic/kibana/issues/32661). Much like the Core Application Service, you either choose an EUI icon, or provide a URL to an icon. # Drawbacks From 842a562e92cc7558f7edf2d3be8027da1f629d4a Mon Sep 17 00:00:00 2001 From: Matt Kime Date: Mon, 30 Sep 2019 16:39:09 -0500 Subject: [PATCH 7/8] remove description field --- rfcs/text/0006_management_section_service.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index 6a0a57699de3e..cf5f009d9b6cd 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -41,14 +41,12 @@ export class MyPlugin { const mySection = management.sections.register({ id: 'my-section', title: 'My Main Section', // display name - description: 'hello', // not used in current UI, but possibly in future order: 10, euiIconType: 'iconName', }); mySection.registerLink({ id: 'my-link', title: 'My Link', // display name - description: 'hello', // not used in current UI, but possibly in future order: 20, async mount(context, params) { const { renderApp } = await import('./my-section'); @@ -149,7 +147,6 @@ interface SectionsServiceStart { type Register = ( id: string, title: string, - description: string, // not used in current UI, but possibly in future order?: number, euiIconType?: string, // takes precedence over `icon` property. icon?: string, // URL to image file; fallback if no `euiIconType` @@ -158,7 +155,6 @@ type Register = ( type RegisterLink = ( id: string; title: string; - description: string; // not used in current UI, but possibly in future order?: number; mount: ManagementSectionMount; ) => Link; @@ -178,7 +174,6 @@ type ManagementSectionMount = ( interface Link { id: string; title: string; - description: string; basePath: string; sectionId: string; order?: number; @@ -187,7 +182,6 @@ interface Link { interface Section { id: string; title: string; - description: string; links: Link[]; registerLink: RegisterLink; order?: number; @@ -312,11 +306,6 @@ interface Options { - This removes the ability to infinitely nest sections within each other by making a distinction between a section header and a nav link. - So far we didn't seem to be using this feature anyway, but would like feedback on any use cases for it. -# Unresolved questions - -- Do we need a field for a description? Currently there is no place for it in the UI, but it was added based on the mocks from the [reference section](#reference). -- From a product perspective, what does the future of the Management section look like? There has been recent discussion around making Global vs Space-specific management areas (see links in [reference section](#reference)); if this is a feature we want in an `8.0` timeframe, we will probably need to resolve this question before moving forward. If this is something we plan for `8.x` and beyond, the current proposal remains mostly unaffected and we can add that as - # Reference - Issues about Global vs Spaces-based management sections: https://github.com/elastic/kibana/issues/37285 https://github.com/elastic/kibana/issues/37283 From 157b5433db2b17da45449f31bbba0621f009ce57 Mon Sep 17 00:00:00 2001 From: Matt Kime Date: Mon, 30 Sep 2019 21:13:55 -0500 Subject: [PATCH 8/8] address feedback, clean up how things are named --- rfcs/text/0006_management_section_service.md | 46 ++++++++++---------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index cf5f009d9b6cd..bcb74b1bcd8da 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -37,16 +37,16 @@ This API is influenced heavily by the [application service mounting RFC](https:/ export class MyPlugin { setup(core, { management }) { - // Registering a new link to a new section + // Registering a new app to a new section const mySection = management.sections.register({ id: 'my-section', title: 'My Main Section', // display name order: 10, euiIconType: 'iconName', }); - mySection.registerLink({ - id: 'my-link', - title: 'My Link', // display name + mySection.registerApp({ + id: 'my-management-app', + title: 'My Management App', // display name order: 20, async mount(context, params) { const { renderApp } = await import('./my-section'); @@ -54,16 +54,17 @@ export class MyPlugin { } }); - // Registering a new link to an existing section + // Registering a new app to an existing section const kibanaSection = management.sections.get('kibana'); - kibanaSection.registerLink({ id: 'my-kibana-section-link', ... }); + kibanaSection.registerApp({ id: 'my-kibana-management-app', ... }); } start(core, { management }) { // access all registered sections, filtered based on capabilities - management.sections.getAvailable(); - // automatically navigate to any section link by id - management.sections.navigateToSectionLink('my-kibana-section-link'); + const sections = management.sections.getAvailable(); + sections.forEach(section => console.log(`${section.id} - ${section.title}`)); + // automatically navigate to any app by id + management.sections.navigateToApp('my-kibana-management-app'); } } @@ -71,7 +72,7 @@ export class MyPlugin { export function renderApp(context, { sectionBasePath, element }) { ReactDOM.render( - // `sectionBasePath` would be `/app/management/my-section/my-link` + // `sectionBasePath` would be `/app/management/my-section/my-management-app` , element ); @@ -109,12 +110,13 @@ import { mountWithReact } from 'src/plugins/kibana_react/public'; export class MyPlugin { setup(core, { management }) { const kibanaSection = management.sections.get('kibana'); - kibanaSection.registerLink({ - id: 'my-other-kibana-section-link', + kibanaSection.registerApp({ + id: 'my-other-kibana-management-app', ..., async mount(context, params) { const { MySection } = await import('./components/my-section'); - return mountWithReact(MySection, context, params); + const unmountCallback = mountWithReact(MySection, context, params); + return () => unmountCallback(); } }); } @@ -135,16 +137,16 @@ interface ManagementStart { interface SectionsServiceSetup { get: (sectionId: string) => Section; getAvailable: () => Section[]; // filtered based on capabilities - register: Register; + register: RegisterSection; } interface SectionsServiceStart { - getAvailable: () => Array>; // filtered based on capabilities + getAvailable: () => Array>; // filtered based on capabilities // uses `core.application.navigateToApp` under the hood, automatically prepending the `path` for the link - navigateToSectionLink: (linkId: string, options?: { path?: string; state?: any }) => void; + navigateToApp: (appId: string, options?: { path?: string; state?: any }) => void; } -type Register = ( +type RegisterSection = ( id: string, title: string, order?: number, @@ -152,12 +154,12 @@ type Register = ( icon?: string, // URL to image file; fallback if no `euiIconType` ) => Section; -type RegisterLink = ( +type RegisterManagementApp = ( id: string; title: string; order?: number; mount: ManagementSectionMount; -) => Link; +) => ManagementApp; type Unmount = () => Promise | void; @@ -171,7 +173,7 @@ type ManagementSectionMount = ( params: ManagementSectionMountParams, ) => Unmount | Promise; -interface Link { +interface ManagementApp { id: string; title: string; basePath: string; @@ -182,8 +184,8 @@ interface Link { interface Section { id: string; title: string; - links: Link[]; - registerLink: RegisterLink; + apps: ManagementApp[]; + registerApp: RegisterManagementApp; order?: number; euiIconType?: string; icon?: string;