Skip to content

Commit

Permalink
Support new page header (#351)
Browse files Browse the repository at this point in the history
* Support new page header

Signed-off-by: Lin Wang <[email protected]>

* Updatenew home page title text

Signed-off-by: Lin Wang <[email protected]>

* Fix unit tests for page header

Signed-off-by: Lin Wang <[email protected]>

---------

Signed-off-by: Lin Wang <[email protected]>
  • Loading branch information
wanglam authored Aug 19, 2024
1 parent 6c280ae commit 63326ee
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 25 deletions.
1 change: 1 addition & 0 deletions public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const renderApp = (
setActionMenu={setHeaderActionMenu}
dataSource={services.dataSource}
dataSourceManagement={services.dataSourceManagement}
application={services.application}
/>
</services.i18n.Context>
</OpenSearchDashboardsContextProvider>
Expand Down
19 changes: 17 additions & 2 deletions public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -37,6 +38,7 @@ interface MlCommonsPluginAppDeps {
dataSource?: DataSourcePluginSetup;
dataSourceManagement?: DataSourceManagementPluginSetup;
setActionMenu: (menuMount: MountPoint | undefined) => void;
application: CoreStart['application'];
}

export interface ComponentsCommonProps {
Expand All @@ -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 (
<I18nProvider>
<DataSourceContextProvider
Expand All @@ -73,7 +79,15 @@ export const MlCommonsPluginApp = ({
key={path}
path={path}
render={() => (
<Component http={http} notifications={notifications} data={data} />
<Component
http={http}
notifications={notifications}
chrome={chrome}
data={data}
navigation={navigation}
useNewPageHeader={useNewPageHeader}
application={application}
/>
)}
exact={exact ?? false}
/>
Expand All @@ -82,7 +96,8 @@ export const MlCommonsPluginApp = ({
</Switch>
</EuiPageBody>
</EuiPage>
<GlobalBreadcrumbs chrome={chrome} basename={basename} />
{/* Breadcrumbs will contains dynamic content in new page header, should be provided by each page self*/}
{!useNewPageHeader && <GlobalBreadcrumbs chrome={chrome} basename={basename} />}
{dataSourceEnabled && (
<DataSourceTopNavMenu
notifications={notifications}
Expand Down
24 changes: 22 additions & 2 deletions public/components/monitoring/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { render, screen, waitFor, within } from '../../../../test/test_utils';
import { Monitoring } from '../index';
import * as useMonitoringExports from '../use_monitoring';
import { APIProvider } from '../../../apis/api_provider';
import { applicationServiceMock, chromeServiceMock } from '../../../../../../src/core/public/mocks';
import { navigationPluginMock } from '../../../../../../src/plugins/navigation/public/mocks';

jest.mock('../../../../../../src/plugins/opensearch_dashboards_react/public', () => {
return {
Expand All @@ -18,7 +20,8 @@ jest.mock('../../../../../../src/plugins/opensearch_dashboards_react/public', ()
});

const setup = (
monitoringReturnValue?: Partial<ReturnType<typeof useMonitoringExports.useMonitoring>>
monitoringReturnValue?: Partial<ReturnType<typeof useMonitoringExports.useMonitoring>>,
useNewPageHeader = false
) => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
const finalMonitoringReturnValue = {
Expand Down Expand Up @@ -85,7 +88,18 @@ const setup = (
...monitoringReturnValue,
} as ReturnType<typeof useMonitoringExports.useMonitoring>;
jest.spyOn(useMonitoringExports, 'useMonitoring').mockReturnValue(finalMonitoringReturnValue);
render(<Monitoring />);
const applicationStartMock = applicationServiceMock.createStartContract();
const chromeStartMock = chromeServiceMock.createStartContract();
const navigationStartMock = navigationPluginMock.createStartContract();
navigationStartMock.ui.HeaderControl = () => null;
render(
<Monitoring
application={applicationStartMock}
chrome={chromeStartMock}
navigation={navigationStartMock}
useNewPageHeader={useNewPageHeader}
/>
);
return { finalMonitoringReturnValue, user };
};

Expand Down Expand Up @@ -382,4 +396,10 @@ describe('<Monitoring />', () => {
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);
});
});
Original file line number Diff line number Diff line change
@@ -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<MonitoringPageHeaderProps>) {
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(
<MonitoringPageHeader
navigation={navigationStartMock}
application={applicationStartMock}
setBreadcrumbs={setBreadcrumbsMock}
onRefresh={onRefreshMock}
useNewPageHeader={true}
{...options}
/>
);

return {
user,
renderResult,
setBreadcrumbsMock,
onRefreshMock,
applicationStartMock,
navigationStartMock,
};
}

describe('<MonitoringPageHeader />', () => {
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();
});
});
61 changes: 40 additions & 21 deletions public/components/monitoring/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import {
EuiPanel,
EuiPageHeader,
EuiSpacer,
EuiTextColor,
EuiFlexGroup,
Expand All @@ -14,19 +13,30 @@ 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';
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,
Expand Down Expand Up @@ -83,25 +93,34 @@ export const Monitoring = () => {
);

return (
<div>
<EuiSpacer size="s" />
<EuiSpacer size="xs" />
<EuiPageHeader
pageTitle="Overview"
rightSideItems={[<RefreshInterval onRefresh={reload} />]}
<>
<MonitoringPageHeader
onRefresh={reload}
navigation={navigation}
setBreadcrumbs={chrome.setBreadcrumbs}
recordsCount={pagination?.totalRecords}
application={application}
useNewPageHeader={useNewPageHeader}
/>
<EuiSpacer size="m" />
<EuiPanel>
<EuiText size="s">
<h2>
Models{' '}
{pageStatus !== 'empty' && (
<EuiTextColor aria-label="total number of results" color="subdued">
({pagination?.totalRecords ?? 0})
</EuiTextColor>
)}
</h2>
</EuiText>
{!useNewPageHeader && (
<EuiText size="s">
<h2>
<FormattedMessage
id="machineLearning.aiModels.table.header.title"
defaultMessage="Models {records}"
values={{
records:
pageStatus === 'normal' ? (
<EuiTextColor aria-label="total number of results" color="subdued">
({pagination?.totalRecords ?? 0})
</EuiTextColor>
) : undefined,
}}
/>
</h2>
</EuiText>
)}

<EuiSpacer size="m" />
{pageStatus !== 'empty' && (
Expand Down Expand Up @@ -145,6 +164,6 @@ export const Monitoring = () => {
/>
)}
</EuiPanel>
</div>
</>
);
};
75 changes: 75 additions & 0 deletions public/components/monitoring/monitoring_page_header.tsx
Original file line number Diff line number Diff line change
@@ -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: <RefreshInterval onRefresh={onRefresh} />,
},
];
}
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 <HeaderControl setMountPoint={setAppRightControls} controls={controls} />;
}
return (
<>
<EuiSpacer size="s" />
<EuiSpacer size="xs" />
<EuiPageHeader
pageTitle="Overview"
rightSideItems={[<RefreshInterval onRefresh={onRefresh} />]}
/>
<EuiSpacer size="m" />
</>
);
};

0 comments on commit 63326ee

Please sign in to comment.