diff --git a/public/application.tsx b/public/application.tsx index 149b83b6..94431f69 100644 --- a/public/application.tsx +++ b/public/application.tsx @@ -35,6 +35,7 @@ export const renderApp = ( setActionMenu={setHeaderActionMenu} dataSource={services.dataSource} dataSourceManagement={services.dataSourceManagement} + application={services.application} /> diff --git a/public/components/app.tsx b/public/components/app.tsx index e8d38fb6..892d475a 100644 --- a/public/components/app.tsx +++ b/public/components/app.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { I18nProvider } from '@osd/i18n/react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { EuiPage, EuiPageBody } from '@elastic/eui'; +import { useObservable } from 'react-use'; import { ROUTES } from '../../common/router'; import { routerPaths } from '../../common/router_paths'; @@ -37,6 +38,7 @@ interface MlCommonsPluginAppDeps { dataSource?: DataSourcePluginSetup; dataSourceManagement?: DataSourceManagementPluginSetup; setActionMenu: (menuMount: MountPoint | undefined) => void; + application: CoreStart['application']; } export interface ComponentsCommonProps { @@ -55,8 +57,12 @@ export const MlCommonsPluginApp = ({ dataSourceManagement, savedObjects, setActionMenu, + navigation, + uiSettingsClient, + application, }: MlCommonsPluginAppDeps) => { const dataSourceEnabled = !!dataSource; + const useNewPageHeader = useObservable(uiSettingsClient.get$('home:useNewHomePage')); return ( ( - + )} exact={exact ?? false} /> @@ -82,7 +96,8 @@ export const MlCommonsPluginApp = ({ - + {/* Breadcrumbs will contains dynamic content in new page header, should be provided by each page self*/} + {!useNewPageHeader && } {dataSourceEnabled && ( { return { @@ -18,7 +20,8 @@ jest.mock('../../../../../../src/plugins/opensearch_dashboards_react/public', () }); const setup = ( - monitoringReturnValue?: Partial> + monitoringReturnValue?: Partial>, + useNewPageHeader = false ) => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); const finalMonitoringReturnValue = { @@ -85,7 +88,18 @@ const setup = ( ...monitoringReturnValue, } as ReturnType; jest.spyOn(useMonitoringExports, 'useMonitoring').mockReturnValue(finalMonitoringReturnValue); - render(); + const applicationStartMock = applicationServiceMock.createStartContract(); + const chromeStartMock = chromeServiceMock.createStartContract(); + const navigationStartMock = navigationPluginMock.createStartContract(); + navigationStartMock.ui.HeaderControl = () => null; + render( + + ); return { finalMonitoringReturnValue, user }; }; @@ -382,4 +396,10 @@ describe('', () => { await user.click(screen.getByLabelText('Close this dialog')); expect(reload).not.toHaveBeenCalled(); }); + + it('should NOT render table header title if useNewPageHeader equal true', () => { + setup({}, true); + + expect(screen.queryByLabelText('total number of results')).toBe(null); + }); }); diff --git a/public/components/monitoring/__tests__/monitoring_page_header.test.tsx b/public/components/monitoring/__tests__/monitoring_page_header.test.tsx new file mode 100644 index 00000000..a0048260 --- /dev/null +++ b/public/components/monitoring/__tests__/monitoring_page_header.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { applicationServiceMock } from '../../../../../../src/core/public/mocks'; +import { navigationPluginMock } from '../../../../../../src/plugins/navigation/public/mocks'; + +import { render, screen } from '../../../../test/test_utils'; +import { MonitoringPageHeader, MonitoringPageHeaderProps } from '../monitoring_page_header'; + +jest.mock('../../../apis/connector'); + +async function setup(options: Partial) { + const setBreadcrumbsMock = jest.fn(); + const onRefreshMock = jest.fn(); + const applicationStartMock = applicationServiceMock.createStartContract(); + const navigationStartMock = navigationPluginMock.createStartContract(); + const user = userEvent.setup({}); + + navigationStartMock.ui.HeaderControl = ({ controls }) => { + return controls?.[0].renderComponent ?? null; + }; + + const renderResult = render( + + ); + + return { + user, + renderResult, + setBreadcrumbsMock, + onRefreshMock, + applicationStartMock, + navigationStartMock, + }; +} + +describe('', () => { + it('should old page header and refresh button when usePageHeader is false', async () => { + await setup({ + useNewPageHeader: false, + }); + expect(screen.getByText('Overview')).toBeInTheDocument(); + expect(screen.getByLabelText('set refresh interval')).toBeInTheDocument(); + }); + + it('should set breadcrumbs and render refresh button', async () => { + const { setBreadcrumbsMock } = await setup({ + useNewPageHeader: true, + recordsCount: 2, + }); + + expect(setBreadcrumbsMock).toHaveBeenCalledWith([ + { + text: 'AI Models(2)', + }, + ]); + expect(screen.getByLabelText('set refresh interval')).toBeInTheDocument(); + }); +}); diff --git a/public/components/monitoring/index.tsx b/public/components/monitoring/index.tsx index 83e755e7..ca28790b 100644 --- a/public/components/monitoring/index.tsx +++ b/public/components/monitoring/index.tsx @@ -5,7 +5,6 @@ import { EuiPanel, - EuiPageHeader, EuiSpacer, EuiTextColor, EuiFlexGroup, @@ -14,10 +13,12 @@ import { EuiFilterGroup, } from '@elastic/eui'; import React, { useState, useRef, useCallback } from 'react'; +import { FormattedMessage } from '@osd/i18n/react'; import { ModelDeploymentProfile } from '../../apis/profile'; -import { RefreshInterval } from '../common/refresh_interval'; import { PreviewPanel } from '../preview_panel'; +import { ApplicationStart, ChromeStart } from '../../../../../src/core/public'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; import { ModelDeploymentItem, ModelDeploymentTable } from './model_deployment_table'; import { useMonitoring } from './use_monitoring'; @@ -25,8 +26,17 @@ import { ModelStatusFilter } from './model_status_filter'; import { SearchBar } from './search_bar'; import { ModelSourceFilter } from './model_source_filter'; import { ModelConnectorFilter } from './model_connector_filter'; +import { MonitoringPageHeader } from './monitoring_page_header'; -export const Monitoring = () => { +interface MonitoringProps { + chrome: ChromeStart; + navigation: NavigationPublicPluginStart; + application: ApplicationStart; + useNewPageHeader: boolean; +} + +export const Monitoring = (props: MonitoringProps) => { + const { useNewPageHeader, chrome, application, navigation } = props; const { pageStatus, params, @@ -83,25 +93,34 @@ export const Monitoring = () => { ); return ( -
- - - ]} + <> + - - -

