From 9355fd6ef703e6eac80a03528e57c2cfe892c5d5 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Wed, 1 Nov 2023 16:16:50 -0400 Subject: [PATCH 01/21] Add Spaces column to TableListViewTable component See MSearch developer example under Content Management --- .../content_management_examples/kibana.jsonc | 3 + .../public/examples/index.tsx | 3 +- .../public/examples/msearch/msearch_app.tsx | 41 +++++++---- .../public/types.ts | 2 + .../content_management_examples/tsconfig.json | 1 + .../src/components/spaces_list.tsx | 71 +++++++++++++++++++ .../table_list_view_table/src/services.tsx | 6 +- .../src/table_list_view_table.tsx | 34 +++++++++ 8 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 packages/content-management/table_list_view_table/src/components/spaces_list.tsx 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/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_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..61423df5dd27f --- /dev/null +++ b/packages/content-management/table_list_view_table/src/components/spaces_list.tsx @@ -0,0 +1,71 @@ +/* + * 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 { Capabilities } from '@kbn/core/public'; +import type { SpacesPluginStart, ShareToSpaceFlyoutProps } from '@kbn/spaces-plugin/public'; + +interface Props { + spacesApi: SpacesPluginStart; + capabilities: Capabilities | undefined; + spaceIds: string[]; + type: string; + noun: string; + id: string; + title: string; + refresh(): void; +} + +export const SpacesList: FC = ({ + spacesApi, + capabilities, + 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 canAssignSpaces = !capabilities || !!capabilities.savedObjectsManagement.shareIntoSpace; + const clickProperties = canAssignSpaces + ? { 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..19a36c258446e 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'; @@ -56,6 +57,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 +157,7 @@ export interface TableListViewKibanaDependencies { getTagIdsFromReferences: (references: SavedObjectsReference[]) => string[]; }; }; + spacesApi?: SpacesApi; /** The component from the @kbn/i18n-react package */ FormattedRelative: typeof FormattedRelative; } @@ -166,7 +169,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) { @@ -245,6 +248,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_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index ccf6ef791d8d4..2e13c201dc649 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 { @@ -147,6 +148,7 @@ export interface UserContentCommonSchema { id: string; updatedAt: string; managed?: boolean; + namespaces: string[]; references: SavedObjectsReference[]; type: string; attributes: { @@ -246,6 +248,10 @@ const tableColumnMetadata = { field: 'attributes.title', name: 'Name, description, tags', }, + spaces: { + field: 'namespaces', + name: 'Spaces', + }, updatedAt: { field: 'updatedAt', name: 'Last updated', @@ -322,6 +328,7 @@ function TableListViewTableComp({ notifyError, DateFormatterComp, getTagList, + spacesApi, } = useServices(); const openContentEditor = useOpenContentEditor(); @@ -527,6 +534,31 @@ function TableListViewTableComp({ columns.push(customTableColumn); } + if (spacesApi) { + columns.push({ + field: tableColumnMetadata.spaces.field, + name: i18n.translate('contentManagement.tableList.spacesColumnTitle', { + defaultMessage: 'Spaces', + }), + width: '20%', + render: ( + field: string, + record: { id: string; attributes: { title: string }; type: string; namespaces: string[] } + ) => ( + fetchItems()} + /> + ), + }); + } + if (hasUpdatedAtMetadata) { columns.push({ field: tableColumnMetadata.updatedAt.field, @@ -609,6 +641,7 @@ function TableListViewTableComp({ }, [ titleColumnName, customTableColumn, + spacesApi, hasUpdatedAtMetadata, editItem, contentEditor.enabled, @@ -618,6 +651,7 @@ function TableListViewTableComp({ searchQuery.text, addOrRemoveExcludeTagFilter, addOrRemoveIncludeTagFilter, + fetchItems, DateFormatterComp, isEditable, inspectItem, From 88741fbb49e627a974eb5205bcf63916d31b5487 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 1 Nov 2023 20:38:16 +0000 Subject: [PATCH 02/21] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/content-management/table_list_view_table/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/content-management/table_list_view_table/tsconfig.json b/packages/content-management/table_list_view_table/tsconfig.json index 16a8a6b1a6de1..ee89eb32fde35 100644 --- a/packages/content-management/table_list_view_table/tsconfig.json +++ b/packages/content-management/table_list_view_table/tsconfig.json @@ -25,6 +25,8 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/shared-ux-link-redirect-app", "@kbn/test-jest-helpers", + "@kbn/core", + "@kbn/spaces-plugin", ], "exclude": [ "target/**/*", From f9f0940e0f77102aee12b3f7c1e8775ee2bcc8a1 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Wed, 1 Nov 2023 16:43:36 -0400 Subject: [PATCH 03/21] Add Spaces column to Annotations, Graph, Maps, Visualize lists --- .../event_annotation_service/service.tsx | 1 + .../event_annotation_listing/kibana.jsonc | 1 + .../public/get_table_list.tsx | 3 + .../event_annotation_listing/public/plugin.ts | 3 + .../event_annotation_listing/tsconfig.json | 3 +- .../public/utils/saved_visualize_utils.ts | 4 + .../components/visualize_listing.tsx | 1 + .../public/visualize_app/index.tsx | 38 ++++++---- x-pack/plugins/graph/public/application.tsx | 3 +- x-pack/plugins/lens/public/vis_type_alias.ts | 3 +- .../maps/public/maps_vis_type_alias.ts | 3 +- x-pack/plugins/maps/public/plugin.ts | 9 ++- x-pack/plugins/maps/public/render_app.tsx | 74 +++++++++++-------- 13 files changed, 96 insertions(+), 50 deletions(-) diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 4003ea524b291..832a1c3db018e 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -82,6 +82,7 @@ export function getEventAnnotationService( return { id: savedObject.id, references: savedObject.references, + namespaces: savedObject.namespaces, type: savedObject.type, updatedAt: savedObject.updatedAt ? savedObject.updatedAt : '', attributes: { diff --git a/src/plugins/event_annotation_listing/kibana.jsonc b/src/plugins/event_annotation_listing/kibana.jsonc index e8f51a817b977..0b3c374158932 100644 --- a/src/plugins/event_annotation_listing/kibana.jsonc +++ b/src/plugins/event_annotation_listing/kibana.jsonc @@ -21,6 +21,7 @@ "optionalPlugins": [ "savedObjectsTagging", "lens", + "spaces" ], "requiredBundles": [ "kibanaReact", diff --git a/src/plugins/event_annotation_listing/public/get_table_list.tsx b/src/plugins/event_annotation_listing/public/get_table_list.tsx index 0bcffc803cc94..bf7cba6e239bc 100644 --- a/src/plugins/event_annotation_listing/public/get_table_list.tsx +++ b/src/plugins/event_annotation_listing/public/get_table_list.tsx @@ -9,6 +9,7 @@ import React, { FC } from 'react'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { FormattedRelative } from '@kbn/i18n-react'; +import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table'; import { type TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; @@ -24,6 +25,7 @@ import { EventAnnotationGroupTableList } from './components/table_list'; export interface EventAnnotationListingPageServices { core: CoreStart; savedObjectsTagging: SavedObjectsTaggingApi; + spaces?: SpacesPluginStart; eventAnnotationService: EventAnnotationServiceType; PresentationUtilContextProvider: FC; dataViews: DataView[]; @@ -45,6 +47,7 @@ export const getTableList = ( toMountPoint, savedObjectsTagging: services.savedObjectsTagging, FormattedRelative, + spacesApi: services.spaces, }} > ): Visua return { id: savedObject.id as string, updatedAt: savedObject.updatedAt as string, + namespaces: savedObject.namespaces as string[], references: savedObject.references as Array<{ id: string; type: string; name: string }>, type: savedObject.savedObjectType as string, editUrl: savedObject.editUrl as string, diff --git a/src/plugins/visualizations/public/visualize_app/index.tsx b/src/plugins/visualizations/public/visualize_app/index.tsx index ed50345208ef0..b6205e177d517 100644 --- a/src/plugins/visualizations/public/visualize_app/index.tsx +++ b/src/plugins/visualizations/public/visualize_app/index.tsx @@ -17,11 +17,14 @@ import { toMountPoint, } from '@kbn/kibana-react-plugin/public'; import { FormattedRelative } from '@kbn/i18n-react'; +import { SpacesContextProps } from '@kbn/spaces-plugin/public'; import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table'; import { VisualizeApp } from './app'; import { VisualizeServices } from './types'; import { addHelpMenuToAppChrome, addBadgeToAppChrome } from './utils'; +const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; + export const renderApp = ( { element, onAppLeave }: AppMountParameters, services: VisualizeServices @@ -33,24 +36,31 @@ export const renderApp = ( addBadgeToAppChrome(services.chrome); } + const SpacesContextWrapper = services.spaces + ? services.spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; + const app = ( - - - - - - - + + + + + + + + + diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index 89ba72b203413..238293ef43452 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -77,7 +77,7 @@ export interface GraphDependencies { export type GraphServices = Omit; export const renderApp = ({ history, element, ...deps }: GraphDependencies) => { - const { chrome, capabilities, core } = deps; + const { chrome, capabilities, core, spaces } = deps; const { theme$ } = core.theme; if (!capabilities.graph.save) { @@ -122,6 +122,7 @@ export const renderApp = ({ history, element, ...deps }: GraphDependencies) => { {...{ core, toMountPoint, + spacesApi: spaces, FormattedRelative, }} > diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts index 2fc493df38edc..cffe764a82d1e 100644 --- a/x-pack/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -34,12 +34,13 @@ export const getLensAliasConfig = (): VisTypeAlias => ({ clientOptions: { update: { overwrite: true } }, client: getLensClient, toListItem(savedObject) { - const { id, type, updatedAt, attributes } = savedObject; + const { id, type, updatedAt, attributes, namespaces } = savedObject; const { title, description } = attributes as { title: string; description?: string }; return { id, title, description, + namespaces, updatedAt, editUrl: getEditPath(id), editApp: 'lens', diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.ts b/x-pack/plugins/maps/public/maps_vis_type_alias.ts index bcbf0170afb25..6d7ed4db6825a 100644 --- a/x-pack/plugins/maps/public/maps_vis_type_alias.ts +++ b/x-pack/plugins/maps/public/maps_vis_type_alias.ts @@ -37,13 +37,14 @@ export function getMapsVisTypeAlias() { searchFields: ['title^3'], client: getMapClient, toListItem(mapItem: MapItem) { - const { id, type, updatedAt, attributes } = mapItem; + const { id, type, updatedAt, attributes, namespaces } = mapItem; const { title, description } = attributes; return { id, title, description, + namespaces, updatedAt, editUrl: getEditPath(id), editApp: APP_ID, diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 4e654f9e5c641..80c87057f4637 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -198,11 +198,16 @@ export class MapsPlugin euiIconType: APP_ICON_SOLUTION, category: DEFAULT_APP_CATEGORIES.kibana, async mount(params: AppMountParameters) { - const [coreStart, { savedObjectsTagging }] = await core.getStartServices(); + const [coreStart, { savedObjectsTagging, spaces }] = await core.getStartServices(); const UsageTracker = plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; const { renderApp } = await import('./render_app'); - return renderApp(params, { coreStart, AppUsageTracker: UsageTracker, savedObjectsTagging }); + return renderApp(params, { + coreStart, + AppUsageTracker: UsageTracker, + savedObjectsTagging, + spaces, + }); }, }); diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index 6ca0258af9d20..1e30cb3456f14 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -10,6 +10,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { i18n } from '@kbn/i18n'; +import { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { CoreStart, AppMountParameters } from '@kbn/core/public'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; @@ -60,16 +61,20 @@ function setAppChrome() { }); } +const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; + export async function renderApp( { element, history, onAppLeave, setHeaderActionMenu, theme$ }: AppMountParameters, { coreStart, AppUsageTracker, savedObjectsTagging, + spaces, }: { coreStart: CoreStart; savedObjectsTagging?: SavedObjectTaggingPluginStart; AppUsageTracker: React.FC; + spaces?: SpacesPluginStart; } ) { const stateTransfer = getEmbeddableService().getStateTransfer(); @@ -108,40 +113,49 @@ export async function renderApp( } const I18nContext = getCoreI18n().Context; + + const SpacesContextWrapper = spaces + ? spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; + render( - - - - - - // Redirect other routes to list, or if hash-containing, their non-hash equivalents - { - if (hash) { - // Remove leading hash - const newPath = hash.substr(1); - return ; - } else if (pathname === '/' || pathname === '') { - return ; - } else { - return ; - } - }} - /> - - - + + + + + + + // Redirect other routes to list, or if hash-containing, their non-hash + equivalents + { + if (hash) { + // Remove leading hash + const newPath = hash.substr(1); + return ; + } else if (pathname === '/' || pathname === '') { + return ; + } else { + return ; + } + }} + /> + + + + , From 9772e65992dda0642471df24c052b0fd6793d2d9 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 2 Nov 2023 12:17:11 -0400 Subject: [PATCH 04/21] Add Spaces column to Dashboard listing --- .../public/dashboard_app/dashboard_router.tsx | 36 ++++++++++++------- .../use_dashboard_outcome_validation.tsx | 13 ++++--- .../dashboard_listing/dashboard_listing.tsx | 2 ++ .../hooks/use_dashboard_listing_table.tsx | 1 + .../public/services/spaces/spaces.stub.ts | 4 +-- .../public/services/spaces/spaces_service.ts | 16 ++------- .../dashboard/public/services/spaces/types.ts | 8 ++--- 7 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_app/dashboard_router.tsx b/src/plugins/dashboard/public/dashboard_app/dashboard_router.tsx index 2d479a8d9bf15..ccd2ca0eea0ca 100644 --- a/src/plugins/dashboard/public/dashboard_app/dashboard_router.tsx +++ b/src/plugins/dashboard/public/dashboard_app/dashboard_router.tsx @@ -15,6 +15,7 @@ import { HashRouter, RouteComponentProps, Redirect } from 'react-router-dom'; import { Routes, Route } from '@kbn/shared-ux-router'; import { I18nProvider } from '@kbn/i18n-react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { SpacesContextProps } from '@kbn/spaces-plugin/public'; import { AppMountParameters, CoreSetup } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; @@ -44,6 +45,8 @@ export const dashboardUrlParams = { hideFilterBar: 'hide-filter-bar', }; +const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; + export interface DashboardMountProps { appUnMounted: () => void; element: AppMountParameters['element']; @@ -61,6 +64,7 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da data: dataStart, notifications, embeddable, + spaces: { spacesApi }, } = pluginServices.getServices(); let globalEmbedSettings: DashboardEmbedSettings | undefined; @@ -144,23 +148,29 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da window.dispatchEvent(new HashChangeEvent('hashchange')); }); + const SpacesContextWrapper = spacesApi + ? spacesApi.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; + const app = ( - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx index 9d7017845d41e..e9219eb74f2eb 100644 --- a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx +++ b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx @@ -25,7 +25,10 @@ export const useDashboardOutcomeValidation = () => { /** * Unpack dashboard services */ - const { screenshotMode, spaces } = pluginServices.getServices(); + const { + screenshotMode, + spaces: { spacesApi }, + } = pluginServices.getServices(); const validateOutcome: DashboardCreationOptions['validateLoadedSavedObject'] = useCallback( ({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardReturn) => { @@ -43,7 +46,7 @@ export const useDashboardOutcomeValidation = () => { if (screenshotMode.isScreenshotMode()) { scopedHistory.replace(path); // redirect without the toast when in screenshot mode. } else { - spaces.redirectLegacyUrl?.({ path, aliasPurpose }); + spacesApi?.ui.redirectLegacyUrl?.({ path, aliasPurpose }); } return 'redirected'; // redirected. Stop loading dashboard. } @@ -53,20 +56,20 @@ export const useDashboardOutcomeValidation = () => { } return 'valid'; }, - [scopedHistory, screenshotMode, spaces] + [scopedHistory, screenshotMode, spacesApi] ); const getLegacyConflictWarning = useMemo(() => { if (savedObjectId && outcome === 'conflict' && aliasId) { return () => - spaces.getLegacyUrlConflict?.({ + spacesApi?.ui.components?.getLegacyUrlConflict?.({ currentObjectId: savedObjectId, otherObjectId: aliasId, otherObjectPath: `#${createDashboardEditUrl(aliasId)}${scopedHistory.location.search}`, }); } return null; - }, [aliasId, outcome, savedObjectId, scopedHistory, spaces]); + }, [aliasId, outcome, savedObjectId, scopedHistory, spacesApi]); return { validateOutcome, getLegacyConflictWarning }; }; diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx index 6d328fdc18e20..886a4979e5a45 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx @@ -42,6 +42,7 @@ export const DashboardListing = ({ chrome: { theme }, savedObjectsTagging, coreContext: { executionContext }, + spaces: { spacesApi }, } = pluginServices.getServices(); useExecutionContext(executionContext, { @@ -78,6 +79,7 @@ export const DashboardListing = ({ }, toMountPoint, savedObjectsTagging: savedObjectsTaggingFakePlugin, + spacesApi, FormattedRelative, }} > diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 80b39ff0e9d62..7b7ad32e02add 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -44,6 +44,7 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse updatedAt: hit.updatedAt!, references: hit.references, managed: hit.managed, + namespaces: hit.namespaces!, attributes: { title, description, diff --git a/src/plugins/dashboard/public/services/spaces/spaces.stub.ts b/src/plugins/dashboard/public/services/spaces/spaces.stub.ts index d89d5ea6b8356..a50fa503442f0 100644 --- a/src/plugins/dashboard/public/services/spaces/spaces.stub.ts +++ b/src/plugins/dashboard/public/services/spaces/spaces.stub.ts @@ -16,8 +16,6 @@ export const spacesServiceFactory: SpacesServiceFactory = () => { const pluginMock = spacesPluginMock.createStartContract(); return { - getActiveSpace$: pluginMock.getActiveSpace$, - getLegacyUrlConflict: pluginMock.ui.components.getLegacyUrlConflict, - redirectLegacyUrl: pluginMock.ui.redirectLegacyUrl, + spacesApi: pluginMock, }; }; diff --git a/src/plugins/dashboard/public/services/spaces/spaces_service.ts b/src/plugins/dashboard/public/services/spaces/spaces_service.ts index 1146ab0434373..f25f0c42e046f 100644 --- a/src/plugins/dashboard/public/services/spaces/spaces_service.ts +++ b/src/plugins/dashboard/public/services/spaces/spaces_service.ts @@ -16,21 +16,9 @@ export type SpacesServiceFactory = KibanaPluginServiceFactory< >; export const spacesServiceFactory: SpacesServiceFactory = ({ startPlugins }) => { const { spaces } = startPlugins; - if (!spaces || !spaces.ui) return {}; + if (!spaces) return {}; - const { - getActiveSpace$, - ui: { - components: { getLegacyUrlConflict, getSpacesContextProvider }, - redirectLegacyUrl, - useSpaces, - }, - } = spaces; return { - getActiveSpace$, - getLegacyUrlConflict, - getSpacesContextProvider, - redirectLegacyUrl, - useSpaces, + spacesApi: spaces, }; }; diff --git a/src/plugins/dashboard/public/services/spaces/types.ts b/src/plugins/dashboard/public/services/spaces/types.ts index fd324bb9f4773..3583304e5d13a 100644 --- a/src/plugins/dashboard/public/services/spaces/types.ts +++ b/src/plugins/dashboard/public/services/spaces/types.ts @@ -6,12 +6,8 @@ * Side Public License, v 1. */ -import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { SpacesApi } from '@kbn/spaces-plugin/public'; export interface DashboardSpacesService { - getActiveSpace$?: SpacesPluginStart['getActiveSpace$']; - getLegacyUrlConflict?: SpacesPluginStart['ui']['components']['getLegacyUrlConflict']; - getSpacesContextProvider?: SpacesPluginStart['ui']['components']['getSpacesContextProvider']; - redirectLegacyUrl?: SpacesPluginStart['ui']['redirectLegacyUrl']; - useSpaces?: SpacesPluginStart['ui']['useSpaces']; + spacesApi?: SpacesApi; } From 6fecfef5f01ed381d3ea1697ea98884cf6a05da0 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 3 Nov 2023 08:44:05 -0400 Subject: [PATCH 05/21] PoC: Disable delete for shared items --- .../hooks/use_dashboard_listing_table.tsx | 12 ++++++++++++ .../public/components/table_list.tsx | 12 ++++++++++++ .../visualize_app/components/visualize_listing.tsx | 13 +++++++++++++ .../maps/public/routes/list_page/maps_list_view.tsx | 13 +++++++++++++ 4 files changed, 50 insertions(+) diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 7b7ad32e02add..60bb3c0f181f7 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -53,6 +53,17 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse }; }; +const disableDeleteForSharedItem = ({ namespaces }: DashboardSavedObjectUserContent) => { + if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { + return { + delete: { + enabled: false, + reason: 'Dashboards shared to other Spaces can not be deleted.', + }, + }; + } +}; + type DashboardListingViewTableProps = Omit< TableListViewTableProps, 'tableCaption' @@ -296,6 +307,7 @@ export const useDashboardListingTable = ({ initialPageSize, listingLimit, onFetchSuccess, + rowItemActions: disableDeleteForSharedItem, setPageDataTestSubject, title, urlStateEnabled, diff --git a/src/plugins/event_annotation_listing/public/components/table_list.tsx b/src/plugins/event_annotation_listing/public/components/table_list.tsx index 603d39e193360..a8582f1d06999 100644 --- a/src/plugins/event_annotation_listing/public/components/table_list.tsx +++ b/src/plugins/event_annotation_listing/public/components/table_list.tsx @@ -31,6 +31,17 @@ import { GroupEditorFlyout } from './group_editor_flyout'; export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; +const disableDeleteForSharedItem = ({ namespaces }: EventAnnotationGroupContent) => { + if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { + return { + delete: { + enabled: false, + reason: 'Annotation groups shared to other Spaces can not be deleted.', + }, + }; + } +}; + const getCustomColumn = (dataViews: DataView[]) => { const dataViewNameMap = Object.fromEntries( dataViews.map((dataView) => [dataView.id, dataView.name ?? dataView.title]) @@ -201,6 +212,7 @@ export const EventAnnotationGroupTableList = ({ initialPageSize={initialPageSize} initialFilter={''} customTableColumn={getCustomColumn(dataViews)} + rowItemActions={disableDeleteForSharedItem} emptyPrompt={ { + if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { + return { + delete: { + enabled: false, + reason: 'Visualizations shared to other Spaces can not be deleted.', + }, + }; + } +}; + const toTableListViewSavedObject = (savedObject: Record): VisualizeUserContent => { return { id: savedObject.id as string, @@ -95,6 +106,7 @@ type CustomTableViewProps = Pick< | 'contentEditor' | 'emptyPrompt' | 'itemIsEditable' + | 'rowItemActions' >; const useTableListViewProps = ( @@ -258,6 +270,7 @@ const useTableListViewProps = ( editItem, emptyPrompt: noItemsFragment, createItem: createNewVis, + rowItemActions: disableDeleteForSharedItem, itemIsEditable: ({ attributes: { readOnly } }) => visualizeCapabilities.save && !readOnly, }; diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index 8c982a2853708..54a7a2cb86a30 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -43,10 +43,22 @@ function navigateToNewMap() { }); } +const disableDeleteForSharedItem = ({ namespaces }: MapUserContent) => { + if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { + return { + delete: { + enabled: false, + reason: 'Maps shared to other Spaces can not be deleted.', + }, + }; + } +}; + const toTableListViewSavedObject = (mapItem: MapItem): MapUserContent => { return { ...mapItem, updatedAt: mapItem.updatedAt!, + namespaces: mapItem.namespaces!, attributes: { ...mapItem.attributes, title: mapItem.attributes.title ?? '', @@ -138,6 +150,7 @@ function MapsListViewComp({ history }: Props) { entityNamePlural={i18n.translate('xpack.maps.mapListing.entityNamePlural', { defaultMessage: 'maps', })} + rowItemActions={disableDeleteForSharedItem} title={APP_NAME} onClickTitle={({ id }) => history.push(getEditPath(id))} /> From 5985e120e012e16fcc2e0a22fbab93beb73da73b Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 7 Nov 2023 15:30:14 -0500 Subject: [PATCH 06/21] Allow unshare from current space --- .../src/components/spaces_list.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 index 61423df5dd27f..718c7bfa05d2e 100644 --- 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 @@ -9,10 +9,10 @@ import React, { FC, useState } from 'react'; import type { Capabilities } from '@kbn/core/public'; -import type { SpacesPluginStart, ShareToSpaceFlyoutProps } from '@kbn/spaces-plugin/public'; +import type { SpacesApi, ShareToSpaceFlyoutProps } from '@kbn/spaces-plugin/public'; interface Props { - spacesApi: SpacesPluginStart; + spacesApi: SpacesApi; capabilities: Capabilities | undefined; spaceIds: string[]; type: string; @@ -38,6 +38,11 @@ export const SpacesList: FC = ({ setShowFlyout(false); } + // TODO Check the namespaceType and disable column if not multiple + // See src/plugins/saved_objects_management/public/services/columns/share_saved_objects_to_space_column.tsx + + // TODO Disable for Serverless + const LazySpaceList = spacesApi.ui.components.getSpaceList; const LazyShareToSpaceFlyout = spacesApi.ui.components.getShareToSpaceFlyout; @@ -49,6 +54,7 @@ export const SpacesList: FC = ({ title, noun, }, + behaviorContext: 'outside-space', onUpdate: refresh, onClose, }; From 96295d4ea6b57da55955d02438035cb5647fa088 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 7 Nov 2023 15:31:47 -0500 Subject: [PATCH 07/21] Add Spaces column to Add from library flyout --- .../embeddable/api/add_panel_from_library.ts | 5 ++++ .../add_panel_flyout/add_panel_flyout.tsx | 25 ++++--------------- .../open_add_panel_flyout.tsx | 14 +++++++---- src/plugins/embeddable/tsconfig.json | 1 - .../public/finder/index.tsx | 9 +++++-- .../public/finder/saved_object_finder.tsx | 21 ++++++++++++++++ 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts index 60c78a62ebbc7..58901b16a30ca 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts @@ -8,8 +8,12 @@ import { isErrorEmbeddable, openAddPanelFlyout } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../dashboard_container'; +import { pluginServices } from '../../../services/plugin_services'; export function addFromLibrary(this: DashboardContainer) { + const { + spaces: { spacesApi }, + } = pluginServices.getServices(); if (isErrorEmbeddable(this)) return; this.openOverlay( openAddPanelFlyout({ @@ -21,6 +25,7 @@ export function addFromLibrary(this: DashboardContainer) { onClose: () => { this.clearOverlays(); }, + spaces: spacesApi, }) ); } diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx index 04407ff00d022..27fd1abbc5533 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx @@ -12,13 +12,13 @@ import { i18n } from '@kbn/i18n'; import { Toast } from '@kbn/core/public'; import { METRIC_TYPE } from '@kbn/analytics'; import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import { SpacesApi } from '@kbn/spaces-plugin/public'; import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; import { SavedObjectFinder, SavedObjectFinderProps, type SavedObjectMetaData, } from '@kbn/saved-objects-finder-plugin/public'; -import { DashboardAPI } from '@kbn/dashboard-plugin/public'; import { core, @@ -26,7 +26,6 @@ import { usageCollection, savedObjectsTaggingOss, contentManagement, - spaces, } from '../kibana_services'; import { IContainer, @@ -67,9 +66,11 @@ const runAddTelemetry = ( export const AddPanelFlyout = ({ container, onAddPanel, + spaces, }: { container: IContainer; onAddPanel?: (id: string) => void; + spaces?: SpacesApi; }) => { const factoriesBySavedObjectType: FactoryMap = useMemo(() => { return [...embeddableStart.getEmbeddableFactories()] @@ -80,10 +81,6 @@ export const AddPanelFlyout = ({ }, {} as FactoryMap); }, []); - const { spacesManager } = spaces.ui.useSpaces(); - - const namespaces = (container as DashboardAPI).select((state) => state.componentState.namespaces); - const metaData = useMemo( () => Object.values(factoriesBySavedObjectType) @@ -107,19 +104,6 @@ export const AddPanelFlyout = ({ throw new EmbeddableFactoryNotFoundError(type); } - const shareableRefs = await spacesManager.getShareableReferences([{ type, id }]); - // TODO: toast this, warn users before auto sharing nested SOs to other spaces? - const updateSpacesResult = await spacesManager.updateSavedObjectsSpaces( - shareableRefs.objects.map( - ({ type: objectType, id: objectId }: { type: string; id: string }) => ({ - type: objectType, - id: objectId, - }) - ), - namespaces || [], - [] - ); - const embeddable = await container.addNewEmbeddable( factoryForSavedObjectType.type, { savedObjectId: id }, @@ -130,7 +114,7 @@ export const AddPanelFlyout = ({ showSuccessToast(name); runAddTelemetry(container.type, factoryForSavedObjectType, savedObject); }, - [container, factoriesBySavedObjectType, namespaces, onAddPanel, spacesManager] + [container, factoriesBySavedObjectType, onAddPanel] ); return ( @@ -148,6 +132,7 @@ export const AddPanelFlyout = ({ contentClient: contentManagement.client, savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), uiSettings: core.uiSettings, + spaces, }} onChoose={onChoose} savedObjectMetaData={metaData} diff --git a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx index da8771ea1b8c1..0f264645923d8 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx @@ -10,11 +10,11 @@ import React, { Suspense } from 'react'; import { OverlayRef } from '@kbn/core/public'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { SpacesContextProps } from '@kbn/spaces-plugin/public'; +import { SpacesApi, SpacesContextProps } from '@kbn/spaces-plugin/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { IContainer } from '../lib'; -import { core, spaces } from '../kibana_services'; +import { core } from '../kibana_services'; const LazyAddPanelFlyout = React.lazy(async () => { const module = await import('./add_panel_flyout'); @@ -27,19 +27,23 @@ export const openAddPanelFlyout = ({ container, onAddPanel, onClose, + spaces, }: { container: IContainer; onAddPanel?: (id: string) => void; onClose?: () => void; + spaces?: SpacesApi; }): OverlayRef => { - const SpacesContextWrapper = - spaces.ui.components.getSpacesContextProvider ?? getEmptyFunctionComponent; + const SpacesContextWrapper = spaces + ? spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; + // send the overlay ref to the root embeddable if it is capable of tracking overlays const flyoutSession = core.overlays.openFlyout( toMountPoint( }> - + , { theme$: core.theme.theme$ } diff --git a/src/plugins/embeddable/tsconfig.json b/src/plugins/embeddable/tsconfig.json index 9b2fe7bbdeede..e3ef806177171 100644 --- a/src/plugins/embeddable/tsconfig.json +++ b/src/plugins/embeddable/tsconfig.json @@ -35,7 +35,6 @@ "@kbn/unified-search-plugin", "@kbn/data-views-plugin", "@kbn/spaces-plugin", - "@kbn/dashboard-plugin", ], "exclude": ["target/**/*"] } diff --git a/src/plugins/saved_objects_finder/public/finder/index.tsx b/src/plugins/saved_objects_finder/public/finder/index.tsx index 3d991ee7fd4dd..6872ab8660bc3 100644 --- a/src/plugins/saved_objects_finder/public/finder/index.tsx +++ b/src/plugins/saved_objects_finder/public/finder/index.tsx @@ -8,6 +8,7 @@ import { EuiDelayRender, EuiSkeletonText } from '@elastic/eui'; import React from 'react'; +import { SpacesApi } from '@kbn/spaces-plugin/public'; import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { ContentClient } from '@kbn/content-management-plugin/public'; import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; @@ -29,10 +30,14 @@ const SavedObjectFinder = (props: SavedObjectFinderProps) => ( export const getSavedObjectFinder = ( contentClient: ContentClient, uiSettings: IUiSettingsClient, - savedObjectsTagging?: SavedObjectsTaggingApi + savedObjectsTagging?: SavedObjectsTaggingApi, + spaces?: SpacesApi ) => { return (props: SavedObjectFinderProps) => ( - + ); }; diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index d660a8bf2857a..9efa6864e9e0c 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -30,6 +30,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { SpacesApi } from '@kbn/spaces-plugin/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { FinderAttributes, SavedObjectCommon, LISTING_LIMIT_SETTING } from '../../common'; @@ -59,6 +60,7 @@ interface SavedObjectFinderState { interface SavedObjectFinderServices { savedObjectsTagging?: SavedObjectsTaggingApi; + spaces?: SpacesApi; contentClient: ContentClient; uiSettings: IUiSettingsClient; } @@ -211,6 +213,7 @@ export class SavedObjectFinderUi extends React.Component< public render() { const { onChoose, savedObjectMetaData } = this.props; const taggingApi = this.props.services.savedObjectsTagging; + const spaces = this.props.services.spaces; const originalTagColumn = taggingApi?.ui.getTableColumnDefinition(); const tagColumn: EuiTableFieldDataColumnType | undefined = originalTagColumn ? { @@ -222,6 +225,22 @@ export class SavedObjectFinderUi extends React.Component< ['data-test-subj']: 'savedObjectFinderTags', } : undefined; + const spacesColumn: EuiTableFieldDataColumnType | undefined = spaces + ? { + field: 'spaces', + name: i18n.translate('savedObjectsFinder.spacesColumnName', { + defaultMessage: 'Spaces', + }), + width: '20%', + render: (_, item) => { + return spaces.ui.components.getSpaceList({ + namespaces: item.namespaces!, + displayLimit: 5, + behaviorContext: 'outside-space', + }); + }, + } + : undefined; const typeColumn: EuiTableFieldDataColumnType | undefined = savedObjectMetaData.length > 1 ? { @@ -266,6 +285,7 @@ export class SavedObjectFinderUi extends React.Component< }, } : undefined; + const columns: Array> = [ ...(typeColumn ? [typeColumn] : []), { @@ -306,6 +326,7 @@ export class SavedObjectFinderUi extends React.Component< }, }, ...(tagColumn ? [tagColumn] : []), + ...(spacesColumn ? [spacesColumn] : []), ]; const pagination = { initialPageSize: this.props.initialPageSize || this.props.fixedPageSize || 10, From 61291a57c5f89f0c2784ab708b6a8eecaaf0e8de Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 7 Nov 2023 15:32:28 -0500 Subject: [PATCH 08/21] Add Spaces column to Open Search Panel --- .../components/top_nav/open_search_panel.tsx | 142 ++++++++++-------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx index a571ec2db7784..38c73416fe576 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -19,7 +19,9 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; +import { SpacesContextProps } from '@kbn/spaces-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { PLUGIN_ID } from '../../../../../common'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; const SEARCH_OBJECT_TYPE = 'search'; @@ -29,75 +31,85 @@ interface OpenSearchPanelProps { onOpenSavedSearch: (id: string) => void; } +const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; + export function OpenSearchPanel(props: OpenSearchPanelProps) { - const { addBasePath, capabilities, savedObjectsTagging, contentClient, uiSettings } = + const { addBasePath, capabilities, savedObjectsTagging, contentClient, spaces, uiSettings } = useDiscoverServices(); const hasSavedObjectPermission = capabilities.savedObjectsManagement?.edit || capabilities.savedObjectsManagement?.delete; + const SpacesContextWrapper = useMemo( + () => (spaces ? spaces.ui.components.getSpacesContextProvider : getEmptyFunctionComponent), + [spaces] + ); + return ( - - - -

- -

-
-
- - - } - savedObjectMetaData={[ - { - type: SEARCH_OBJECT_TYPE, - getIconForSavedObject: () => 'discoverApp', - name: i18n.translate('discover.savedSearch.savedObjectName', { - defaultMessage: 'Saved search', - }), - }, - ]} - onChoose={(id) => { - props.onOpenSavedSearch(id); - props.onClose(); - }} - showFilter={true} - /> - - {hasSavedObjectPermission && ( - - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - - - - - - )} -
+ + + + +

+ +

+
+
+ + + } + savedObjectMetaData={[ + { + type: SEARCH_OBJECT_TYPE, + getIconForSavedObject: () => 'discoverApp', + name: i18n.translate('discover.savedSearch.savedObjectName', { + defaultMessage: 'Saved search', + }), + }, + ]} + onChoose={(id) => { + props.onOpenSavedSearch(id); + props.onClose(); + }} + showFilter={true} + /> + + {hasSavedObjectPermission && ( + + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + + + )} +
+
); } From 488d64cdc534fefe799220f6faf92dd54fd96a84 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:40:25 +0000 Subject: [PATCH 09/21] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/saved_objects_finder/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/saved_objects_finder/tsconfig.json b/src/plugins/saved_objects_finder/tsconfig.json index cecc9fbdadb61..13cde4ce702de 100644 --- a/src/plugins/saved_objects_finder/tsconfig.json +++ b/src/plugins/saved_objects_finder/tsconfig.json @@ -15,6 +15,7 @@ "@kbn/content-management-plugin", "@kbn/content-management-utils", "@kbn/core-ui-settings-browser", + "@kbn/spaces-plugin", ], "exclude": [ "target/**/*", From 0f6d24ce7e701af046c0baf51ed373b382292f80 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 7 Nov 2023 16:03:32 -0500 Subject: [PATCH 10/21] Use default cursor style for spaces list --- .../saved_objects_finder/public/finder/saved_object_finder.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index 9efa6864e9e0c..4cd10bb38851c 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -237,6 +237,7 @@ export class SavedObjectFinderUi extends React.Component< namespaces: item.namespaces!, displayLimit: 5, behaviorContext: 'outside-space', + cursorStyle: 'default', }); }, } From eb35f806f9e32f95a3478d17852aed62ac6616c4 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Wed, 8 Nov 2023 12:18:58 -0500 Subject: [PATCH 11/21] Fix types on test --- .../src/table_list_view.test.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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..a1d15bec35655 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 @@ -141,6 +141,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: 'item-1', + namespaces: ['default'], type: 'dashboard', updatedAt: '2020-01-01T00:00:00Z', attributes: { @@ -188,6 +189,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -198,6 +200,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 +333,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [...Array(totalItems)].map((_, i) => ({ id: `item${i}`, + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -438,6 +442,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), // first asc, last desc type: 'dashboard', attributes: { @@ -447,6 +452,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: yesterday.toISOString(), // first desc, last asc type: 'dashboard', attributes: { @@ -639,6 +645,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 +656,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 2)).toISOString(), attributes: { title: 'Item 2', @@ -697,6 +705,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 +719,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: new Date(new Date().setDate(new Date().getDate() - 2)).toISOString(), type: 'dashboard', attributes: { @@ -890,6 +900,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: 'item-1', + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -899,6 +910,7 @@ describe('TableListView', () => { }, { id: 'item-2', + namespaces: ['default'], type: 'dashboard', updatedAt, attributes: { @@ -1075,6 +1087,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: yesterday.toISOString(), type: 'dashboard', attributes: { @@ -1085,6 +1098,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -1344,6 +1358,7 @@ describe('TableListView', () => { const hits: UserContentCommonSchema[] = [ { id: '123', + namespaces: ['default'], updatedAt: twoDaysAgo.toISOString(), type: 'dashboard', attributes: { @@ -1354,6 +1369,7 @@ describe('TableListView', () => { }, { id: '456', + namespaces: ['default'], updatedAt: yesterday.toISOString(), type: 'dashboard', attributes: { @@ -1467,6 +1483,7 @@ describe('TableList', () => { const originalHits: UserContentCommonSchema[] = [ { id: `item`, + namespaces: ['default'], type: 'dashboard', updatedAt: 'original timestamp', attributes: { @@ -1491,6 +1508,7 @@ describe('TableList', () => { const hits: UserContentCommonSchema[] = [ { id: `item`, + namespaces: ['default'], type: 'dashboard', updatedAt: 'updated timestamp', attributes: { From ad4bb6267022c4815183d61ac69ed43b85d193d9 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Wed, 8 Nov 2023 16:38:26 -0500 Subject: [PATCH 12/21] Make Files shareable in File Management --- packages/shared-ux/file/types/index.ts | 4 ++ src/plugins/files/server/file/file.ts | 3 +- src/plugins/files/server/file/to_json.ts | 7 ++- .../files/server/file_client/file_client.ts | 17 ++++--- .../adapters/saved_objects.ts | 3 ++ .../file_metadata_client.ts | 4 ++ .../file_service/internal_file_service.ts | 2 +- .../files/server/saved_objects/file.ts | 4 +- src/plugins/files_management/kibana.jsonc | 3 ++ src/plugins/files_management/public/app.tsx | 1 + .../public/mount_management_section.tsx | 48 ++++++++++++------- src/plugins/files_management/public/types.ts | 2 + src/plugins/files_management/tsconfig.json | 1 + 13 files changed, 70 insertions(+), 29 deletions(-) diff --git a/packages/shared-ux/file/types/index.ts b/packages/shared-ux/file/types/index.ts index 32dda0bd933a7..053bda5b2d73c 100644 --- a/packages/shared-ux/file/types/index.ts +++ b/packages/shared-ux/file/types/index.ts @@ -230,6 +230,10 @@ export interface FileJSON { * File hash information */ hash?: BaseFileMetadata['hash']; + /** + * The Spaces to which this saved object is shared + */ + namespaces?: string[]; } export interface FileKindBase { diff --git a/src/plugins/files/server/file/file.ts b/src/plugins/files/server/file/file.ts index eeec150cfc78c..9d150e8ebdb16 100644 --- a/src/plugins/files/server/file/file.ts +++ b/src/plugins/files/server/file/file.ts @@ -43,7 +43,8 @@ export class File implements IFile { public readonly id: string, private metadata: FileJSON, private readonly fileClient: FileClientImpl, - private readonly logger: Logger + private readonly logger: Logger, + public readonly namespaces?: string[] ) {} private async updateFileState(action: Action): Promise { diff --git a/src/plugins/files/server/file/to_json.ts b/src/plugins/files/server/file/to_json.ts index 68870e94ee67c..c59ab53fda8a5 100644 --- a/src/plugins/files/server/file/to_json.ts +++ b/src/plugins/files/server/file/to_json.ts @@ -43,7 +43,11 @@ export function serializeJSON(attrs: Partial): Partial(id: string, attrs: FileMetadata): FileJSON { +export function toJSON( + id: string, + attrs: FileMetadata, + namespaces?: string[] +): FileJSON { const { name, mime_type: mimeType, @@ -61,6 +65,7 @@ export function toJSON(id: string, attrs: FileMetadata): FileJSO return pickBy>( { id, + namespaces, user, name, mimeType, diff --git a/src/plugins/files/server/file_client/file_client.ts b/src/plugins/files/server/file_client/file_client.ts index 3bce97f6bcade..f889a23804cb1 100644 --- a/src/plugins/files/server/file_client/file_client.ts +++ b/src/plugins/files/server/file_client/file_client.ts @@ -107,7 +107,11 @@ export class FileClientImpl implements FileClient { FileClientImpl.usageCounter?.incrementCounter({ counterName: this.getCounters()[counter] }); } - private instantiateFile(id: string, metadata: FileMetadata): File { + private instantiateFile( + id: string, + metadata: FileMetadata, + namespaces?: string[] + ): File { return new FileImpl( id, toJSON(id, { @@ -115,7 +119,8 @@ export class FileClientImpl implements FileClient { ...metadata, }), this, - this.logger + this.logger, + namespaces ); } @@ -150,8 +155,8 @@ export class FileClientImpl implements FileClient { } public async get(arg: P1): Promise> { - const { id, metadata } = await this.metadataClient.get(arg); - return this.instantiateFile(id, metadata as FileMetadata); + const { id, namespaces, metadata } = await this.metadataClient.get(arg); + return this.instantiateFile(id, metadata as FileMetadata, namespaces); } public async internalUpdate(id: string, metadata: Partial): Promise { @@ -170,8 +175,8 @@ export class FileClientImpl implements FileClient { const result = await this.metadataClient.find(arg); return { total: result.total, - files: result.files.map(({ id, metadata }) => - this.instantiateFile(id, metadata as FileMetadata) + files: result.files.map(({ id, namespaces, metadata }) => + this.instantiateFile(id, metadata as FileMetadata, namespaces) ), }; } diff --git a/src/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts b/src/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts index 84274d695d292..f507ee781cdb2 100644 --- a/src/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts +++ b/src/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts @@ -61,6 +61,7 @@ export class SavedObjectsFileMetadataClient implements FileMetadataClient { const result = await this.soClient.get(this.soType, id); return { id: result.id, + namespaces: result.namespaces, metadata: result.attributes as FileDescriptor['metadata'], }; } @@ -78,6 +79,7 @@ export class SavedObjectsFileMetadataClient implements FileMetadataClient { return { id: so.id, + namespaces: so.namespaces, metadata: so.attributes as FileDescriptor['metadata'], }; }); @@ -98,6 +100,7 @@ export class SavedObjectsFileMetadataClient implements FileMetadataClient { return { files: result.saved_objects.map((so) => ({ id: so.id, + namespaces: so.namespaces, metadata: so.attributes as FileMetadata, })), total: result.total, diff --git a/src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts b/src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts index 33117ad842afc..db38b2f94c947 100644 --- a/src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts +++ b/src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts @@ -33,6 +33,10 @@ export interface FileDescriptor { * Unique ID of a file, used to locate a file. */ id: string; + /** + * Spaces to which the file is shared + */ + namespaces?: string[]; /** * The file's metadata. */ diff --git a/src/plugins/files/server/file_service/internal_file_service.ts b/src/plugins/files/server/file_service/internal_file_service.ts index b224a28313a70..4fbf824d75acd 100644 --- a/src/plugins/files/server/file_service/internal_file_service.ts +++ b/src/plugins/files/server/file_service/internal_file_service.ts @@ -167,7 +167,7 @@ export class InternalFileService { const { total, files } = await this.metadataClient.find(args); return { total, - files: files.map(({ id, metadata }) => toJSON(id, metadata)), + files: files.map(({ id, namespaces, metadata }) => toJSON(id, metadata, namespaces)), }; } diff --git a/src/plugins/files/server/saved_objects/file.ts b/src/plugins/files/server/saved_objects/file.ts index f00946aadc05c..8d25fff5e88de 100644 --- a/src/plugins/files/server/saved_objects/file.ts +++ b/src/plugins/files/server/saved_objects/file.ts @@ -61,8 +61,8 @@ const properties: Properties = { export const fileObjectType: SavedObjectsType = { name: FILE_SO_TYPE, - hidden: true, - namespaceType: 'multiple-isolated', + hidden: false, + namespaceType: 'multiple', management: { importableAndExportable: false, }, diff --git a/src/plugins/files_management/kibana.jsonc b/src/plugins/files_management/kibana.jsonc index c336e0c61bf67..d4a33a3c1ec92 100644 --- a/src/plugins/files_management/kibana.jsonc +++ b/src/plugins/files_management/kibana.jsonc @@ -13,6 +13,9 @@ ], "requiredBundles": [ "kibanaReact" + ], + "optionalPlugins": [ + "spaces" ] } } diff --git a/src/plugins/files_management/public/app.tsx b/src/plugins/files_management/public/app.tsx index 08f942c644789..e377c1ca1d4be 100644 --- a/src/plugins/files_management/public/app.tsx +++ b/src/plugins/files_management/public/app.tsx @@ -57,6 +57,7 @@ export const App: FunctionComponent = () => { .then(({ files, total }) => ({ hits: files.map((file) => ({ id: file.id, + namespaces: file.namespaces ?? [], updatedAt: file.updated, references: [], type: 'file', diff --git a/src/plugins/files_management/public/mount_management_section.tsx b/src/plugins/files_management/public/mount_management_section.tsx index 7e8f6cb1e551d..1f64f4b569463 100755 --- a/src/plugins/files_management/public/mount_management_section.tsx +++ b/src/plugins/files_management/public/mount_management_section.tsx @@ -10,6 +10,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Router } from '@kbn/shared-ux-router'; import { Route } from '@kbn/shared-ux-router'; +import { SpacesContextProps } from '@kbn/spaces-plugin/public'; import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; import { I18nProvider, FormattedRelative } from '@kbn/i18n-react'; import type { CoreStart } from '@kbn/core/public'; @@ -22,9 +23,12 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { StartDependencies } from './types'; import { App } from './app'; import { FilesManagementAppContextProvider } from './context'; +import { PLUGIN_ID } from '../common'; const queryClient = new QueryClient(); +const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; + export const mountManagementSection = ( coreStart: CoreStart, startDeps: StartDependencies, @@ -32,30 +36,38 @@ export const mountManagementSection = ( ) => { const { files: { filesClientFactory, getAllFindKindDefinitions, getFileKindDefinition }, + spaces, } = startDeps; + const SpacesContextWrapper = spaces + ? spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; + ReactDOM.render( - - - + + - - - - - - + + + + + + + + , element diff --git a/src/plugins/files_management/public/types.ts b/src/plugins/files_management/public/types.ts index 303d5e1c5d1a7..0e65d071be119 100755 --- a/src/plugins/files_management/public/types.ts +++ b/src/plugins/files_management/public/types.ts @@ -8,6 +8,7 @@ import { FilesClient, FilesSetup, FilesStart } from '@kbn/files-plugin/public'; import { ManagementSetup } from '@kbn/management-plugin/public'; +import { SpacesApi } from '@kbn/spaces-plugin/public'; export interface AppContext { filesClient: FilesClient; @@ -21,4 +22,5 @@ export interface SetupDependencies { } export interface StartDependencies { files: FilesStart; + spaces?: SpacesApi; } diff --git a/src/plugins/files_management/tsconfig.json b/src/plugins/files_management/tsconfig.json index 28986030e75f8..26b9f2e1c91cb 100644 --- a/src/plugins/files_management/tsconfig.json +++ b/src/plugins/files_management/tsconfig.json @@ -15,6 +15,7 @@ "@kbn/i18n-react", "@kbn/shared-ux-file-image", "@kbn/shared-ux-router", + "@kbn/spaces-plugin", ], "exclude": [ "target/**/*", From 59c9cbeaf2984589891beecf003eca266868eff8 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Nov 2023 22:02:19 +0000 Subject: [PATCH 13/21] [CI] Auto-commit changed files from 'node scripts/jest_integration -u src/core/server/integration_tests/ci_checks' --- .../ci_checks/saved_objects/check_registered_types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 3394bb890f9ae..8915dc34f4f59 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -90,7 +90,7 @@ describe('checking migration metadata changes on all registered SO types', () => "event_loop_delays_daily": "01b967e8e043801357503de09199dfa3853bab88", "exception-list": "4aebc4e61fb5d608cae48eaeb0977e8db21c61a4", "exception-list-agnostic": "6d3262d58eee28ac381ec9654f93126a58be6f5d", - "file": "6b65ae5899b60ebe08656fd163ea532e557d3c98", + "file": "15fd72d960d43cc12cb2d34645497cd28aa05c02", "file-upload-usage-collection-telemetry": "06e0a8c04f991e744e09d03ab2bd7f86b2088200", "fileShare": "5be52de1747d249a221b5241af2838264e19aaa1", "fleet-fleet-server-host": "b04898fcde07f4ce86e844c8fe2f4b23b77ef60a", From b0f05f5fe991a9da52e673324cad9214bffdecbe Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 9 Nov 2023 11:05:11 -0500 Subject: [PATCH 14/21] Add Spaces column test to savedobjectfinder --- .../finder/saved_object_finder.test.tsx | 72 ++++++++++++------- .../public/finder/saved_object_finder.tsx | 1 + 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx index 8ac5715263e30..3065cad51a9c1 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx @@ -9,7 +9,10 @@ const nextTick = () => new Promise((res) => process.nextTick(res)); import lodash from 'lodash'; -jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => fn); +jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => { + fn.cancel = jest.fn(); + return fn; +}); import { EuiInMemoryTable, EuiLink, @@ -20,6 +23,7 @@ import { } from '@elastic/eui'; import { IconType } from '@elastic/eui'; import { mount, shallow } from 'enzyme'; +import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import * as sinon from 'sinon'; import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder'; @@ -27,21 +31,24 @@ import { contentManagementMock } from '@kbn/content-management-plugin/public/moc import { findTestSubject } from '@kbn/test-jest-helpers'; import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { coreMock } from '@kbn/core/public/mocks'; +import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; describe('SavedObjectsFinder', () => { const doc = { id: '1', + namespaces: ['default'], type: 'search', attributes: { title: 'Example title' }, }; const doc2 = { id: '2', + namespaces: ['default'], type: 'search', attributes: { title: 'Another title' }, }; - const doc3 = { type: 'vis', id: '3', attributes: { title: 'Vis' } }; + const doc3 = { type: 'vis', id: '3', namespaces: ['default'], attributes: { title: 'Vis' } }; const searchMetaData = [ { @@ -72,6 +79,7 @@ describe('SavedObjectsFinder', () => { (contentClient.mSearch as any as jest.SpyInstance).mockClear(); }); const coreStart = coreMock.createStart(); + const spaces = spacesPluginMock.createStartContract(); const uiSettings = coreStart.uiSettings; uiSettings.get.mockImplementation(() => 10); @@ -904,20 +912,36 @@ describe('SavedObjectsFinder', () => { hits: [doc, doc2, doc3], }); - const wrapper = mount( + render( ); - wrapper.instance().componentDidMount!(); - await nextTick(); - wrapper.update(); - expect(wrapper.find(EuiInMemoryTable).find('th')).toHaveLength(3); - expect(findTestSubject(wrapper, 'tableHeaderCell_type_0')).toHaveLength(1); - expect(findTestSubject(wrapper, 'tableHeaderCell_title_1')).toHaveLength(1); - expect(findTestSubject(wrapper, 'tableHeaderCell_references_2')).toHaveLength(1); + await waitFor(async () => expect(await screen.findAllByRole('columnheader')).toHaveLength(3)); + expect(screen.getByTestId('tableHeaderCell_type_0')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_title_1')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_references_2')).toBeInTheDocument(); + }); + + it('should show the Spaces column if spaces is available', async () => { + (contentClient.mSearch as any as jest.SpyInstance).mockResolvedValue({ + hits: [doc, doc2, doc3], + }); + + render( + + ); + + await waitFor(async () => expect(await screen.findAllByRole('columnheader')).toHaveLength(4)); + expect(screen.getByTestId('tableHeaderCell_type_0')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_title_1')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_references_2')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_spaces_3')).toBeInTheDocument(); }); it('should hide the type column if there is only one type in the metadata list', async () => { @@ -925,20 +949,17 @@ describe('SavedObjectsFinder', () => { hits: [doc, doc2], }); - const wrapper = mount( + render( ); - wrapper.instance().componentDidMount!(); - await nextTick(); - wrapper.update(); - expect(wrapper.find(EuiInMemoryTable).find('th')).toHaveLength(2); - expect(findTestSubject(wrapper, 'tableHeaderCell_type_0')).toHaveLength(0); - expect(findTestSubject(wrapper, 'tableHeaderCell_title_0')).toHaveLength(1); - expect(findTestSubject(wrapper, 'tableHeaderCell_references_1')).toHaveLength(1); + await waitFor(async () => expect(await screen.findAllByRole('columnheader')).toHaveLength(2)); + expect(screen.getByTestId('tableHeaderCell_title_0')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_references_1')).toBeInTheDocument(); + expect(screen.queryByTestId('tableHeaderCell_type_0')).not.toBeInTheDocument(); }); it('should hide the tags column if savedObjectsTagging is undefined', async () => { @@ -946,20 +967,17 @@ describe('SavedObjectsFinder', () => { hits: [doc, doc2, doc3], }); - const wrapper = mount( + render( ); - wrapper.instance().componentDidMount!(); - await nextTick(); - wrapper.update(); - expect(wrapper.find(EuiInMemoryTable).find('th')).toHaveLength(2); - expect(findTestSubject(wrapper, 'tableHeaderCell_type_0')).toHaveLength(1); - expect(findTestSubject(wrapper, 'tableHeaderCell_title_1')).toHaveLength(1); - expect(findTestSubject(wrapper, 'tableHeaderCell_references_2')).toHaveLength(0); + await waitFor(async () => expect(await screen.findAllByRole('columnheader')).toHaveLength(2)); + expect(screen.getByTestId('tableHeaderCell_type_0')).toBeInTheDocument(); + expect(screen.getByTestId('tableHeaderCell_title_1')).toBeInTheDocument(); + expect(screen.queryByTestId('tableHeaderCell_references_2')).not.toBeInTheDocument(); }); }); }); diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index 4cd10bb38851c..9896a1b2790d6 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -232,6 +232,7 @@ export class SavedObjectFinderUi extends React.Component< defaultMessage: 'Spaces', }), width: '20%', + ['data-test-subj']: 'savedObjectFinderSpaces', render: (_, item) => { return spaces.ui.components.getSpaceList({ namespaces: item.namespaces!, From 97913d372d75765ac5bdc0f5266e562531260a31 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 9 Nov 2023 11:05:34 -0500 Subject: [PATCH 15/21] Fix hook usage error --- .../main/components/top_nav/open_search_panel.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx index 38c73416fe576..2ae106ce34c17 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -39,10 +39,9 @@ export function OpenSearchPanel(props: OpenSearchPanelProps) { const hasSavedObjectPermission = capabilities.savedObjectsManagement?.edit || capabilities.savedObjectsManagement?.delete; - const SpacesContextWrapper = useMemo( - () => (spaces ? spaces.ui.components.getSpacesContextProvider : getEmptyFunctionComponent), - [spaces] - ); + const SpacesContextWrapper = spaces + ? spaces.ui.components.getSpacesContextProvider + : getEmptyFunctionComponent; return ( From f25323497faf68dfabdaceb537990b5bc07e6a96 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 9 Nov 2023 11:14:48 -0500 Subject: [PATCH 16/21] Update Annotations snapshot --- .../__snapshots__/service.test.ts.snap | 12 ++++++++++++ .../public/event_annotation_service/service.test.ts | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap b/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap index 0bd643095dcdc..ad113cc376690 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap +++ b/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap @@ -11,6 +11,9 @@ Object { "title": undefined, }, "id": "nonExistingGroup", + "namespaces": Array [ + "default", + ], "references": Array [], "type": undefined, "updatedAt": "", @@ -23,6 +26,9 @@ Object { "title": "groupTitle", }, "id": undefined, + "namespaces": Array [ + "default", + ], "references": Array [ Object { "id": "ipid", @@ -46,6 +52,9 @@ Object { "title": "groupTitle", }, "id": "multiAnnotations", + "namespaces": Array [ + "default", + ], "references": Array [ Object { "id": "ipid", @@ -66,6 +75,9 @@ Object { "title": "groupTitle", }, "id": "multiAnnotations", + "namespaces": Array [ + "default", + ], "references": Array [], "type": "event-annotation-group", "updatedAt": "", diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts index 7a0f00e7a4de2..3765d964859d2 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts @@ -22,6 +22,7 @@ type AnnotationGroupSavedObject = SimpleSavedObject = { nonExistingGroup: { attributes: {} as EventAnnotationGroupSavedObjectAttributes, + namespaces: ['default'], references: [], id: 'nonExistingGroup', error: { @@ -40,6 +41,7 @@ const annotationGroupResolveMocks: Record = dataViewSpec: null, }, type: 'event-annotation-group', + namespaces: ['default'], references: [ { id: 'ipid', @@ -59,6 +61,7 @@ const annotationGroupResolveMocks: Record = }, id: 'multiAnnotations', type: 'event-annotation-group', + namespaces: ['default'], references: [ { id: 'ipid', @@ -76,6 +79,7 @@ const annotationGroupResolveMocks: Record = } as Partial, id: 'multiAnnotations', type: 'event-annotation-group', + namespaces: ['default'], references: [], } as Partial as AnnotationGroupSavedObject, }; From 8911612ebb577781bd2c294f0130035298bc67e0 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 9 Nov 2023 14:34:52 -0500 Subject: [PATCH 17/21] Revert "PoC: Disable delete for shared items" This reverts commit 6fecfef5f01ed381d3ea1697ea98884cf6a05da0. --- .../hooks/use_dashboard_listing_table.tsx | 12 ------------ .../public/components/table_list.tsx | 12 ------------ .../visualize_app/components/visualize_listing.tsx | 13 ------------- .../maps/public/routes/list_page/maps_list_view.tsx | 13 ------------- 4 files changed, 50 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 60bb3c0f181f7..7b7ad32e02add 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -53,17 +53,6 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse }; }; -const disableDeleteForSharedItem = ({ namespaces }: DashboardSavedObjectUserContent) => { - if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { - return { - delete: { - enabled: false, - reason: 'Dashboards shared to other Spaces can not be deleted.', - }, - }; - } -}; - type DashboardListingViewTableProps = Omit< TableListViewTableProps, 'tableCaption' @@ -307,7 +296,6 @@ export const useDashboardListingTable = ({ initialPageSize, listingLimit, onFetchSuccess, - rowItemActions: disableDeleteForSharedItem, setPageDataTestSubject, title, urlStateEnabled, diff --git a/src/plugins/event_annotation_listing/public/components/table_list.tsx b/src/plugins/event_annotation_listing/public/components/table_list.tsx index a8582f1d06999..603d39e193360 100644 --- a/src/plugins/event_annotation_listing/public/components/table_list.tsx +++ b/src/plugins/event_annotation_listing/public/components/table_list.tsx @@ -31,17 +31,6 @@ import { GroupEditorFlyout } from './group_editor_flyout'; export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; -const disableDeleteForSharedItem = ({ namespaces }: EventAnnotationGroupContent) => { - if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { - return { - delete: { - enabled: false, - reason: 'Annotation groups shared to other Spaces can not be deleted.', - }, - }; - } -}; - const getCustomColumn = (dataViews: DataView[]) => { const dataViewNameMap = Object.fromEntries( dataViews.map((dataView) => [dataView.id, dataView.name ?? dataView.title]) @@ -212,7 +201,6 @@ export const EventAnnotationGroupTableList = ({ initialPageSize={initialPageSize} initialFilter={''} customTableColumn={getCustomColumn(dataViews)} - rowItemActions={disableDeleteForSharedItem} emptyPrompt={ { - if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { - return { - delete: { - enabled: false, - reason: 'Visualizations shared to other Spaces can not be deleted.', - }, - }; - } -}; - const toTableListViewSavedObject = (savedObject: Record): VisualizeUserContent => { return { id: savedObject.id as string, @@ -106,7 +95,6 @@ type CustomTableViewProps = Pick< | 'contentEditor' | 'emptyPrompt' | 'itemIsEditable' - | 'rowItemActions' >; const useTableListViewProps = ( @@ -270,7 +258,6 @@ const useTableListViewProps = ( editItem, emptyPrompt: noItemsFragment, createItem: createNewVis, - rowItemActions: disableDeleteForSharedItem, itemIsEditable: ({ attributes: { readOnly } }) => visualizeCapabilities.save && !readOnly, }; diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index 54a7a2cb86a30..8c982a2853708 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -43,22 +43,10 @@ function navigateToNewMap() { }); } -const disableDeleteForSharedItem = ({ namespaces }: MapUserContent) => { - if (namespaces && (namespaces.length > 1 || namespaces.includes('*'))) { - return { - delete: { - enabled: false, - reason: 'Maps shared to other Spaces can not be deleted.', - }, - }; - } -}; - const toTableListViewSavedObject = (mapItem: MapItem): MapUserContent => { return { ...mapItem, updatedAt: mapItem.updatedAt!, - namespaces: mapItem.namespaces!, attributes: { ...mapItem.attributes, title: mapItem.attributes.title ?? '', @@ -150,7 +138,6 @@ function MapsListViewComp({ history }: Props) { entityNamePlural={i18n.translate('xpack.maps.mapListing.entityNamePlural', { defaultMessage: 'maps', })} - rowItemActions={disableDeleteForSharedItem} title={APP_NAME} onClickTitle={({ id }) => history.push(getEditPath(id))} /> From 47e60e1f3395863c973c17412e8d873fedaaa95c Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 9 Nov 2023 16:41:05 -0500 Subject: [PATCH 18/21] Disable Spaces column in Serverless --- .../src/table_list_view_table.tsx | 2 +- .../public/finder/saved_object_finder.tsx | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) 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 2e13c201dc649..4a1f84e2d72b7 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 @@ -534,7 +534,7 @@ function TableListViewTableComp({ columns.push(customTableColumn); } - if (spacesApi) { + if (spacesApi && !spacesApi.hasOnlyDefaultSpace) { columns.push({ field: tableColumnMetadata.spaces.field, name: i18n.translate('contentManagement.tableList.spacesColumnTitle', { diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index 9896a1b2790d6..9102739f7a902 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -225,24 +225,25 @@ export class SavedObjectFinderUi extends React.Component< ['data-test-subj']: 'savedObjectFinderTags', } : undefined; - const spacesColumn: EuiTableFieldDataColumnType | undefined = spaces - ? { - field: 'spaces', - name: i18n.translate('savedObjectsFinder.spacesColumnName', { - defaultMessage: 'Spaces', - }), - width: '20%', - ['data-test-subj']: 'savedObjectFinderSpaces', - render: (_, item) => { - return spaces.ui.components.getSpaceList({ - namespaces: item.namespaces!, - displayLimit: 5, - behaviorContext: 'outside-space', - cursorStyle: 'default', - }); - }, - } - : undefined; + const spacesColumn: EuiTableFieldDataColumnType | undefined = + spaces && !spaces.hasOnlyDefaultSpace + ? { + field: 'spaces', + name: i18n.translate('savedObjectsFinder.spacesColumnName', { + defaultMessage: 'Spaces', + }), + width: '20%', + ['data-test-subj']: 'savedObjectFinderSpaces', + render: (_, item) => { + return spaces.ui.components.getSpaceList({ + namespaces: item.namespaces!, + displayLimit: 5, + behaviorContext: 'outside-space', + cursorStyle: 'default', + }); + }, + } + : undefined; const typeColumn: EuiTableFieldDataColumnType | undefined = savedObjectMetaData.length > 1 ? { From fa86e8a34a193fbc1494c2515ace75f68860151a Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 14 Nov 2023 08:27:25 -0500 Subject: [PATCH 19/21] Fix dashboard backup service --- .../services/dashboard_backup/dashboard_backup_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts b/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts index fb5b2c12f05ee..6f94e9d83506c 100644 --- a/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts +++ b/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts @@ -51,8 +51,8 @@ class DashboardBackupService implements DashboardBackupServiceType { this.localStorage = new Storage(localStorage); this.activeSpaceId = 'default'; - if (this.spaces.getActiveSpace$) { - firstValueFrom(this.spaces.getActiveSpace$()).then((space) => { + if (this.spaces.spacesApi?.getActiveSpace$) { + firstValueFrom(this.spaces.spacesApi.getActiveSpace$()).then((space) => { this.activeSpaceId = space.id; }); } From 7c7e65fcf6fbdfc01dc341d0e8397eabb3562dc7 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 14 Nov 2023 08:28:04 -0500 Subject: [PATCH 20/21] Fix namespace type in annotation group --- .../public/event_annotation_service/service.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 832a1c3db018e..54d0ebcea0d16 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -82,7 +82,7 @@ export function getEventAnnotationService( return { id: savedObject.id, references: savedObject.references, - namespaces: savedObject.namespaces, + namespaces: savedObject.namespaces!, type: savedObject.type, updatedAt: savedObject.updatedAt ? savedObject.updatedAt : '', attributes: { From def3db8be709dad4066deda0c39d3e3bd5930371 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 14 Nov 2023 08:28:46 -0500 Subject: [PATCH 21/21] Add namespaces to Graph table listing --- x-pack/plugins/graph/public/apps/listing_route.tsx | 1 + x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts | 1 + x-pack/plugins/graph/public/types/persistence.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/x-pack/plugins/graph/public/apps/listing_route.tsx b/x-pack/plugins/graph/public/apps/listing_route.tsx index d5e7d2be00967..126bef9ef3d21 100644 --- a/x-pack/plugins/graph/public/apps/listing_route.tsx +++ b/x-pack/plugins/graph/public/apps/listing_route.tsx @@ -28,6 +28,7 @@ const toTableListViewSavedObject = (savedObject: GraphWorkspaceSavedObject): Gra id: savedObject.id!, updatedAt: savedObject.updatedAt!, references: savedObject.references ?? [], + namespaces: savedObject.namespaces!, type: savedObject.type, attributes: { title: savedObject.title, diff --git a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts index 658e2f9f91709..0783363de6428 100644 --- a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts +++ b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts @@ -59,6 +59,7 @@ function mapHits(hit: any, url: string): GraphWorkspaceSavedObject { source.id = hit.id; source.url = url; source.updatedAt = hit.updatedAt; + source.namespaces = hit.namespaces; source.icon = 'cluster'; // maybe there's a better choice here? return source; } diff --git a/x-pack/plugins/graph/public/types/persistence.ts b/x-pack/plugins/graph/public/types/persistence.ts index f5c1366ff8661..3eeb070092e5c 100644 --- a/x-pack/plugins/graph/public/types/persistence.ts +++ b/x-pack/plugins/graph/public/types/persistence.ts @@ -34,6 +34,7 @@ export interface GraphWorkspaceSavedObject { legacyIndexPatternRef?: string; _source: Record; updatedAt?: string; + namespaces?: string[]; references: SavedObjectReference[]; }