diff --git a/CHANGELOG.md b/CHANGELOG.md index e05b727b0d50..c9786371a782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fix Node.js download link ([#4556](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4556)) - [TSVB, Dashboards] Fix inconsistent dark mode code editor themes ([#4609](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4609)) - [Legacy Maps] Fix dark mode style overrides ([#4658](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4658)) +- [BUG] Fix management overview page duplicate rendering ([#4636](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4636)) ### 🚞 Infrastructure @@ -760,4 +761,4 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🔩 Tests -- Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) +- Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) \ No newline at end of file diff --git a/src/plugins/management_overview/public/__snapshots__/application.test.tsx.snap b/src/plugins/management_overview/public/__snapshots__/application.test.tsx.snap new file mode 100644 index 000000000000..666f1b50c696 --- /dev/null +++ b/src/plugins/management_overview/public/__snapshots__/application.test.tsx.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Overview page rendering should render normally 1`] = ` +
+
+

+ + Overview + +

+
+
+
+
+
+

+ +

+
+

+ dev tools description +

+
+
+
+
+
+
+
+`; diff --git a/src/plugins/management_overview/public/application.test.tsx b/src/plugins/management_overview/public/application.test.tsx new file mode 100644 index 000000000000..3f879f05b5da --- /dev/null +++ b/src/plugins/management_overview/public/application.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from '@testing-library/react'; +import { ManagementOverviewWrapper } from './application'; +import React from 'react'; +import { ApplicationStart, PublicAppInfo } from 'opensearch-dashboards/public'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { deepFreeze } from '@osd/std'; +import { OverviewApp } from './overview_app'; +import { AppNavLinkStatus, AppStatus } from '../../../core/public'; + +const applicationStartMock = (apps: Map): jest.Mocked => { + const currentAppId$ = new Subject(); + + return { + applications$: new BehaviorSubject>(apps), + currentAppId$: currentAppId$.asObservable(), + capabilities: deepFreeze({ + catalogue: {}, + management: {}, + navLinks: {}, + }), + navigateToApp: jest.fn(), + navigateToUrl: jest.fn(), + getUrlForApp: jest.fn(), + registerMountContext: jest.fn(), + }; +}; + +function renderOverviewPage(apps: Map, overviewApps?: OverviewApp[]) { + return render( + + ); +} + +describe('Overview page rendering', () => { + it('should render normally', () => { + const overviewApps: OverviewApp[] = [ + { + id: 'dev_tools', + title: 'Dev Tools', + description: 'dev tools description', + order: 0, + }, + ]; + + const apps: Map = new Map(); + apps.set('dev_tools', { + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: '/app/console', + } as PublicAppInfo); + const { container, queryByText } = renderOverviewPage(apps, overviewApps); + expect(container.firstChild).toMatchSnapshot(); + expect(queryByText('Dev Tools')).not.toBeNull(); + }); + + it('should render normally when no overview app', () => { + const { queryByText } = renderOverviewPage(new Map()); + expect(queryByText('Overview')).not.toBeNull(); + }); + + it('should render normally when no application available', () => { + const overviewApps: OverviewApp[] = [ + { + id: 'dev_tools', + title: 'Dev Tools', + description: 'dev tools description', + order: 0, + }, + ]; + const { queryByText } = renderOverviewPage(new Map(), overviewApps); + expect(queryByText('Dev Tools')).toBeNull(); + }); + + it('should not display overview app when nav link status is hidden', () => { + const overviewApps: OverviewApp[] = [ + { + id: 'dev_tools', + title: 'Dev Tools', + description: 'dev tools description', + order: 0, + }, + ]; + + const apps: Map = new Map(); + apps.set('dev_tools', { + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.hidden, + appRoute: '/app/console', + } as PublicAppInfo); + const { queryByText } = renderOverviewPage(apps, overviewApps); + expect(queryByText('Dev Tools')).toBeNull(); + }); + + it('should not display overview app when it is invalid app', () => { + const overviewApps: OverviewApp[] = [ + { + id: 'invalid_app_id', + title: 'Dev Tools', + description: 'dev tools description', + order: 0, + }, + ]; + + const apps: Map = new Map(); + apps.set('dev_tools', { + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.hidden, + appRoute: '/app/console', + } as PublicAppInfo); + const { queryByText } = renderOverviewPage(apps, overviewApps); + expect(queryByText('Dev Tools')).toBeNull(); + }); +}); diff --git a/src/plugins/management_overview/public/application.tsx b/src/plugins/management_overview/public/application.tsx index 26ee63e78252..805c43081fdd 100644 --- a/src/plugins/management_overview/public/application.tsx +++ b/src/plugins/management_overview/public/application.tsx @@ -5,28 +5,28 @@ import ReactDOM from 'react-dom'; import { I18nProvider, FormattedMessage } from '@osd/i18n/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiFlexGrid, EuiFlexItem, EuiPage, EuiPageBody, EuiSpacer, EuiTitle } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; -import { ApplicationStart, ChromeStart, CoreStart } from '../../../core/public'; +import { ApplicationStart, AppNavLinkStatus, CoreStart } from '../../../core/public'; import { OverviewApp } from '.'; import { OverviewCard } from './components/overview_card'; export interface ManagementOverviewProps { application: ApplicationStart; - chrome: ChromeStart; overviewApps?: OverviewApp[]; } -function ManagementOverviewWrapper(props: ManagementOverviewProps) { - const { chrome, application, overviewApps } = props; +export function ManagementOverviewWrapper(props: ManagementOverviewProps) { + const { application, overviewApps } = props; + const applications = useObservable(application.applications$); - const hiddenAppIds = - useObservable(chrome.navLinks.getNavLinks$()) - ?.filter((link) => link.hidden) - .map((link) => link.id) || []; - - const availableApps = overviewApps?.filter((app) => hiddenAppIds.indexOf(app.id) === -1); + const availableApps = useMemo(() => { + return overviewApps?.filter((overviewApp) => { + const app = applications?.get(overviewApp.id); + return app && app.navLinkStatus !== AppNavLinkStatus.hidden; + }); + }, [applications, overviewApps]); return ( @@ -61,11 +61,7 @@ export function renderApp( ) { ReactDOM.render( - + , element ); diff --git a/src/plugins/management_overview/public/plugin.ts b/src/plugins/management_overview/public/plugin.ts index c1b77be0f795..f11b58ef9b41 100644 --- a/src/plugins/management_overview/public/plugin.ts +++ b/src/plugins/management_overview/public/plugin.ts @@ -22,8 +22,13 @@ interface ManagementOverviewSetupDeps { export interface ManagementOverViewPluginSetup { register: (overviewApp: OverviewApp) => void; } + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ManagementOverViewPluginStart {} + /** @public */ -export class ManagementOverViewPlugin implements Plugin { +export class ManagementOverViewPlugin + implements Plugin { private readonly overviewApps = new Map(); private getSortedOverviewApps(): OverviewApp[] { @@ -82,5 +87,7 @@ export class ManagementOverViewPlugin implements Plugin