From 5e526c49359e02d3ddfac09cdf7a575016598ab2 Mon Sep 17 00:00:00 2001 From: yujin-emma Date: Wed, 17 Apr 2024 21:27:39 +0000 Subject: [PATCH 1/2] add empty state popover Signed-off-by: yujin-emma --- CHANGELOG.md | 1 + .../data_source_selectable.test.tsx | 19 ++ .../data_source_selectable.tsx | 7 +- .../data_source_view.test.tsx.snap | 122 +----------- .../data_source_view.test.tsx | 17 ++ .../data_source_view/data_source_view.tsx | 11 +- .../no_data_source.test.tsx.snap | 173 +++++++++++++++++- .../no_data_source/no_data_source.scss | 3 + .../no_data_source/no_data_source.test.tsx | 48 ++++- .../no_data_source/no_data_source.tsx | 112 +++++++++++- 10 files changed, 378 insertions(+), 135 deletions(-) create mode 100644 src/plugins/data_source_management/public/components/no_data_source/no_data_source.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b3ecdcec6..30525f39ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Add installedPlugins list to data source saved object ([#6348](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6348)) - [Multiple Datasource] Add default icon in multi-selectable picker ([#6357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6357)) - [Multiple Datasource] Add empty state component for no connected data source ([#6499](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6499)) +- [Multiple Datasource] Add popover for empty state and redirect to data source management page([#6514](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6514)) - [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303)) - [Workspace] Filter left nav menu items according to the current workspace ([#6234](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6234)) - [Multiple Datasource] Add multi data source support to Timeline ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx index 1ced7e3872c..0ea8a5dea20 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx @@ -488,4 +488,23 @@ describe('DataSourceSelectable', () => { { id: 'test2', label: 'test2', checked: 'on' }, ]); }); + + it('should render no data source when no data source filtered out and hide local cluster', async () => { + const onSelectedDataSource = jest.fn(); + const container = render( + false} + /> + ); + await nextTick(); + const button = await container.findByTestId('dataSourceEmptyStateHeaderButton'); + expect(button).toHaveTextContent('No data sources'); + }); }); diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx index ec4af533c5d..da28ce903e5 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx @@ -251,7 +251,12 @@ export class DataSourceSelectable extends React.Component< render() { if (this.state.showEmptyState) { - return ; + return ( + + ); } if (this.state.showError) { return ; diff --git a/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap index 2132450772c..954e4924795 100644 --- a/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap @@ -68,125 +68,15 @@ exports[`DataSourceView Should render successfully when provided datasource has `; exports[`DataSourceView Should return error when provided datasource has been filtered out 1`] = ` - - - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="dataSourceViewPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - - - - - - - - - - - + `; exports[`DataSourceView When selected option is local cluster and hide local Cluster is true, should return error 1`] = ` - - - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="dataSourceViewPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - - - - - - - - - - - + `; exports[`DataSourceView should call getDataSourceById when only pass id with no label 1`] = ` diff --git a/src/plugins/data_source_management/public/components/data_source_view/data_source_view.test.tsx b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.test.tsx index bf933616118..b675d683606 100644 --- a/src/plugins/data_source_management/public/components/data_source_view/data_source_view.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.test.tsx @@ -136,4 +136,21 @@ describe('DataSourceView', () => { button.click(); expect(container).toMatchSnapshot(); }); + + it('should render no data source when no data source filtered out and hide local cluster', async () => { + const onSelectedDataSource = jest.fn(); + const container = render( + false} + /> + ); + const button = await container.findByTestId('dataSourceEmptyStateHeaderButton'); + expect(button).toHaveTextContent('No data sources'); + }); }); diff --git a/src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx index c967e3021af..3c816bdb977 100644 --- a/src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx +++ b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx @@ -35,10 +35,10 @@ interface DataSourceViewProps { fullWidth: boolean; selectedOption: DataSourceOption[]; hideLocalCluster: boolean; + application?: ApplicationStart; savedObjectsClient?: SavedObjectsClientContract; notifications?: ToastsStart; uiSettings?: IUiSettingsClient; - application?: ApplicationStart; dataSourceFilter?: (dataSource: any) => boolean; onSelectedDataSources?: (dataSources: DataSourceOption[]) => void; } @@ -93,7 +93,7 @@ export class DataSourceView extends React.Component; + return ( + + ); } if (this.state.showError) { return ; diff --git a/src/plugins/data_source_management/public/components/no_data_source/__snapshots__/no_data_source.test.tsx.snap b/src/plugins/data_source_management/public/components/no_data_source/__snapshots__/no_data_source.test.tsx.snap index 8c9f1339fed..28c48cd26ff 100644 --- a/src/plugins/data_source_management/public/components/no_data_source/__snapshots__/no_data_source.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/no_data_source/__snapshots__/no_data_source.test.tsx.snap @@ -1,14 +1,169 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`NoDataSource should render correctly with the provided totalDataSourceCount 1`] = ` + + No data sources + + } + closePopover={[Function]} + data-test-subj="dataSourceEmptyStatePopover" + display="inlineBlock" + hasArrow={true} + id="dataSourceEmptyStatePopover" + initialFocus=".euiSelectableSearch" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" +> + + + + + + + + + + + + + + + + + + + + +`; + exports[`NoDataSource should render normally 1`] = ` - + No data sources + + } + closePopover={[Function]} + data-test-subj="dataSourceEmptyStatePopover" + display="inlineBlock" + hasArrow={true} + id="dataSourceEmptyStatePopover" + initialFocus=".euiSelectableSearch" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" > - No data sources - + + + + + + + + + + + + + + + + + + + + `; diff --git a/src/plugins/data_source_management/public/components/no_data_source/no_data_source.scss b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.scss new file mode 100644 index 00000000000..705c6f1e80e --- /dev/null +++ b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.scss @@ -0,0 +1,3 @@ +.dataSourceEmptyStatePanel { + text-align: center; +} diff --git a/src/plugins/data_source_management/public/components/no_data_source/no_data_source.test.tsx b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.test.tsx index 380c6ad77e4..4fc257e5355 100644 --- a/src/plugins/data_source_management/public/components/no_data_source/no_data_source.test.tsx +++ b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.test.tsx @@ -5,12 +5,58 @@ import { ShallowWrapper, shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { NoDataSource } from './no_data_source'; +import { coreMock } from '../../../../../core/public/mocks'; +import { DSM_APP_ID } from '../../plugin'; describe('NoDataSource', () => { let component: ShallowWrapper, React.Component<{}, {}, any>>; + const totalDataSourceCount = 0; + const nextTick = () => new Promise((res) => process.nextTick(res)); + + it('should render correctly with the provided totalDataSourceCount', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it('should display popover when click "No data sources" button', async () => { + const applicationMock = coreMock.createStart().application; + const container = render( + + ); + + await nextTick(); + + const button = await container.findByTestId('dataSourceEmptyStateHeaderButton'); + button.click(); + + expect(container.getByTestId('dataSourceEmptyStatePopover')).toBeVisible(); + }); + + it('should call application.navigateToApp when the "Manage" link is clicked', async () => { + const applicationMock = coreMock.createStart().application; + const navigateToAppMock = applicationMock.navigateToApp; + + const container = render( + + ); + + await nextTick(); + + const button = await container.findByTestId('dataSourceEmptyStateHeaderButton'); + button.click(); + const redirectButton = await container.findByTestId( + 'dataSourceEmptyStateManageDataSourceButton' + ); + redirectButton.click(); + expect(navigateToAppMock).toHaveBeenCalledWith('management', { + path: `opensearch-dashboards/${DSM_APP_ID}`, + }); + }); + it('should render normally', () => { - component = shallow(); + component = shallow(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/data_source_management/public/components/no_data_source/no_data_source.tsx b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.tsx index 2f9cf8c8415..36d5b5befca 100644 --- a/src/plugins/data_source_management/public/components/no_data_source/no_data_source.tsx +++ b/src/plugins/data_source_management/public/components/no_data_source/no_data_source.tsx @@ -3,22 +3,124 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useState } from 'react'; -import { EuiButtonEmpty } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiContextMenuPanel, + EuiHorizontalRule, + EuiPanel, + EuiPopover, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { ApplicationStart } from 'opensearch-dashboards/public'; +import { FormattedMessage } from 'react-intl'; +import { DataSourceDropDownHeader } from '../drop_down_header'; +import { DSM_APP_ID } from '../../plugin'; -export const NoDataSource = () => { +interface DataSourceDropDownHeaderProps { + totalDataSourceCount: number; + activeDataSourceCount?: number; + application?: ApplicationStart; +} + +export const NoDataSource: React.FC = ({ + activeDataSourceCount, + totalDataSourceCount, + application, +}) => { + const [showPopover, setShowPopover] = useState(false); const label = ' No data sources'; - return ( + const button = ( { + setShowPopover(!showPopover); + }} > {label} ); + + const redirectButton = ( + + application?.navigateToApp('management', { + path: `opensearch-dashboards/${DSM_APP_ID}`, + }) + } + > + + + ); + const text = ( + <> + + { + + } + + + + { + + } + + + ); + + return ( + setShowPopover(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + data-test-subj={'dataSourceEmptyStatePopover'} + > + + + + + + + {text} + + {redirectButton} + + + + + ); }; From 4ac4f97b3b5e4890d79c424d7f0bbdcbbbf1c7b5 Mon Sep 17 00:00:00 2001 From: yujin-emma Date: Wed, 17 Apr 2024 22:39:37 +0000 Subject: [PATCH 2/2] fix failed test Signed-off-by: yujin-emma --- .../data_source_selectable.test.tsx | 86 ------------------- 1 file changed, 86 deletions(-) diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx index 0ea8a5dea20..63a67964a6a 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx @@ -403,92 +403,6 @@ describe('DataSourceSelectable', () => { expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); expect(onSelectedDataSource).toHaveBeenCalled(); }); - - it('should render opensearch cluster group label at the top of options, when there are options availiable', async () => { - const onSelectedDataSource = jest.fn(); - component = shallow( - - ); - - component.instance().componentDidMount!(); - await nextTick(); - const optionsProp = component.find(EuiSelectable).prop('options'); - expect(optionsProp[0]).toEqual(dataSourceOptionGroupLabel.opensearchCluster); - }); - - it('should not render opensearch cluster group label, when there is no option availiable', async () => { - const onSelectedDataSource = jest.fn(); - spyOn(utils, 'getDefaultDataSource').and.returnValue([]); - component = shallow( - - ); - - component.instance().componentDidMount!(); - await nextTick(); - const optionsProp = component.find(EuiSelectable).prop('options'); - expect(optionsProp).toEqual([]); - }); - - it('should render group lablel normally after onChange', async () => { - const onSelectedDataSource = jest.fn(); - component = shallow( - - ); - const componentInstance = component.instance(); - - componentInstance.componentDidMount!(); - await nextTick(); - const optionsPropBefore = component.find(EuiSelectable).prop('options'); - expect(optionsPropBefore).toEqual([ - dataSourceOptionGroupLabel.opensearchCluster, - { - id: 'test1', - label: 'test1', - checked: 'on', - }, - { - id: 'test2', - label: 'test2', - }, - { - id: 'test3', - label: 'test3', - }, - ]); - componentInstance.onChange([ - dataSourceOptionGroupLabel.opensearchCluster, - { id: 'test2', label: 'test2', checked: 'on' }, - ]); - await nextTick(); - const optionsPropAfter = component.find(EuiSelectable).prop('options'); - expect(optionsPropAfter).toEqual([ - dataSourceOptionGroupLabel.opensearchCluster, - { id: 'test2', label: 'test2', checked: 'on' }, - ]); - }); - it('should render no data source when no data source filtered out and hide local cluster', async () => { const onSelectedDataSource = jest.fn(); const container = render(