From 8fb8feda667d0b5bee34ee8552556ac02b4e48a4 Mon Sep 17 00:00:00 2001 From: Jincheng Wan <45655760+Kapian1234@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:43:23 +0800 Subject: [PATCH 1/2] [Worksapce] Hide delete button for non osd admins in workspace list (#8315) * hide delete_workspace button for non osd admins in workspace list Signed-off-by: Kapian1234 * Changeset file for PR #8315 created/updated * Disable selection of workspaces for non osd admins Signed-off-by: Kapian1234 --------- Signed-off-by: Kapian1234 Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/8315.yml | 2 ++ .../components/workspace_list/index.tsx | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/8315.yml diff --git a/changelogs/fragments/8315.yml b/changelogs/fragments/8315.yml new file mode 100644 index 000000000000..77f370408739 --- /dev/null +++ b/changelogs/fragments/8315.yml @@ -0,0 +1,2 @@ +fix: +- Hide delete button for non osd admins in workspace list ([#8315](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8315)) \ No newline at end of file diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 8366c240cca3..68b4e2acc886 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -317,17 +317,19 @@ export const WorkspaceListInner = ({ }; return ( - <> - - Delete {selection.length} Workspace - - {deletedWorkspaces && deletedWorkspaces.length > 0 && ( - setDeletedWorkspaces([])} - /> - )} - + isDashboardAdmin && ( + <> + + Delete {selection.length} Workspace + + {deletedWorkspaces && deletedWorkspaces.length > 0 && ( + setDeletedWorkspaces([])} + /> + )} + + ) ); }; @@ -601,9 +603,9 @@ export const WorkspaceListInner = ({ direction: initialSortDirection, }, }} - isSelectable={selectable} + isSelectable={selectable && !!isDashboardAdmin} search={searchable ? search : undefined} - selection={selectable ? selectionValue : undefined} + selection={selectable && !!isDashboardAdmin ? selectionValue : undefined} /> ); From c1a349a560e9ae036183e22a97631685e8bb06af Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Sun, 29 Sep 2024 17:49:49 +0800 Subject: [PATCH 2/2] Update sample data page UI when useUpdatedUX enabled (#8291) * Update sample data page with latest UI when useUpdatedUX enabled Signed-off-by: Lin Wang * Fix crashed in home page Signed-off-by: Lin Wang * Changeset file for PR #8291 created/updated * Reduce spacing between sample data cards Signed-off-by: Lin Wang --------- Signed-off-by: Lin Wang Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/8291.yml | 2 + src/plugins/home/opensearch_dashboards.json | 2 +- .../home/public/application/application.tsx | 20 +++-- .../components/sample_data_set_cards.js | 12 ++- .../components/tutorial_directory.js | 86 ++++++++++++++++--- .../components/tutorial_directory.test.tsx | 86 +++++++++++++++---- src/plugins/home/public/plugin.ts | 22 ++++- 7 files changed, 190 insertions(+), 40 deletions(-) create mode 100644 changelogs/fragments/8291.yml diff --git a/changelogs/fragments/8291.yml b/changelogs/fragments/8291.yml new file mode 100644 index 000000000000..77341a36410b --- /dev/null +++ b/changelogs/fragments/8291.yml @@ -0,0 +1,2 @@ +feat: +- Update sample data page UI when useUpdatedUX enabled ([#8291](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8291)) \ No newline at end of file diff --git a/src/plugins/home/opensearch_dashboards.json b/src/plugins/home/opensearch_dashboards.json index e96b2bee998d..2436e472a3d1 100644 --- a/src/plugins/home/opensearch_dashboards.json +++ b/src/plugins/home/opensearch_dashboards.json @@ -3,7 +3,7 @@ "version": "opensearchDashboards", "server": true, "ui": true, - "requiredPlugins": ["data", "urlForwarding", "savedObjects", "contentManagement"], + "requiredPlugins": ["data", "urlForwarding", "savedObjects", "contentManagement", "navigation"], "optionalPlugins": ["usageCollection", "telemetry", "dataSource"], "requiredBundles": ["opensearchDashboardsReact", "dataSourceManagement"] } diff --git a/src/plugins/home/public/application/application.tsx b/src/plugins/home/public/application/application.tsx index fae0dbdd56e5..f042957eb975 100644 --- a/src/plugins/home/public/application/application.tsx +++ b/src/plugins/home/public/application/application.tsx @@ -31,8 +31,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@osd/i18n'; -import { ScopedHistory, CoreStart } from 'opensearch-dashboards/public'; +import { ScopedHistory, CoreStart, MountPoint } from 'opensearch-dashboards/public'; import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; +import { NavigationPublicPluginStart } from '../../../navigation/public'; // @ts-ignore import { HomeApp, ImportSampleDataApp } from './components/home_app'; import { getServices } from './opensearch_dashboards_services'; @@ -43,7 +44,10 @@ import { SearchUseCaseOverviewApp } from './components/usecase_overview/search_u export const renderApp = async ( element: HTMLElement, - coreStart: CoreStart, + startServices: CoreStart & { + navigation: NavigationPublicPluginStart; + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + }, history: ScopedHistory ) => { const homeTitle = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); @@ -68,7 +72,7 @@ export const renderApp = async ( }); render( - + , element @@ -80,9 +84,15 @@ export const renderApp = async ( }; }; -export const renderImportSampleDataApp = async (element: HTMLElement, coreStart: CoreStart) => { +export const renderImportSampleDataApp = async ( + element: HTMLElement, + startServices: CoreStart & { + navigation: NavigationPublicPluginStart; + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + } +) => { render( - + , element diff --git a/src/plugins/home/public/application/components/sample_data_set_cards.js b/src/plugins/home/public/application/components/sample_data_set_cards.js index 5d1e2b86f149..b324998b2a53 100644 --- a/src/plugins/home/public/application/components/sample_data_set_cards.js +++ b/src/plugins/home/public/application/components/sample_data_set_cards.js @@ -217,10 +217,17 @@ export class SampleDataSetCards extends React.Component { render() { return ( - + {this.state.sampleDataSets.map((sampleDataSet) => { return ( - + ); } @@ -229,21 +238,43 @@ class TutorialDirectoryUi extends React.Component { }; renderDataSourceSelector = () => { - const { isDataSourceEnabled, isLocalClusterHidden } = this.state; + const { isDataSourceEnabled, isLocalClusterHidden, useUpdatedUX } = this.state; + const { toastNotifications, savedObjectsClient, application, uiSettings } = getServices(); - return isDataSourceEnabled ? ( + if (!isDataSourceEnabled) { + return null; + } + + if (useUpdatedUX) { + return ( + + + + ); + } + return (
- ) : null; + ); }; renderNotices = () => { @@ -275,6 +306,28 @@ class TutorialDirectoryUi extends React.Component { renderHeader = () => { const notices = this.renderNotices(); const headerLinks = this.renderHeaderLinks(); + const { application } = getServices(); + const { + navigation: { + ui: { HeaderControl }, + }, + } = this.props.opensearchDashboards.services; + + if (this.state.useUpdatedUX) { + return ( + + ); + } return ( <> @@ -297,21 +350,29 @@ class TutorialDirectoryUi extends React.Component { }; renderPageBody = () => { + const { useUpdatedUX } = this.state; return ( {this.renderHeader()} - + {!useUpdatedUX && } {this.renderDataSourceSelector()} {this.renderTabs()} - + {this.renderTabContent()} ); }; render() { - const { isDataSourceEnabled } = this.state; + const { isDataSourceEnabled, useUpdatedUX } = this.state; + if (useUpdatedUX) { + return ( + + {this.renderPageBody()} + + ); + } return isDataSourceEnabled ? ( {this.renderPageBody()} @@ -327,6 +388,7 @@ TutorialDirectoryUi.propTypes = { openTab: PropTypes.string, isCloudEnabled: PropTypes.bool.isRequired, withoutHomeBreadCrumb: PropTypes.bool, + openSearchDashboards: PropTypes.object, }; -export const TutorialDirectory = injectI18n(TutorialDirectoryUi); +export const TutorialDirectory = injectI18n(withOpenSearchDashboards(TutorialDirectoryUi)); diff --git a/src/plugins/home/public/application/components/tutorial_directory.test.tsx b/src/plugins/home/public/application/components/tutorial_directory.test.tsx index 6114f463304d..2a02f49f660e 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.test.tsx +++ b/src/plugins/home/public/application/components/tutorial_directory.test.tsx @@ -11,6 +11,7 @@ import { setServices } from '../opensearch_dashboards_services'; import { getMockedServices } from '../opensearch_dashboards_services.mock'; import * as utils from '../../../../../plugins/data_source_management/public/components/utils'; import { DataSourceSelectionService } from '../../../../../plugins/data_source_management/public'; +import { OpenSearchDashboardsContextProvider } from '../../../../opensearch_dashboards_react/public'; const makeProps = () => { const coreMocks = coreMock.createStart(); @@ -21,6 +22,39 @@ const makeProps = () => { }; }; +const setup = async ({ props, services }) => { + const mockHeaderControl = ({ controls }) => { + return controls?.[0].description ?? controls?.[0].renderComponent ?? null; + }; + const setHeaderActionMenuMock = jest.fn(); + + // @ts-ignore + const { TutorialDirectory } = await import('./tutorial_directory'); + const finalServices = { + ...services, + notifications: services.toastNotifications, + setHeaderActionMenu: services.setHeaderActionMenu ?? setHeaderActionMenuMock, + navigation: services.navigation ?? { + ui: { + HeaderControl: mockHeaderControl, + }, + }, + }; + + const renderResult = render( + + + + + + ); + + return { + renderResult, + setHeaderActionMenuMock, + }; +}; + describe('', () => { let currentService: ReturnType; beforeEach(() => { @@ -28,17 +62,10 @@ describe('', () => { setServices(currentService); }); it('should render home breadcrumbs when withoutHomeBreadCrumb is undefined', async () => { - const finalProps = makeProps(); currentService.http.get.mockResolvedValueOnce([]); spyOn(utils, 'getDataSourceSelection').and.returnValue(new DataSourceSelectionService()); - // @ts-ignore - const { TutorialDirectory } = await import('./tutorial_directory'); - render( - - - - ); + await setup({ services: currentService }); expect(currentService.chrome.setBreadcrumbs).toBeCalledWith([ { href: '#/', @@ -51,21 +78,48 @@ describe('', () => { }); it('should not render home breadcrumbs when withoutHomeBreadCrumb is true', async () => { - const finalProps = makeProps(); currentService.http.get.mockResolvedValueOnce([]); spyOn(utils, 'getDataSourceSelection').and.returnValue(new DataSourceSelectionService()); - // @ts-ignore - const { TutorialDirectory } = await import('./tutorial_directory'); - render( - - - - ); + await setup({ + props: { withoutHomeBreadCrumb: true }, + services: currentService, + }); expect(currentService.chrome.setBreadcrumbs).toBeCalledWith([ { text: 'Add data', }, ]); }); + + it('should call setBreadcrumbs with "Sample data" when usedUpdatedUX', async () => { + currentService.http.get.mockResolvedValueOnce([]); + currentService.uiSettings.get.mockResolvedValueOnce(true); + spyOn(utils, 'getDataSourceSelection').and.returnValue(new DataSourceSelectionService()); + + await setup({ + props: { withoutHomeBreadCrumb: true }, + services: currentService, + }); + expect(currentService.chrome.setBreadcrumbs).toBeCalledWith([ + { + text: 'Sample data', + }, + ]); + }); + + it('should render description and call setHeaderActionMenu when usedUpdatedUX', async () => { + currentService.http.get.mockResolvedValueOnce([]); + currentService.uiSettings.get.mockResolvedValueOnce(true); + spyOn(utils, 'getDataSourceSelection').and.returnValue(new DataSourceSelectionService()); + + const { setHeaderActionMenuMock, renderResult } = await setup({ + props: { withoutHomeBreadCrumb: true }, + services: currentService, + }); + expect( + renderResult.getByText('Explore sample data, visualizations, and dashboards.') + ).toBeInTheDocument(); + expect(setHeaderActionMenuMock).toHaveBeenCalled(); + }); }); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 7ede9a32ecdb..3cacbf0950b8 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -59,6 +59,7 @@ import { import { DataPublicPluginStart } from '../../data/public'; import { TelemetryPluginStart } from '../../telemetry/public'; import { UsageCollectionSetup } from '../../usage_collection/public'; +import { NavigationPublicPluginStart } from '../../navigation/public'; import { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public'; import { AppNavLinkStatus, WorkspaceAvailability } from '../../../core/public'; import { PLUGIN_ID, HOME_APP_BASE_PATH, IMPORT_SAMPLE_DATA_APP_ID } from '../common/constants'; @@ -87,6 +88,7 @@ export interface HomePluginStartDependencies { urlForwarding: UrlForwardingStart; dataSource?: DataSourcePluginStart; contentManagement: ContentManagementPluginStart; + navigation: NavigationPublicPluginStart; } export interface HomePluginSetupDependencies { @@ -163,13 +165,21 @@ export class HomePublicPlugin title: 'Home', navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); + const [coreStart, { navigation }] = await core.getStartServices(); setCommonService(); coreStart.chrome.docTitle.change( i18n.translate('home.pageTitle', { defaultMessage: 'Home' }) ); const { renderApp } = await import('./application'); - return await renderApp(params.element, coreStart, params.history); + return await renderApp( + params.element, + { + ...coreStart, + navigation, + setHeaderActionMenu: params.setHeaderActionMenu, + }, + params.history + ); }, workspaceAvailability: WorkspaceAvailability.outsideWorkspace, }); @@ -214,7 +224,7 @@ export class HomePublicPlugin ? AppNavLinkStatus.default : AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); + const [coreStart, { navigation }] = await core.getStartServices(); setCommonService(); coreStart.chrome.docTitle.change( i18n.translate('home.tutorialDirectory.featureCatalogueTitle', { @@ -222,7 +232,11 @@ export class HomePublicPlugin }) ); const { renderImportSampleDataApp } = await import('./application'); - return await renderImportSampleDataApp(params.element, coreStart); + return await renderImportSampleDataApp(params.element, { + ...coreStart, + navigation, + setHeaderActionMenu: params.setHeaderActionMenu, + }); }, }); urlForwarding.forwardApp('home', 'home');