diff --git a/CHANGELOG.md b/CHANGELOG.md index 344a3345b76d..200a8e653248 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 37f84bb9c142..63a67964a6ad 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,4 +403,22 @@ describe('DataSourceSelectable', () => { expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); expect(onSelectedDataSource).toHaveBeenCalled(); }); + 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 6586e7bbdcd5..2259f93fbbec 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 @@ -228,7 +228,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 2132450772c9..954e49247957 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 bf9336161186..b675d6836060 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 c967e3021af2..3c816bdb9770 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 8c9f1339fed4..28c48cd26ffb 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 000000000000..705c6f1e80e9 --- /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 380c6ad77e4f..4fc257e5355a 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 2f9cf8c8415a..36d5b5befcab 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} + + + + + ); };