diff --git a/examples/content_management_examples/kibana.jsonc b/examples/content_management_examples/kibana.jsonc index 0d4a9b39dc0ef..caa71594d7760 100644 --- a/examples/content_management_examples/kibana.jsonc +++ b/examples/content_management_examples/kibana.jsonc @@ -13,6 +13,9 @@ "kibanaReact", "savedObjectsTaggingOss" ], + "optionalPlugins": [ + "spaces" + ], "requiredBundles": ["savedObjectsFinder"] } } diff --git a/examples/content_management_examples/public/examples/index.tsx b/examples/content_management_examples/public/examples/index.tsx index 3b92da0ba025e..bd83f3f93417e 100644 --- a/examples/content_management_examples/public/examples/index.tsx +++ b/examples/content_management_examples/public/examples/index.tsx @@ -20,7 +20,7 @@ import { FinderApp } from './finder'; export const renderApp = ( core: CoreStart, - { contentManagement, savedObjectsTaggingOss }: StartDeps, + { contentManagement, savedObjectsTaggingOss, spaces }: StartDeps, { element, history }: AppMountParameters ) => { ReactDOM.render( @@ -69,6 +69,7 @@ export const renderApp = ( contentClient={contentManagement.client} core={core} savedObjectsTagging={savedObjectsTaggingOss} + spaces={spaces} /> diff --git a/examples/content_management_examples/public/examples/msearch/msearch_app.tsx b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx index 2bb4ab1bf6fb9..34ea97d92f1b1 100644 --- a/examples/content_management_examples/public/examples/msearch/msearch_app.tsx +++ b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { FC, useMemo } from 'react'; +import { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public'; import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table'; import type { CoreStart } from '@kbn/core/public'; @@ -15,23 +16,37 @@ import { FormattedRelative, I18nProvider } from '@kbn/i18n-react'; import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { MSearchTable } from './msearch_table'; +const getEmptyFunctionComponent: FC = ({ children }) => <>{children}; + export const MSearchApp = (props: { contentClient: ContentClient; core: CoreStart; savedObjectsTagging: SavedObjectTaggingOssPluginStart; + spaces?: SpacesPluginStart; }) => { + const SpacesContextWrapper = useMemo( + () => + props.spaces + ? props.spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent, + [props.spaces] + ); + return ( - - - - - - - + + + + + + + + + ); }; diff --git a/examples/content_management_examples/public/examples/msearch/msearch_table.tsx b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx index 84c5d58b86558..05b3e5f26f130 100644 --- a/examples/content_management_examples/public/examples/msearch/msearch_table.tsx +++ b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx @@ -13,6 +13,11 @@ import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-br const LISTING_LIMIT = 1000; +// TODO Currently each consumer of TableListView needs to pass an itemIsShareable handler to the TableListView. +// Ideally, the contentClient would use the ISavedObjectTypeRegistry.isShareable method to tell us if the saved +// object type is shareable. For this demo we are hard-coding the shareable object types. +const shareableSavedObjects = ['index-pattern']; + export const MSearchTable = () => { const contentClient = useContentClient(); @@ -59,6 +64,7 @@ export const MSearchTable = () => { entityNamePlural={`ContentItems`} title={`MSearch Demo`} urlStateEnabled={false} + itemIsShareable={({ type }) => shareableSavedObjects.includes(type)} emptyPrompt={<>No data found. Try to install some sample data first.} onClickTitle={(item) => { alert(`Clicked item ${item.attributes.title} (${item.id})`); diff --git a/examples/content_management_examples/public/types.ts b/examples/content_management_examples/public/types.ts index 747bfd961c434..3dca87717e791 100644 --- a/examples/content_management_examples/public/types.ts +++ b/examples/content_management_examples/public/types.ts @@ -10,6 +10,7 @@ import { ContentManagementPublicSetup, ContentManagementPublicStart, } from '@kbn/content-management-plugin/public'; +import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; @@ -21,4 +22,5 @@ export interface SetupDeps { export interface StartDeps { contentManagement: ContentManagementPublicStart; savedObjectsTaggingOss: SavedObjectTaggingOssPluginStart; + spaces?: SpacesPluginStart; } diff --git a/examples/content_management_examples/tsconfig.json b/examples/content_management_examples/tsconfig.json index 33d00ec0ee82d..154ab455a88b8 100644 --- a/examples/content_management_examples/tsconfig.json +++ b/examples/content_management_examples/tsconfig.json @@ -30,5 +30,6 @@ "@kbn/content-management-table-list-view", "@kbn/shared-ux-router", "@kbn/saved-objects-finder-plugin", + "@kbn/spaces-plugin", ] } diff --git a/packages/content-management/table_list_view/src/table_list_view.tsx b/packages/content-management/table_list_view/src/table_list_view.tsx index f5d1a9bc596b9..0b773e8caa177 100644 --- a/packages/content-management/table_list_view/src/table_list_view.tsx +++ b/packages/content-management/table_list_view/src/table_list_view.tsx @@ -37,6 +37,7 @@ export type TableListViewProps & { title: string; description?: string; @@ -74,6 +75,7 @@ export const TableListView = ({ additionalRightSideActions, withoutPageTemplateWrapper, itemIsEditable, + itemIsShareable, }: TableListViewProps) => { const PageTemplate = withoutPageTemplateWrapper ? (React.Fragment as unknown as typeof KibanaPageTemplate) @@ -120,6 +122,7 @@ export const TableListView = ({ contentEditor={contentEditor} titleColumnName={titleColumnName} itemIsEditable={itemIsEditable} + itemIsShareable={itemIsShareable} withoutPageTemplateWrapper={withoutPageTemplateWrapper} onFetchSuccess={onFetchSuccess} setPageDataTestSubject={setPageDataTestSubject} diff --git a/packages/content-management/table_list_view_table/src/components/spaces_list.tsx b/packages/content-management/table_list_view_table/src/components/spaces_list.tsx new file mode 100644 index 0000000000000..99ab75e77ee8e --- /dev/null +++ b/packages/content-management/table_list_view_table/src/components/spaces_list.tsx @@ -0,0 +1,68 @@ +/* + * 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 React, { FC, useState } from 'react'; +import type { SpacesPluginStart, ShareToSpaceFlyoutProps } from '@kbn/spaces-plugin/public'; + +interface Props { + spacesApi: SpacesPluginStart; + canShareToSpaces?: boolean; + spaceIds: string[]; + type: string; + noun: string; + id: string; + title: string; + refresh(): void; +} + +export const SpacesList: FC = ({ + spacesApi, + canShareToSpaces, + type, + noun, + spaceIds, + id, + title, + refresh, +}) => { + const [showFlyout, setShowFlyout] = useState(false); + + function onClose() { + setShowFlyout(false); + } + + const LazySpaceList = spacesApi.ui.components.getSpaceList; + const LazyShareToSpaceFlyout = spacesApi.ui.components.getShareToSpaceFlyout; + + const shareToSpaceFlyoutProps: ShareToSpaceFlyoutProps = { + savedObjectTarget: { + type, + namespaces: spaceIds, + id, + title, + noun, + }, + onUpdate: refresh, + onClose, + }; + + const clickProperties = canShareToSpaces + ? { cursorStyle: 'pointer', listOnClick: () => setShowFlyout(true) } + : { cursorStyle: 'not-allowed' }; + return ( + <> + + {showFlyout && } + + ); +}; diff --git a/packages/content-management/table_list_view_table/src/services.tsx b/packages/content-management/table_list_view_table/src/services.tsx index 5ee9f9d4b32bd..b319fbfc21a6d 100644 --- a/packages/content-management/table_list_view_table/src/services.tsx +++ b/packages/content-management/table_list_view_table/src/services.tsx @@ -9,6 +9,7 @@ import React, { FC, useContext, useMemo, useCallback } from 'react'; import type { Observable } from 'rxjs'; import type { FormattedRelative } from '@kbn/i18n-react'; +import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { MountPoint, OverlayRef } from '@kbn/core-mount-utils-browser'; import type { OverlayFlyoutOpenOptions } from '@kbn/core-overlays-browser'; import { RedirectAppLinksKibanaProvider } from '@kbn/shared-ux-link-redirect-app'; @@ -43,6 +44,7 @@ export interface TagListProps { */ export interface Services { canEditAdvancedSettings: boolean; + canShareToSpaces?: boolean; getListingLimitSettingsUrl: () => string; notifyError: NotifyFn; currentAppId$: Observable; @@ -56,6 +58,7 @@ export interface Services { /** Handler to retrieve the list of available tags */ getTagList: () => Tag[]; TagList: FC; + spacesApi?: SpacesApi; /** Predicate function to indicate if some of the saved object references are tags */ itemHasTags: (references: SavedObjectsReference[]) => boolean; /** Handler to return the url to navigate to the kibana tags management */ @@ -155,6 +158,11 @@ export interface TableListViewKibanaDependencies { getTagIdsFromReferences: (references: SavedObjectsReference[]) => string[]; }; }; + /** + * The public API from the Spaces plugin. Provide the `spacesApi` to show the Spaces + * column in the table. + */ + spacesApi?: SpacesApi; /** The component from the @kbn/i18n-react package */ FormattedRelative: typeof FormattedRelative; } @@ -166,7 +174,7 @@ export const TableListViewKibanaProvider: FC = children, ...services }) => { - const { core, toMountPoint, savedObjectsTagging, FormattedRelative } = services; + const { core, toMountPoint, savedObjectsTagging, spacesApi, FormattedRelative } = services; const searchQueryParser = useMemo(() => { if (savedObjectsTagging) { @@ -228,6 +236,9 @@ export const TableListViewKibanaProvider: FC = > core.application.getUrlForApp('management', { path: `/kibana/settings?query=savedObjects:listingLimit`, @@ -245,6 +256,7 @@ export const TableListViewKibanaProvider: FC = itemHasTags={itemHasTags} getTagIdsFromReferences={getTagIdsFromReferences} getTagManagementUrl={() => core.http.basePath.prepend(TAG_MANAGEMENT_APP_URL)} + spacesApi={spacesApi} > {children} diff --git a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx index 277748b44b883..71958e691c893 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx @@ -8,6 +8,7 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import React, { useEffect } from 'react'; import queryString from 'query-string'; import moment, { Moment } from 'moment'; @@ -141,6 +142,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: 'item-1', + namespaces: ['default'], type: 'dashboard', updatedAt: '2020-01-01T00:00:00Z', attributes: { @@ -188,6 +190,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -198,6 +201,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], // This is the latest updated and should come first in the table updatedAt: yesterday.toISOString(), type: 'dashboard', @@ -330,6 +334,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [...Array(totalItems)].map((_, i) => ({ id: `item${i}`, + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -424,13 +429,18 @@ describe('TableListView', () => { }); }); - describe('column sorting', () => { - const setupColumnSorting = registerTestBed( + describe('spaces column', () => { + const spacesApi = spacesPluginMock.createStartContract(); + jest + .spyOn(spacesApi.ui.components, 'getSpaceList') + .mockImplementation(({ namespaces }) => <>{namespaces.toString()}); + const setupSpacesColumn = registerTestBed( WithServices(TableListViewTable, { + spacesApi, TagList: getTagList({ references: [] }), }), { - defaultProps: { ...requiredProps }, + defaultProps: { ...requiredProps, itemIsShareable: ({ id }) => id !== '789' }, memoryRouter: { wrapComponent: true }, } ); @@ -438,6 +448,71 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default', 'engineering', 'product-management'], + updatedAt: twoDaysAgo.toISOString(), // first asc, last desc + type: 'dashboard', + attributes: { + title: 'z-foo', // first desc, last asc + }, + references: [{ id: 'id-tag-1', name: 'tag-1', type: 'tag' }], + }, + { + id: '456', + namespaces: ['*'], + updatedAt: yesterday.toISOString(), // first desc, last asc + type: 'dashboard', + attributes: { + title: 'a-foo', // first asc, last desc + }, + references: [], + }, + { + id: '789', + namespaces: ['default'], + updatedAt: yesterday.toISOString(), + type: 'lens', + attributes: { + title: 'not-shareable', + }, + references: [], + }, + ]; + + test('should display on shareable items when spaces is provided', async () => { + let testBed: TestBed; + + await act(async () => { + testBed = await setupSpacesColumn({ + findItems: jest.fn().mockResolvedValue({ total: hits.length, hits }), + }); + }); + + const { component, table } = testBed!; + component.update(); + + const { tableCellsValues } = table.getMetaData('itemsInMemTable'); + + expect(tableCellsValues).toEqual([ + ['a-foo', '*', yesterdayToString], + ['not-shareable', '', yesterdayToString], + ['z-foo', 'default,engineering,product-management', twoDaysAgoToString], + ]); + }); + }); + + describe('column sorting', () => { + const tagList = WithServices(TableListViewTable, { + TagList: getTagList({ references: [] }), + }); + const setupColumnSorting = registerTestBed(tagList, { + defaultProps: { ...requiredProps }, + memoryRouter: { wrapComponent: true }, + }); + + const hits: UserContentCommonSchema[] = [ + { + id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), // first asc, last desc type: 'dashboard', attributes: { @@ -447,6 +522,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: yesterday.toISOString(), // first desc, last asc type: 'dashboard', attributes: { @@ -639,6 +715,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), attributes: { title: 'Item 1', @@ -649,6 +726,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 2)).toISOString(), attributes: { title: 'Item 2', @@ -697,6 +775,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), type: 'dashboard', attributes: { @@ -710,6 +789,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 2)).toISOString(), type: 'dashboard', attributes: { @@ -890,6 +970,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: 'item-1', + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -899,6 +980,7 @@ describe('TableListView', () => { }, { id: 'item-2', + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -1075,6 +1157,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: yesterday.toISOString(), type: 'dashboard', attributes: { @@ -1085,6 +1168,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -1344,6 +1428,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -1354,6 +1439,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: yesterday.toISOString(), type: 'dashboard', attributes: { @@ -1467,6 +1553,7 @@ describe('TableList', () => { const originalHits: UserContentCommonSchema[] = [ { id: `item`, + namespaces: ['default'], type: 'dashboard', updatedAt: 'original timestamp', attributes: { @@ -1491,6 +1578,7 @@ describe('TableList', () => { const hits: UserContentCommonSchema[] = [ { id: `item`, + namespaces: ['default'], type: 'dashboard', updatedAt: 'updated timestamp', attributes: { diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index f7de8935ea949..ab13843021563 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx @@ -43,6 +43,7 @@ import type { SortColumnField } from './components'; import { useTags } from './use_tags'; import { useInRouterContext, useUrlState } from './use_url_state'; import { RowActions, TableItemsRowActions } from './types'; +import { SpacesList } from './components/spaces_list'; interface ContentEditorConfig extends Pick { @@ -96,6 +97,11 @@ export interface TableListViewTableProps< */ itemIsEditable?(item: T): boolean; + /** + * Handler to set whether item can be shared into other Spaces. + */ + itemIsShareable?(item: T): boolean; + /** * Name for the column containing the "title" value. */ @@ -147,6 +153,7 @@ export interface UserContentCommonSchema { id: string; updatedAt: string; managed?: boolean; + namespaces?: string[]; references: SavedObjectsReference[]; type: string; attributes: { @@ -246,6 +253,10 @@ const tableColumnMetadata = { field: 'attributes.title', name: 'Name, description, tags', }, + spaces: { + field: 'namespaces', + name: 'Spaces', + }, updatedAt: { field: 'updatedAt', name: 'Last updated', @@ -268,6 +279,7 @@ function TableListViewTableComp({ createItem, editItem, itemIsEditable, + itemIsShareable, deleteItems, getDetailViewLink, onClickTitle, @@ -310,12 +322,14 @@ function TableListViewTableComp({ const { canEditAdvancedSettings, + canShareToSpaces, getListingLimitSettingsUrl, getTagIdsFromReferences, searchQueryParser, notifyError, DateFormatterComp, getTagList, + spacesApi, } = useServices(); const openContentEditor = useOpenContentEditor(); @@ -521,6 +535,31 @@ function TableListViewTableComp({ columns.push(customTableColumn); } + if (spacesApi && !spacesApi.hasOnlyDefaultSpace) { + columns.push({ + field: tableColumnMetadata.spaces.field, + name: i18n.translate('contentManagement.tableList.spacesColumnTitle', { + defaultMessage: 'Spaces', + }), + width: '20%', + render: (field: string, record: T) => + !itemIsShareable || !itemIsShareable(record) ? ( + <> + ) : ( + fetchItems()} + /> + ), + }); + } + if (hasUpdatedAtMetadata) { columns.push({ field: tableColumnMetadata.updatedAt.field, @@ -603,6 +642,7 @@ function TableListViewTableComp({ }, [ titleColumnName, customTableColumn, + spacesApi, hasUpdatedAtMetadata, editItem, contentEditor.enabled, @@ -612,6 +652,9 @@ function TableListViewTableComp({ searchQuery.text, addOrRemoveExcludeTagFilter, addOrRemoveIncludeTagFilter, + itemIsShareable, + canShareToSpaces, + fetchItems, DateFormatterComp, isEditable, inspectItem, diff --git a/packages/content-management/table_list_view_table/tsconfig.json b/packages/content-management/table_list_view_table/tsconfig.json index 16a8a6b1a6de1..5649881e54fa9 100644 --- a/packages/content-management/table_list_view_table/tsconfig.json +++ b/packages/content-management/table_list_view_table/tsconfig.json @@ -25,6 +25,7 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/shared-ux-link-redirect-app", "@kbn/test-jest-helpers", + "@kbn/spaces-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.test.tsx index 417795dee9334..91a83998911fe 100644 --- a/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.test.tsx @@ -40,6 +40,7 @@ const defaultCapabilities = { visualize: { save: true }, maps: { save: true }, navLinks: {}, + savedObjectsManagement: {}, }; Object.defineProperty(pluginServices.getServices().application, 'capabilities', { diff --git a/src/plugins/dashboard/public/services/application/application.stub.ts b/src/plugins/dashboard/public/services/application/application.stub.ts index 9717e75ea7bc8..b2e8111b6cec6 100644 --- a/src/plugins/dashboard/public/services/application/application.stub.ts +++ b/src/plugins/dashboard/public/services/application/application.stub.ts @@ -25,6 +25,7 @@ export const applicationServiceFactory: ApplicationServiceFactory = () => { maps: pluginMock.capabilities.maps, navLinks: pluginMock.capabilities.navLinks, visualize: pluginMock.capabilities.visualize, + savedObjectsManagement: pluginMock.capabilities.savedObjectsManagement, }, }; }; diff --git a/src/plugins/dashboard/public/services/application/application_service.ts b/src/plugins/dashboard/public/services/application/application_service.ts index adc6d23f48461..2518d68b723c6 100644 --- a/src/plugins/dashboard/public/services/application/application_service.ts +++ b/src/plugins/dashboard/public/services/application/application_service.ts @@ -22,7 +22,7 @@ export const applicationServiceFactory: ApplicationServiceFactory = ({ coreStart navigateToApp, navigateToUrl, getUrlForApp, - capabilities: { advancedSettings, maps, navLinks, visualize }, + capabilities: { advancedSettings, maps, navLinks, visualize, savedObjectsManagement }, }, } = coreStart; @@ -36,6 +36,7 @@ export const applicationServiceFactory: ApplicationServiceFactory = ({ coreStart maps, navLinks, visualize, + savedObjectsManagement, }, }; }; diff --git a/src/plugins/dashboard/public/services/application/types.ts b/src/plugins/dashboard/public/services/application/types.ts index 66ef934e44bb3..97b21dddbd4b3 100644 --- a/src/plugins/dashboard/public/services/application/types.ts +++ b/src/plugins/dashboard/public/services/application/types.ts @@ -18,5 +18,6 @@ export interface DashboardApplicationService { maps: CoreStart['application']['capabilities']['maps']; // only used in `add_to_library_action` navLinks: CoreStart['application']['capabilities']['navLinks']; visualize: CoreStart['application']['capabilities']['visualize']; // only used in `add_to_library_action` + savedObjectsManagement: CoreStart['application']['capabilities']['savedObjectsManagement']; }; }