diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.tsx index c4133a3e6c6d..bbf247087f87 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.tsx @@ -52,6 +52,10 @@ const TopNav = ({ const [isFullScreenMode, setIsFullScreenMode] = useState(); const { services } = useOpenSearchDashboards(); + console.log('TopNav: Services:', services); + console.log('TopNav: Navigation service:', services.navigation); + console.log('TopNav: Navigation UI:', services.navigation?.ui); + console.log('TopNav: enum:', TopNavMenuItemRenderType); const { TopNavMenu, HeaderControl } = services.navigation.ui; const { dashboardConfig, setHeaderActionMenu } = services; const { setAppRightControls } = services.application; @@ -151,7 +155,8 @@ const TopNav = ({ defaultMessage: 'New dashboard', }) } - showSearchBar={showSearchBar && TopNavMenuItemRenderType.IN_PORTAL} + // showSearchBar={showSearchBar && TopNavMenuItemRenderType.IN_PORTAL} + showSearchBar={showSearchBar} showQueryBar={showQueryBar} showQueryInput={showQueryInput} showDatePicker={showDatePicker} diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index d994e98142db..8b9be393c292 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -104,6 +104,8 @@ export interface DashboardContainerOptions { SavedObjectFinder: React.ComponentType; ExitFullScreenButton: React.ComponentType; uiActions: UiActionsStart; + savedObjectsClient: CoreStart['savedObjects']['client']; + http: CoreStart['http']; } export type DashboardReactContextValue = OpenSearchDashboardsReactContextValue< @@ -244,6 +246,9 @@ export class DashboardContainer extends Container , diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 60a3dba7384b..df110c6d7f5c 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -30,17 +30,30 @@ import React from 'react'; import { Subscription } from 'rxjs'; -import { Logos } from 'opensearch-dashboards/public'; -import { PanelState, EmbeddableStart } from '../../../../../embeddable/public'; +import { + Logos, + SavedObjectsClientContract, + HttpStart, + NotificationsStart, +} from 'opensearch-dashboards/public'; +import { EmbeddableStart } from '../../../../../embeddable/public'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardGrid } from '../grid'; import { context } from '../../../../../opensearch_dashboards_react/public'; +import { + DashboardExtensions, + getDashboardExtensions, +} from '../../../ui/dashboard_extensions/dashboard_extensions'; +import { DashboardPanelState } from '../types'; export interface DashboardViewportProps { container: DashboardContainer; PanelComponent: EmbeddableStart['EmbeddablePanel']; renderEmpty?: () => React.ReactNode; logos: Logos; + savedObjectsClient: SavedObjectsClientContract; + http: HttpStart; + notifications: NotificationsStart; } interface State { @@ -48,7 +61,7 @@ interface State { useMargins: boolean; title: string; description?: string; - panels: { [key: string]: PanelState }; + panels: { [key: string]: DashboardPanelState }; isEmbeddedExternally?: boolean; isEmptyState?: boolean; } @@ -88,6 +101,7 @@ export class DashboardViewport extends React.Component )} + {/* Render dashboard extensions above the grid */} + ); diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 7a7f1d4ffd2d..36fd5b79a3ef 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -58,6 +58,15 @@ export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; +// Re-export dashboard extensions for use by other plugins +export { + DashboardExtensionConfig, + DashboardExtensionDependencies, + DashboardExtensions, + registerDashboardExtension, + getDashboardExtensions, +} from './ui/dashboard_extensions/dashboard_extensions'; + export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); } diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 55ed9a02d24d..ee5cf22cd289 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -276,6 +276,8 @@ export class DashboardPlugin SavedObjectFinder: getSavedObjectFinder(coreStart.savedObjects, coreStart.uiSettings), ExitFullScreenButton, uiActions: deps.uiActions, + savedObjectsClient: coreStart.savedObjects.client, + http: coreStart.http, }; }; diff --git a/src/plugins/dashboard/public/ui/dashboard_extensions/dashboard_extensions.tsx b/src/plugins/dashboard/public/ui/dashboard_extensions/dashboard_extensions.tsx new file mode 100644 index 000000000000..bd04c927f3d5 --- /dev/null +++ b/src/plugins/dashboard/public/ui/dashboard_extensions/dashboard_extensions.tsx @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiErrorBoundary } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { + HttpStart, + NotificationsStart, + SavedObjectsClientContract, +} from 'opensearch-dashboards/public'; +import { DashboardPanelState } from '../../application/embeddable/types'; + +// Dependencies provided to dashboard extensions +export interface DashboardExtensionDependencies { + http: HttpStart; + notifications: NotificationsStart; + savedObjectsClient: SavedObjectsClientContract; + panels: { [key: string]: DashboardPanelState }; +} + +// Configuration for a dashboard extension +export interface DashboardExtensionConfig { + id: string; // Unique identifier for the extension + order: number; // Lower order means higher position in the UI + isEnabled: () => Promise; // Determines if the extension should be rendered + getComponent: (dependencies: DashboardExtensionDependencies) => React.ReactElement; // Returns the component to render +} + +// Registry to store dashboard extensions +const dashboardExtensions: DashboardExtensionConfig[] = []; + +// Public method to register a dashboard extension +export function registerDashboardExtension(config: DashboardExtensionConfig) { + dashboardExtensions.push(config); +} + +// Getter to access the registered extensions (used internally by DashboardViewport) +export function getDashboardExtensions(): DashboardExtensionConfig[] { + return dashboardExtensions; +} + +interface DashboardExtensionProps { + config: DashboardExtensionConfig; + dependencies: DashboardExtensionDependencies; +} + +// Component to render a single dashboard extension +const DashboardExtension: React.FC = (props) => { + const [isEnabled, setIsEnabled] = React.useState(false); + + const component = useMemo(() => props.config.getComponent(props.dependencies), [ + props.config, + props.dependencies, + ]); + + React.useEffect(() => { + props.config.isEnabled().then(setIsEnabled); + }, [props.config]); + + if (!isEnabled) return null; + + return {component}; +}; + +interface DashboardExtensionsProps { + configs?: DashboardExtensionConfig[]; + dependencies: DashboardExtensionDependencies; +} + +// Component to render all registered dashboard extensions +export const DashboardExtensions: React.FC = (props) => { + const configs = useMemo(() => { + if (!props.configs) return []; + + const seenIds = new Set(); + props.configs.forEach((config) => { + if (seenIds.has(config.id)) { + throw new Error(`Duplicate dashboard extension id '${config.id}' found.`); + } + seenIds.add(config.id); + }); + + return [...props.configs].sort((a, b) => a.order - b.order); + }, [props.configs]); + + return ( + <> + {configs.map((config) => ( + + ))} + + ); +}; diff --git a/src/plugins/data_source_management/opensearch_dashboards.json b/src/plugins/data_source_management/opensearch_dashboards.json index 86f5d0b5d11f..e4f1f7480475 100644 --- a/src/plugins/data_source_management/opensearch_dashboards.json +++ b/src/plugins/data_source_management/opensearch_dashboards.json @@ -3,7 +3,7 @@ "version": "opensearchDashboards", "server": true, "ui": true, - "requiredPlugins": ["navigation", "management", "indexPatternManagement"], + "requiredPlugins": ["navigation", "management", "indexPatternManagement", "dashboard"], "optionalPlugins": ["dataSource"], "requiredBundles": ["opensearchDashboardsReact", "dataSource", "opensearchDashboardsUtils"], "extraPublicDirs": ["public/components/utils"], diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_sync/test_dashboard_extension.tsx b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_sync/test_dashboard_extension.tsx new file mode 100644 index 000000000000..14f1a4ce09bf --- /dev/null +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_sync/test_dashboard_extension.tsx @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { DashboardExtensionDependencies } from 'src/plugins/dashboard/public'; + +interface TestDashboardExtensionProps { + dependencies: DashboardExtensionDependencies; +} + +export const TestDashboardExtension: React.FC = ({ dependencies }) => { + // Log the dependencies for debugging + console.log('TestDashboardExtension: Dependencies:', { + hasHttp: !!dependencies.http, + hasNotifications: !!dependencies.notifications, + hasSavedObjectsClient: !!dependencies.savedObjectsClient, + panelCount: Object.keys(dependencies.panels).length, + }); + + return ( +
+

Test Dashboard Extension

+

Hello World from Data Source Management Plugin!

+
+ ); +}; diff --git a/src/plugins/data_source_management/public/components/direct_query_sync/test_dashboard_extension.tsx b/src/plugins/data_source_management/public/components/direct_query_sync/test_dashboard_extension.tsx new file mode 100644 index 000000000000..14f1a4ce09bf --- /dev/null +++ b/src/plugins/data_source_management/public/components/direct_query_sync/test_dashboard_extension.tsx @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { DashboardExtensionDependencies } from 'src/plugins/dashboard/public'; + +interface TestDashboardExtensionProps { + dependencies: DashboardExtensionDependencies; +} + +export const TestDashboardExtension: React.FC = ({ dependencies }) => { + // Log the dependencies for debugging + console.log('TestDashboardExtension: Dependencies:', { + hasHttp: !!dependencies.http, + hasNotifications: !!dependencies.notifications, + hasSavedObjectsClient: !!dependencies.savedObjectsClient, + panelCount: Object.keys(dependencies.panels).length, + }); + + return ( +
+

Test Dashboard Extension

+

Hello World from Data Source Management Plugin!

+
+ ); +}; diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts index 0a388e6348d9..b1cc1781a1e9 100644 --- a/src/plugins/data_source_management/public/plugin.ts +++ b/src/plugins/data_source_management/public/plugin.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { useObservable } from 'react-use'; + import React from 'react'; import { i18n } from '@osd/i18n'; import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; @@ -15,6 +17,8 @@ import { MountPoint, Plugin, } from '../../../core/public'; +import { registerDashboardExtension, DashboardExtensionDependencies } from '../../dashboard/public'; +import { TestDashboardExtension } from './components/direct_query_data_sources_components/direct_query_sync/test_dashboard_extension'; import { PLUGIN_NAME } from '../common'; import { createDataSourceSelector } from './components/data_source_selector/create_data_source_selector'; @@ -110,6 +114,11 @@ export class DataSourceManagementPlugin private authMethodsRegistry = new AuthenticationMethodRegistry(); private dataSourceSelection = new DataSourceSelectionService(); private featureFlagStatus: boolean = false; + + private getTestComponent(dependencies: DashboardExtensionDependencies): React.ReactElement { + return React.createElement(TestDashboardExtension, { dependencies }); + } + public setup( core: CoreSetup, { management, indexPatternManagement, dataSource }: DataSourceManagementSetupDependencies @@ -211,6 +220,15 @@ export class DataSourceManagementPlugin // This instance will be got in each data source selector component. setDataSourceSelection(this.dataSourceSelection); + // Register the test dashboard extension + console.log('DataSourceManagementPlugin: Registering test dashboard extension'); + registerDashboardExtension({ + id: 'test-dashboard-extension', + order: 1, + isEnabled: async () => Promise.resolve(true), // Always enabled for testing + getComponent: (dependencies) => this.getTestComponent(dependencies), + }); + return { registerAuthenticationMethod, // Other plugins can get this instance from setupDeps and use to get selected data sources.