From a7c6ce8715f9ffd615c554fcd675ba5e1e8dadfe Mon Sep 17 00:00:00 2001 From: yubonluo Date: Sat, 20 Jul 2024 16:06:54 +0800 Subject: [PATCH 1/4] support get start card in home page Signed-off-by: yubonluo --- .../card_container/card_container.tsx | 7 +- .../card_container/card_embeddable.tsx | 10 +- .../public/components/section_input.ts | 2 + .../public/components/section_render.tsx | 27 ++- .../services/content_management/types.ts | 2 + .../workspace/opensearch_dashboards.json | 2 +- .../components/home_get_start_card/index.ts | 6 + .../use_case_footer.test.tsx | 134 +++++++++++ .../home_get_start_card/use_case_footer.tsx | 225 ++++++++++++++++++ src/plugins/workspace/public/plugin.ts | 55 ++++- 10 files changed, 458 insertions(+), 12 deletions(-) create mode 100644 src/plugins/workspace/public/components/home_get_start_card/index.ts create mode 100644 src/plugins/workspace/public/components/home_get_start_card/use_case_footer.test.tsx create mode 100644 src/plugins/workspace/public/components/home_get_start_card/use_case_footer.tsx diff --git a/src/plugins/content_management/public/components/card_container/card_container.tsx b/src/plugins/content_management/public/components/card_container/card_container.tsx index 518734563607..433d44b2e8e0 100644 --- a/src/plugins/content_management/public/components/card_container/card_container.tsx +++ b/src/plugins/content_management/public/components/card_container/card_container.tsx @@ -10,7 +10,12 @@ import { CardList } from './card_list'; export const CARD_CONTAINER = 'CARD_CONTAINER'; -export type CardContainerInput = ContainerInput<{ description: string; onClick?: () => void }>; +export type CardContainerInput = ContainerInput<{ + description: string; + onClick?: () => void; + icon?: React.ReactElement; + footer?: React.ReactElement; +}>; export class CardContainer extends Container<{}, CardContainerInput> { public readonly type = CARD_CONTAINER; diff --git a/src/plugins/content_management/public/components/card_container/card_embeddable.tsx b/src/plugins/content_management/public/components/card_container/card_embeddable.tsx index 844cf13a777c..c29e958ccc50 100644 --- a/src/plugins/content_management/public/components/card_container/card_embeddable.tsx +++ b/src/plugins/content_management/public/components/card_container/card_embeddable.tsx @@ -10,7 +10,12 @@ import { EuiCard } from '@elastic/eui'; import { Embeddable, EmbeddableInput, IContainer } from '../../../../embeddable/public'; export const CARD_EMBEDDABLE = 'card_embeddable'; -export type CardEmbeddableInput = EmbeddableInput & { description: string; onClick?: () => void }; +export type CardEmbeddableInput = EmbeddableInput & { + description: string; + onClick?: () => void; + icon?: React.ReactElement; + footer?: React.ReactElement; +}; export class CardEmbeddable extends Embeddable { public readonly type = CARD_EMBEDDABLE; @@ -27,10 +32,13 @@ export class CardEmbeddable extends Embeddable { this.node = node; ReactDOM.render( , node ); diff --git a/src/plugins/content_management/public/components/section_input.ts b/src/plugins/content_management/public/components/section_input.ts index 1d37feef8ecc..4e04209adcc4 100644 --- a/src/plugins/content_management/public/components/section_input.ts +++ b/src/plugins/content_management/public/components/section_input.ts @@ -42,6 +42,8 @@ export const createCardInput = ( title: content.title, description: content.description, onClick: content.onClick, + icon: content.icon, + footer: content.footer, }, }; } diff --git a/src/plugins/content_management/public/components/section_render.tsx b/src/plugins/content_management/public/components/section_render.tsx index 19f14bdb1d67..180632331c83 100644 --- a/src/plugins/content_management/public/components/section_render.tsx +++ b/src/plugins/content_management/public/components/section_render.tsx @@ -6,9 +6,8 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useObservable } from 'react-use'; import { BehaviorSubject } from 'rxjs'; -import { EuiTitle } from '@elastic/eui'; +import { EuiButtonIcon, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; - import { Content, Section } from '../services'; import { EmbeddableInput, EmbeddableRenderer, EmbeddableStart } from '../../../embeddable/public'; import { DashboardContainerInput } from '../../../dashboard/public'; @@ -49,6 +48,10 @@ const DashboardSection = ({ section, embeddable, contents$, savedObjectsClient } }; const CardSection = ({ section, embeddable, contents$ }: Props) => { + const [isCardVisible, setIsCardVisible] = useState(true); + const toggleCardVisibility = () => { + setIsCardVisible(!isCardVisible); + }; const contents = useObservable(contents$); const input = useMemo(() => { return createCardInput(section, contents ?? []); @@ -58,12 +61,24 @@ const CardSection = ({ section, embeddable, contents$ }: Props) => { if (section.kind === 'card' && factory && input) { return ( -
+ -

{section.title}

+

+ + {section.title} +

- -
+ {isCardVisible && ( + <> + + + )} + ); } diff --git a/src/plugins/content_management/public/services/content_management/types.ts b/src/plugins/content_management/public/services/content_management/types.ts index 0a0020ed6254..98f19532946e 100644 --- a/src/plugins/content_management/public/services/content_management/types.ts +++ b/src/plugins/content_management/public/services/content_management/types.ts @@ -59,6 +59,8 @@ export type Content = title: string; description: string; onClick?: () => void; + icon?: React.ReactElement; + footer?: React.ReactElement; }; export type SavedObjectInput = diff --git a/src/plugins/workspace/opensearch_dashboards.json b/src/plugins/workspace/opensearch_dashboards.json index 2e9377b3bda9..79dff7504bc5 100644 --- a/src/plugins/workspace/opensearch_dashboards.json +++ b/src/plugins/workspace/opensearch_dashboards.json @@ -7,6 +7,6 @@ "savedObjects", "opensearchDashboardsReact" ], - "optionalPlugins": ["savedObjectsManagement","management","dataSourceManagement"], + "optionalPlugins": ["savedObjectsManagement","management","dataSourceManagement","contentManagement"], "requiredBundles": ["opensearchDashboardsReact"] } diff --git a/src/plugins/workspace/public/components/home_get_start_card/index.ts b/src/plugins/workspace/public/components/home_get_start_card/index.ts new file mode 100644 index 000000000000..f78300a492d3 --- /dev/null +++ b/src/plugins/workspace/public/components/home_get_start_card/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { UseCaseFooter } from './use_case_footer'; diff --git a/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.test.tsx b/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.test.tsx new file mode 100644 index 000000000000..0a220ef8fb8b --- /dev/null +++ b/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.test.tsx @@ -0,0 +1,134 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { UseCaseFooter as UseCaseFooterComponent } from './use_case_footer'; +import { httpServiceMock } from '../../../../../core/public/mocks'; +import { IntlProvider } from 'react-intl'; + +const mockBasePath = httpServiceMock.createSetupContract().basePath; +const getUrl = (appId: string) => `https://test.com/app/${appId}`; + +const createWorkspace = (id: string, name: string, useCaseId: string) => ({ + id, + name, + description: '', + features: [useCaseId], + reserved: false, + permissions: { + library_write: { users: [] }, + write: { users: [] }, + }, +}); + +const UseCaseFooter = (props: any) => { + return ( + + + + ); +}; + +describe('UseCaseFooter', () => { + it('renders create workspace button for admin when no workspaces within use case exist', () => { + const { getByTestId } = render( + + ); + + const button = getByTestId('useCase.footer.createWorkspace.button'); + expect(button).toBeInTheDocument(); + fireEvent.click(button); + expect(screen.getByText('No workspaces found')).toBeInTheDocument(); + const createWorkspaceButtonInModal = getByTestId('useCase.footer.modal.create.button'); + expect(createWorkspaceButtonInModal).toHaveAttribute( + 'href', + 'https://test.com/app/workspace_create' + ); + }); + + it('renders create workspace button for non-admin when no workspaces within use case exist', () => { + const { getByTestId } = render( + + ); + + const button = getByTestId('useCase.footer.createWorkspace.button'); + expect(button).toBeInTheDocument(); + fireEvent.click(button); + expect(screen.getByText('Unable to create workspace')).toBeInTheDocument(); + expect(screen.queryByTestId('useCase.footer.modal.create.button')).not.toBeInTheDocument(); + }); + + it('renders open workspace button when one workspace exists', () => { + const { getByTestId } = render( + + ); + + const button = getByTestId('useCase.footer.openWorkspace.button'); + expect(button).toBeInTheDocument(); + expect(button).not.toBeDisabled(); + expect(button).toHaveAttribute('href', 'https://test.com/w/1/app/workspace_overview'); + }); + + it('renders select workspace popover when multiple workspaces exist', () => { + const originalLocation = window.location; + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + const workspaces = [ + createWorkspace('1', 'Workspace 1', 'use-case-observability'), + createWorkspace('2', 'Workspace 2', 'use-case-observability'), + ]; + render( + + ); + + const button = screen.getByText('Select workspace'); + expect(button).toBeInTheDocument(); + + fireEvent.click(button); + expect(screen.getByText('Workspace 1')).toBeInTheDocument(); + expect(screen.getByText('Workspace 2')).toBeInTheDocument(); + expect(screen.getByText('Observability Workspaces')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('Workspace 1')); + expect(window.location.assign).toHaveBeenCalledWith( + 'https://test.com/w/1/app/workspace_overview' + ); + Object.defineProperty(window, 'location', { + value: originalLocation, + }); + }); +}); diff --git a/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.tsx b/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.tsx new file mode 100644 index 000000000000..cf9f71a644fb --- /dev/null +++ b/src/plugins/workspace/public/components/home_get_start_card/use_case_footer.tsx @@ -0,0 +1,225 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiPopover, + EuiContextMenu, + EuiAvatar, + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiFieldSearch, + EuiSpacer, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, +} from '@elastic/eui'; +import React, { useMemo, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { i18n } from '@osd/i18n'; +import { BehaviorSubject } from 'rxjs'; +import { useObservable } from 'react-use'; +import { WORKSPACE_DETAIL_APP_ID } from '../../../common/constants'; +import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; +import { CoreStart, IBasePath, WorkspaceObject } from '../../../../../core/public'; +import { WorkspaceUseCase } from '../../types'; +import { getUseCaseFromFeatureConfig } from '../../utils'; + +interface UseCaseFooterProps { + useCaseId: string; + useCaseTitle: string; + workspaceList: WorkspaceObject[]; + basePath: IBasePath; + isDashboardAdmin: boolean; + getUrl: Function; + availableUseCases: WorkspaceUseCase[]; +} + +export const UseCaseFooter = ({ + useCaseId, + useCaseTitle, + workspaceList, + basePath, + isDashboardAdmin, + getUrl, + availableUseCases, +}: UseCaseFooterProps) => { + // const workspaceList = useObservable(core.workspaces.workspaceList$, []); + // const availableUseCases = useObservable(registeredUseCases$, []); + // const a = registeredUseCases$.getValue(); + const [isPopoverOpen, setPopover] = useState(false); + const [searchValue, setSearchValue] = useState(''); + const [isModalVisible, setIsModalVisible] = useState(false); + const closeModal = () => setIsModalVisible(false); + const showModal = () => setIsModalVisible(!isModalVisible); + const onButtonClick = () => setPopover(!isPopoverOpen); + const closePopover = () => setPopover(false); + // const isDashboardAdmin = !!core.application.capabilities.dashboards; + // const basePath = core.http.basePath; + + const appId = + availableUseCases.find((useCase) => useCase.id === useCaseId)?.features[0] ?? + WORKSPACE_DETAIL_APP_ID; + + const filterWorkspaces = useMemo( + () => + workspaceList.filter( + (workspace) => + workspace.features?.map(getUseCaseFromFeatureConfig).filter(Boolean)[0] === useCaseId + ), + [useCaseId, workspaceList] + ); + + const searchWorkspaces = useMemo( + () => + filterWorkspaces + .filter((workspace) => workspace.name.toLowerCase().includes(searchValue.toLowerCase())) + .slice(0, 5), + [filterWorkspaces, searchValue] + ); + + if (filterWorkspaces.length === 0) { + const modalHeaderTitle = i18n.translate('useCase.footer.modal.headerTitle', { + defaultMessage: isDashboardAdmin ? 'No workspaces found' : 'Unable to create workspace', + }); + const modalBodyContent = i18n.translate('useCase.footer.modal.bodyContent', { + defaultMessage: isDashboardAdmin + ? 'There are no available workspaces found. You can create a workspace in the workspace creation page.' + : 'To create a workspace, contact your administrator.', + }); + + return ( + <> + + + + {isModalVisible && ( + + + {modalHeaderTitle} + + + + {modalBodyContent} + + + + + + + {isDashboardAdmin && ( + + + + )} + + + )} + + ); + } + + if (filterWorkspaces.length === 1) { + const useCaseURL = formatUrlWithWorkspaceId( + getUrl(appId, { absolute: false }), + filterWorkspaces[0].id, + basePath + ); + return ( + + + + ); + } + + const workspaceToItem = (workspace: WorkspaceObject) => { + const useCaseURL = formatUrlWithWorkspaceId( + getUrl(appId, { absolute: false }), + workspace.id, + basePath + ); + const workspaceName = workspace.name; + + return { + name: workspaceName, + key: workspace.id, + icon: ( + + ), + onClick: () => { + window.location.assign(useCaseURL); + }, + }; + }; + + const button = ( + + + + ); + const panels = [ + { + id: 0, + items: searchWorkspaces.map(workspaceToItem), + }, + ]; + + return ( + + + + + + + + +

{useCaseTitle} Workspaces

+
+
+
+ + setSearchValue(e.target.value)} + fullWidth + /> +
+ +
+ ); +}; diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index a2c84554205a..1658fda6e1ad 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -7,6 +7,7 @@ import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; import React from 'react'; import { i18n } from '@osd/i18n'; import { map } from 'rxjs/operators'; +import { EuiIcon } from '@elastic/eui'; import { Plugin, CoreStart, @@ -28,6 +29,7 @@ import { WORKSPACE_DETAIL_APP_ID, WORKSPACE_CREATE_APP_ID, WORKSPACE_LIST_APP_ID, + WORKSPACE_USE_CASES, } from '../common/constants'; import { getWorkspaceIdFromUrl } from '../../../core/public/utils'; import { Services, WorkspaceUseCase } from './types'; @@ -44,6 +46,8 @@ import { isNavGroupInFeatureConfigs, } from './utils'; import { UseCaseService } from './services/use_case_service'; +import { ContentManagementPluginStart } from '../../../plugins/content_management/public'; +import { UseCaseFooter } from './components/home_get_start_card'; type WorkspaceAppType = ( params: AppMountParameters, @@ -57,7 +61,12 @@ interface WorkspacePluginSetupDeps { dataSourceManagement?: DataSourceManagementPluginSetup; } -export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> { +interface WorkspacePluginStartDeps { + contentManagement: ContentManagementPluginStart; +} + +export class WorkspacePlugin + implements Plugin<{}, {}, WorkspacePluginSetupDeps, WorkspacePluginStartDeps> { private coreStart?: CoreStart; private currentWorkspaceSubscription?: Subscription; private breadcrumbsSubscription?: Subscription; @@ -378,7 +387,45 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> return {}; } - public start(core: CoreStart) { + private registerGetStartedCardToNewHome( + core: CoreStart, + contentManagement: ContentManagementPluginStart + ) { + // console.log('------'); + // debugger; + const useCases = [ + WORKSPACE_USE_CASES.observability, + WORKSPACE_USE_CASES['security-analytics'], + WORKSPACE_USE_CASES.search, + WORKSPACE_USE_CASES.analytics, + ]; + + useCases.forEach((useCase, index) => { + contentManagement.registerContentProvider({ + id: `home_get_start_${useCase.id}`, + getTargetArea: () => `osd_homepage/get_started`, + getContent: () => ({ + id: useCase.id, + kind: 'card', + order: (index + 1) * 1000, + description: useCase.description, + title: useCase.title, + icon: React.createElement(EuiIcon, { size: 'xl', type: 'logoOpenSearch' }), + footer: React.createElement(UseCaseFooter, { + useCaseId: useCase.id, + useCaseTitle: useCase.title, + workspaceList: core.workspaces.workspaceList$.getValue(), + basePath: core.http.basePath, + isDashboardAdmin: core.application.capabilities?.dashboards?.isDashboardAdmin !== false, + getUrl: core.application.getUrlForApp, + availableUseCases: this.registeredUseCases$.getValue(), + }), + }), + }); + }); + } + + public start(core: CoreStart, { contentManagement }: WorkspacePluginStartDeps) { this.coreStart = core; this.currentWorkspaceIdSubscription = this._changeSavedObjectCurrentWorkspace(); @@ -398,8 +445,10 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> if (!core.chrome.navGroup.getNavGroupEnabled()) { this.addWorkspaceToBreadcrumbs(core); + } else { + // register get started card in new home page + this.registerGetStartedCardToNewHome(core, contentManagement); } - return {}; } From 9a09024fbeb22f9d1f291c3c8c600a21cfd6fce0 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 11:31:27 +0000 Subject: [PATCH 2/4] Changeset file for PR #7333 created/updated --- changelogs/fragments/7333.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/7333.yml diff --git a/changelogs/fragments/7333.yml b/changelogs/fragments/7333.yml new file mode 100644 index 000000000000..09d225d51ca2 --- /dev/null +++ b/changelogs/fragments/7333.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Register four get started cards in home page ([#7333](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7333)) \ No newline at end of file From f4930dec04db3d56158a02c9f3cac74377c40188 Mon Sep 17 00:00:00 2001 From: yubonluo Date: Sat, 20 Jul 2024 20:11:43 +0800 Subject: [PATCH 3/4] fix unit test errors Signed-off-by: yubonluo --- .../card_container/card_embeddable.test.tsx | 9 +++++++- src/plugins/workspace/public/plugin.test.ts | 22 +++++++++++-------- src/plugins/workspace/public/plugin.ts | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/plugins/content_management/public/components/card_container/card_embeddable.test.tsx b/src/plugins/content_management/public/components/card_container/card_embeddable.test.tsx index b335bac6d996..a87cd43554ea 100644 --- a/src/plugins/content_management/public/components/card_container/card_embeddable.test.tsx +++ b/src/plugins/content_management/public/components/card_container/card_embeddable.test.tsx @@ -3,10 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ +import React from 'react'; import { CardEmbeddable } from './card_embeddable'; test('CardEmbeddable should render a card with the title', () => { - const embeddable = new CardEmbeddable({ id: 'card-id', title: 'card title', description: '' }); + const embeddable = new CardEmbeddable({ + id: 'card-id', + title: 'card title', + description: '', + getIcon: () => <>icon, + getFooter: () => <>footer, + }); const node = document.createElement('div'); embeddable.render(node); diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts index b2ed55c08de6..6ffae95d40c3 100644 --- a/src/plugins/workspace/public/plugin.test.ts +++ b/src/plugins/workspace/public/plugin.test.ts @@ -19,9 +19,13 @@ import { savedObjectsManagementPluginMock } from '../../saved_objects_management import { managementPluginMock } from '../../management/public/mocks'; import { UseCaseService } from './services/use_case_service'; import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock'; -import { WorkspacePlugin } from './plugin'; +import { WorkspacePlugin, WorkspacePluginStartDeps } from './plugin'; +import { contentManagementPluginMocks } from '../../content_management/public'; describe('Workspace plugin', () => { + const mockDependencies: WorkspacePluginStartDeps = { + contentManagement: contentManagementPluginMocks.createStartContract(), + }; const getSetupMock = () => ({ ...coreMock.createSetup(), chrome: chromeServiceMock.createSetupContract(), @@ -48,7 +52,7 @@ describe('Workspace plugin', () => { const setupMock = getSetupMock(); const coreStart = coreMock.createStart(); await workspacePlugin.setup(setupMock, {}); - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); coreStart.workspaces.currentWorkspaceId$.next('foo'); expect(coreStart.savedObjects.client.setCurrentWorkspace).toHaveBeenCalledWith('foo'); expect(setupMock.application.register).toBeCalledTimes(4); @@ -182,7 +186,7 @@ describe('Workspace plugin', () => { const breadcrumbs = new BehaviorSubject([{ text: 'dashboards' }]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock); + workspacePlugin.start(startMock, mockDependencies); expect(startMock.chrome.setBreadcrumbs).toBeCalledWith( expect.arrayContaining([ expect.objectContaining({ @@ -208,7 +212,7 @@ describe('Workspace plugin', () => { ]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock); + workspacePlugin.start(startMock, mockDependencies); expect(startMock.chrome.setBreadcrumbs).not.toHaveBeenCalled(); }); @@ -225,7 +229,7 @@ describe('Workspace plugin', () => { jest.spyOn(navGroupUpdater$, 'next'); expect(navGroupUpdater$.next).not.toHaveBeenCalled(); - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); waitFor(() => { expect(navGroupUpdater$.next).toHaveBeenCalled(); @@ -236,7 +240,7 @@ describe('Workspace plugin', () => { const coreStart = coreMock.createStart(); coreStart.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); expect(coreStart.chrome.navControls.registerLeftBottom).toBeCalledTimes(1); }); @@ -265,7 +269,7 @@ describe('Workspace plugin', () => { const appUpdater$ = setupMock.application.registerAppUpdater.mock.calls[0][0]; - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); const appUpdater = await appUpdater$.pipe(first()).toPromise(); @@ -286,7 +290,7 @@ describe('Workspace plugin', () => { const navGroupUpdater$ = setupMock.chrome.navGroup.registerNavGroupUpdater.mock.calls[0][0]; - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); const navGroupUpdater = await navGroupUpdater$.pipe(first()).toPromise(); @@ -337,7 +341,7 @@ describe('Workspace plugin', () => { const appUpdaterChangeMock = jest.fn(); appUpdater$.subscribe(appUpdaterChangeMock); - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); // Wait for filterNav been executed await new Promise(setImmediate); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index d43d2946f010..a9ae23093a97 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -63,7 +63,7 @@ interface WorkspacePluginSetupDeps { dataSourceManagement?: DataSourceManagementPluginSetup; } -interface WorkspacePluginStartDeps { +export interface WorkspacePluginStartDeps { contentManagement: ContentManagementPluginStart; } From 5a2a1d8b4d887d65198575af265b5a95c591db6d Mon Sep 17 00:00:00 2001 From: yubonluo Date: Sun, 21 Jul 2024 11:02:30 +0800 Subject: [PATCH 4/4] optimize the code Signed-off-by: yubonluo --- .../public/components/page_render.tsx | 25 ++++++++++++------- .../public/components/section_render.tsx | 2 +- src/plugins/home/public/index.ts | 2 ++ .../workspace/opensearch_dashboards.json | 2 +- .../workspace_menu/workspace_menu.test.tsx | 12 --------- .../workspace_menu/workspace_menu.tsx | 12 ++++++--- src/plugins/workspace/public/plugin.ts | 3 ++- 7 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/plugins/content_management/public/components/page_render.tsx b/src/plugins/content_management/public/components/page_render.tsx index 9a5211ca3a46..90d6033576bb 100644 --- a/src/plugins/content_management/public/components/page_render.tsx +++ b/src/plugins/content_management/public/components/page_render.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { useObservable } from 'react-use'; import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Page } from '../services'; import { SectionRender } from './section_render'; import { EmbeddableStart } from '../../../embeddable/public'; @@ -21,16 +22,22 @@ export const PageRender = ({ page, embeddable, savedObjectsClient }: Props) => { const sections = useObservable(page.getSections$()) || []; return ( -
+ {sections.map((section) => ( - + + + ))} -
+ ); }; diff --git a/src/plugins/content_management/public/components/section_render.tsx b/src/plugins/content_management/public/components/section_render.tsx index 180632331c83..d28fbad7296a 100644 --- a/src/plugins/content_management/public/components/section_render.tsx +++ b/src/plugins/content_management/public/components/section_render.tsx @@ -61,7 +61,7 @@ const CardSection = ({ section, embeddable, contents$ }: Props) => { if (section.kind === 'card' && factory && input) { return ( - +

new HomePublicPlugin(initializerContext); + +export { HOME_PAGE_ID, HOME_CONTENT_AREAS } from '../common/constants'; diff --git a/src/plugins/workspace/opensearch_dashboards.json b/src/plugins/workspace/opensearch_dashboards.json index 79dff7504bc5..99a66fb1743a 100644 --- a/src/plugins/workspace/opensearch_dashboards.json +++ b/src/plugins/workspace/opensearch_dashboards.json @@ -8,5 +8,5 @@ "opensearchDashboardsReact" ], "optionalPlugins": ["savedObjectsManagement","management","dataSourceManagement","contentManagement"], - "requiredBundles": ["opensearchDashboardsReact"] + "requiredBundles": ["opensearchDashboardsReact", "home"] } diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx index d3578498c858..68ed1c67359f 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -109,18 +109,6 @@ describe('', () => { expect(screen.getByText('Observability')).toBeInTheDocument(); }); - it('should close the workspace dropdown list', async () => { - render(); - - fireEvent.click(screen.getByTestId('workspace-select-button')); - - expect(screen.getByText(/all workspaces/i)).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('workspace-select-button')); - await waitFor(() => { - expect(screen.queryByText(/all workspaces/i)).not.toBeInTheDocument(); - }); - }); - it('should navigate to the workspace', () => { coreStartMock.workspaces.workspaceList$.next([ { id: 'workspace-1', name: 'workspace 1', features: ['use-case-observability'] }, diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index bda11fb3d113..77d1cd6e602e 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -44,7 +44,7 @@ const allWorkspacesTitle = i18n.translate('workspace.menu.title.allWorkspaces', }); const recentWorkspacesTitle = i18n.translate('workspace.menu.title.recentWorkspaces', { - defaultMessage: 'recent workspaces', + defaultMessage: 'Recent workspaces', }); const createWorkspaceButton = i18n.translate('workspace.menu.button.createWorkspace', { @@ -158,6 +158,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { } onClick={() => { + closePopover(); window.location.assign(useCaseURL); }} /> @@ -221,6 +222,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { { + closePopover(); navigateToWorkspaceDetail(coreStart, currentWorkspace.id); }} > @@ -240,6 +242,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { { + closePopover(); coreStart.application.navigateToApp(WORKSPACE_LIST_APP_ID); }} > @@ -251,8 +254,9 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { - {getWorkspaceListGroup(filteredRecentWorkspaces, 'recent')} - {getWorkspaceListGroup(filteredWorkspaceList, 'all')} + {filteredRecentWorkspaces.length > 0 && + getWorkspaceListGroup(filteredRecentWorkspaces, 'recent')} + {filteredWorkspaceList.length > 0 && getWorkspaceListGroup(filteredWorkspaceList, 'all')} @@ -263,6 +267,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { key={WORKSPACE_LIST_APP_ID} data-test-subj="workspace-menu-view-all-button" onClick={() => { + closePopover(); coreStart.application.navigateToApp(WORKSPACE_LIST_APP_ID); }} > @@ -278,6 +283,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { key={WORKSPACE_CREATE_APP_ID} data-test-subj="workspace-menu-create-workspace-button" onClick={() => { + closePopover(); coreStart.application.navigateToApp(WORKSPACE_CREATE_APP_ID); }} > diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 4338ec6b7c2d..40f475d4a818 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -50,6 +50,7 @@ import { toMountPoint } from '../../opensearch_dashboards_react/public'; import { UseCaseService } from './services/use_case_service'; import { ContentManagementPluginStart } from '../../../plugins/content_management/public'; import { UseCaseFooter } from './components/home_get_start_card'; +import { HOME_CONTENT_AREAS } from '../../home/public'; type WorkspaceAppType = ( params: AppMountParameters, @@ -395,7 +396,7 @@ export class WorkspacePlugin useCases.forEach((useCase, index) => { contentManagement.registerContentProvider({ id: `home_get_start_${useCase.id}`, - getTargetArea: () => `osd_homepage/get_started`, + getTargetArea: () => HOME_CONTENT_AREAS.GET_STARTED, getContent: () => ({ id: useCase.id, kind: 'card',