- Models{' '} - {pageStatus !== 'empty' && ( - - ({pagination?.totalRecords ?? 0}) - - )} -

-
+ {!useNewPageHeader && ( + +

+ + ({pagination?.totalRecords ?? 0}) + + ) : undefined, + }} + /> +

+
+ )} {pageStatus !== 'empty' && ( @@ -145,6 +164,6 @@ export const Monitoring = () => { /> )}
-
+ ); }; diff --git a/public/components/monitoring/monitoring_page_header.tsx b/public/components/monitoring/monitoring_page_header.tsx new file mode 100644 index 00000000..00c0537e --- /dev/null +++ b/public/components/monitoring/monitoring_page_header.tsx @@ -0,0 +1,75 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React, { useEffect, useMemo } from 'react'; +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; + +import type { ChromeBreadcrumb } from 'opensearch-dashboards/public'; + +import { RefreshInterval } from '../common/refresh_interval'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; +import { ApplicationStart } from '../../../../../src/core/public'; + +export interface MonitoringPageHeaderProps { + navigation: NavigationPublicPluginStart; + application: ApplicationStart; + onRefresh: () => void; + recordsCount?: number; + setBreadcrumbs: (breadcrumbs: ChromeBreadcrumb[]) => void; + useNewPageHeader: boolean; +} + +export const MonitoringPageHeader = ({ + onRefresh, + navigation, + recordsCount, + setBreadcrumbs, + application, + useNewPageHeader, +}: MonitoringPageHeaderProps) => { + const { HeaderControl } = navigation.ui; + const { setAppRightControls } = application; + const controls = useMemo(() => { + if (useNewPageHeader) { + return [ + { + renderComponent: , + }, + ]; + } + return []; + }, [useNewPageHeader, onRefresh]); + + useEffect(() => { + if (useNewPageHeader) { + setBreadcrumbs([ + { + text: i18n.translate('machineLearning.AIModels.page.title', { + defaultMessage: + 'AI Models{recordsCount, select, undefined {} other {({recordsCount})}}', + values: { + recordsCount, + }, + }), + }, + ]); + } + }, [useNewPageHeader, recordsCount, setBreadcrumbs]); + + if (useNewPageHeader) { + return ; + } + return ( + <> + + + ]} + /> + + + ); +};