diff --git a/src/plugins/data_source_management/public/components/custom_database_icon/empty_icon.tsx b/src/plugins/data_source_management/public/components/custom_database_icon/empty_icon.tsx
new file mode 100644
index 000000000000..6147b8064c3b
--- /dev/null
+++ b/src/plugins/data_source_management/public/components/custom_database_icon/empty_icon.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import React from 'react';
+
+export const EmptyIcon = () => {
+ return (
+
+ );
+};
diff --git a/src/plugins/data_source_management/public/components/custom_database_icon/index.ts b/src/plugins/data_source_management/public/components/custom_database_icon/index.ts
index 506c9ee84980..262713b0b9fa 100644
--- a/src/plugins/data_source_management/public/components/custom_database_icon/index.ts
+++ b/src/plugins/data_source_management/public/components/custom_database_icon/index.ts
@@ -3,3 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/
export { ErrorIcon } from './error_icon';
+export { EmptyIcon } from './empty_icon';
diff --git a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.scss b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.scss
index 0c10d76c6bd8..62f6239424aa 100644
--- a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.scss
+++ b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.scss
@@ -11,10 +11,20 @@
.euiSelectableListItem__content {
cursor: default;
+
+
+ .euiSelectableListItem__text {
+ max-height: 100%;
+ }
}
- /* stylelint-enable */
.dataSourceAggregatedViewOuiFlexGroup {
+ .euiFlexItem {
+ max-width: fit-content;
+ }
+
+ /* stylelint-enable */
+
.dataSourceAggregatedViewOuiFlexItem {
color: grey;
text-overflow: ellipsis;
diff --git a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx
index 4aa8a31e848e..b9433e5ca189 100644
--- a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx
+++ b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx
@@ -19,7 +19,12 @@ import {
SavedObjectsClientContract,
ToastsStart,
} from 'opensearch-dashboards/public';
-import { getApplication, getDataSourcesWithFields, handleDataSourceFetchError } from '../utils';
+import {
+ getApplication,
+ getDataSourcesWithFields,
+ handleDataSourceFetchError,
+ handleNoAvailableDataSourceError,
+} from '../utils';
import { SavedObject } from '../../../../../core/public';
import { DataSourceAttributes } from '../../types';
import { NoDataSource } from '../no_data_source';
@@ -114,6 +119,15 @@ export class DataSourceAggregatedView extends React.Component<
allDataSourcesIdToTitleMap.set('', 'Local cluster');
}
+ if (allDataSourcesIdToTitleMap.size === 0) {
+ handleNoAvailableDataSourceError(
+ this.onEmptyState.bind(this),
+ this.props.notifications,
+ this.props.application
+ );
+ return;
+ }
+
this.setState({
...this.state,
allDataSourcesIdToTitleMap,
@@ -126,13 +140,17 @@ export class DataSourceAggregatedView extends React.Component<
});
}
+ onEmptyState() {
+ this.setState({ showEmptyState: true });
+ }
+
onError() {
this.setState({ showError: true });
}
render() {
if (this.state.showEmptyState) {
- return ;
+ return ;
}
if (this.state.showError) {
return ;
diff --git a/src/plugins/data_source_management/public/components/data_source_multi_selectable/data_source_multi_selectable.tsx b/src/plugins/data_source_management/public/components/data_source_multi_selectable/data_source_multi_selectable.tsx
index ddc9477061ce..85506ec84b61 100644
--- a/src/plugins/data_source_management/public/components/data_source_multi_selectable/data_source_multi_selectable.tsx
+++ b/src/plugins/data_source_management/public/components/data_source_multi_selectable/data_source_multi_selectable.tsx
@@ -12,7 +12,11 @@ import {
import { IUiSettingsClient } from 'src/core/public';
import { DataSourceFilterGroup, SelectedDataSourceOption } from './data_source_filter_group';
import { NoDataSource } from '../no_data_source';
-import { getDataSourcesWithFields, handleDataSourceFetchError } from '../utils';
+import {
+ getDataSourcesWithFields,
+ handleDataSourceFetchError,
+ handleNoAvailableDataSourceError,
+} from '../utils';
import { DataSourceBaseState } from '../data_source_menu/types';
import { DataSourceErrorMenu } from '../data_source_error_menu';
@@ -85,11 +89,20 @@ export class DataSourceMultiSelectable extends React.Component<
if (!this._isMounted) return;
+ if (selectedOptions.length === 0) {
+ handleNoAvailableDataSourceError(
+ this.onEmptyState.bind(this),
+ this.props.notifications,
+ this.props.application,
+ this.props.onSelectedDataSources
+ );
+ return;
+ }
+
this.setState({
...this.state,
selectedOptions,
defaultDataSource,
- showEmptyState: (fetchedDataSources?.length === 0 && this.props.hideLocalCluster) || false,
});
this.props.onSelectedDataSources(selectedOptions);
@@ -102,6 +115,10 @@ export class DataSourceMultiSelectable extends React.Component<
}
}
+ onEmptyState() {
+ this.setState({ showEmptyState: true });
+ }
+
onError() {
this.setState({ showError: true });
}
@@ -116,7 +133,7 @@ export class DataSourceMultiSelectable 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_selectable/data_source_selectable.scss b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.scss
index 4bc7296e3647..fc0e2775bc43 100644
--- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.scss
+++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.scss
@@ -1,12 +1,23 @@
.dataSourceSelectableOuiPanel {
width: 300px;
+ /* stylelint-disable */
+ .euiSelectableListItem__text {
+ max-height: 100%;
+ }
+
.dataSourceSelectableOuiFlexGroup {
+ .euiFlexItem {
+ max-width: fit-content;
+ }
+ /* stylelint-enable */
+
.dataSourceSelectableOuiFlexItem {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
+ max-width: fit-content;
}
}
}
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 63a67964a6ad..bb63d818df1d 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
@@ -405,7 +405,7 @@ describe('DataSourceSelectable', () => {
});
it('should render no data source when no data source filtered out and hide local cluster', async () => {
const onSelectedDataSource = jest.fn();
- const container = render(
+ render(
{
/>
);
await nextTick();
- const button = await container.findByTestId('dataSourceEmptyStateHeaderButton');
- expect(button).toHaveTextContent('No data sources');
+ expect(toasts.add).toBeCalled();
+ expect(onSelectedDataSource).toBeCalledWith([]);
});
});
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 082e7ffa9669..7fa02be4dd15 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
@@ -24,6 +24,7 @@ import {
getDefaultDataSource,
getFilteredDataSources,
handleDataSourceFetchError,
+ handleNoAvailableDataSourceError,
} from '../utils';
import { LocalCluster } from '../data_source_selector/data_source_selector';
import { SavedObject } from '../../../../../core/public';
@@ -185,6 +186,16 @@ export class DataSourceSelectable extends React.Component<
dataSourceOptions.unshift(LocalCluster);
}
+ if (dataSourceOptions.length === 0) {
+ handleNoAvailableDataSourceError(
+ this.onEmptyState.bind(this),
+ this.props.notifications,
+ this.props.application,
+ this.props.onSelectedDataSources
+ );
+ return;
+ }
+
const defaultDataSource = this.props.uiSettings?.get('defaultDataSource', null) ?? null;
if (this.props.selectedOption?.length) {
@@ -203,6 +214,10 @@ export class DataSourceSelectable extends React.Component<
}
}
+ onEmptyState() {
+ this.setState({ showEmptyState: true });
+ }
+
onError() {
this.setState({ showError: true });
}
@@ -227,12 +242,7 @@ export class DataSourceSelectable extends React.Component<
render() {
if (this.state.showEmptyState) {
- return (
-
- );
+ return ;
}
if (this.state.showError) {
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 f419ed7e6868..9aae5583cab9 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
@@ -62,15 +62,113 @@ 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.scss b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.scss
index 03235cd38537..3a91bd6bbe7d 100644
--- a/src/plugins/data_source_management/public/components/data_source_view/data_source_view.scss
+++ b/src/plugins/data_source_management/public/components/data_source_view/data_source_view.scss
@@ -1,8 +1,19 @@
.dataSourceViewOuiPanel {
width: 300px;
+
+ /* stylelint-disable */
+
+ .euiSelectableListItem__text {
+ max-height: 100%;
+ }
}
.dataSourceViewOuiFlexGroup {
+ .euiFlexItem {
+ max-width: fit-content;
+ }
+ /* stylelint-enable */
+
.dataSourceViewOuiFlexItem {
color: grey;
text-overflow: ellipsis;
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 ccfb70a8f2fd..07c36e414141 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
@@ -39,17 +39,18 @@ describe('DataSourceView', () => {
expect(toasts.addWarning).toBeCalledTimes(0);
});
it('When selected option is local cluster and hide local Cluster is true, should return error', () => {
+ const onSelectedDataSources = jest.fn();
component = shallow(
);
expect(component).toMatchSnapshot();
- expect(toasts.addWarning).toBeCalledTimes(1);
+ expect(onSelectedDataSources).toBeCalledWith([]);
});
it('Should return error when provided datasource has been filtered out', async () => {
component = shallow(
@@ -65,7 +66,6 @@ describe('DataSourceView', () => {
/>
);
expect(component).toMatchSnapshot();
- expect(toasts.addWarning).toBeCalledTimes(1);
});
it('Should render successfully when provided datasource has not been filtered out', async () => {
spyOn(utils, 'getDataSourceById').and.returnValue([{ id: 'test1', label: 'test1' }]);
@@ -139,7 +139,7 @@ describe('DataSourceView', () => {
it('should render no data source when no data source filtered out and hide local cluster', async () => {
const onSelectedDataSource = jest.fn();
- const container = render(
+ render(
{
dataSourceFilter={(ds) => false}
/>
);
- const button = await container.findByTestId('dataSourceEmptyStateHeaderButton');
- expect(button).toHaveTextContent('No data sources');
+ expect(onSelectedDataSource).toBeCalledWith([]);
});
});
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 6e1d135dcb3d..dd032f329753 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
@@ -27,7 +27,6 @@ import {
import { DataSourceDropDownHeader } from '../drop_down_header';
import { DataSourceItem } from '../data_source_item';
import { LocalCluster } from '../constants';
-import { NoDataSource } from '../no_data_source';
import './data_source_view.scss';
import { DataSourceViewError } from './data_source_view_error';
@@ -58,7 +57,7 @@ export class DataSourceView extends React.Component
- );
- }
if (this.state.showError) {
return (
- No data sources
-
+ />
}
closePopover={[Function]}
data-test-subj="dataSourceEmptyStatePopover"
display="inlineBlock"
hasArrow={true}
id="dataSourceEmptyStatePopover"
- initialFocus=".euiSelectableSearch"
isOpen={false}
ownFocus={true}
panelPaddingSize="none"
@@ -30,50 +26,48 @@ exports[`NoDataSource should render correctly with the provided totalDataSourceC
totalDataSourceCount={0}
/>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
@@ -85,7 +79,7 @@ exports[`NoDataSource should render correctly with the provided totalDataSourceC
-
+
`;
@@ -93,24 +87,20 @@ 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"
@@ -119,50 +109,48 @@ exports[`NoDataSource should render normally 1`] = `
totalDataSourceCount={0}
/>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
@@ -174,6 +162,6 @@ exports[`NoDataSource should render normally 1`] = `
-
+
`;
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 4fc257e5355a..7dec36bda609 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
@@ -28,7 +28,7 @@ describe('NoDataSource', () => {
await nextTick();
- const button = await container.findByTestId('dataSourceEmptyStateHeaderButton');
+ const button = await container.findByTestId('dataSourceEmptyMenuHeaderLink');
button.click();
expect(container.getByTestId('dataSourceEmptyStatePopover')).toBeVisible();
@@ -44,7 +44,7 @@ describe('NoDataSource', () => {
await nextTick();
- const button = await container.findByTestId('dataSourceEmptyStateHeaderButton');
+ const button = await container.findByTestId('dataSourceEmptyMenuHeaderLink');
button.click();
const redirectButton = await container.findByTestId(
'dataSourceEmptyStateManageDataSourceButton'
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 7d2142a31765..d10efe8c4a7b 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
@@ -4,55 +4,44 @@
*/
import React, { useState } from 'react';
+import { i18n } from '@osd/i18n';
import {
EuiButton,
- EuiButtonEmpty,
EuiPanel,
EuiPopover,
EuiText,
- EuiPopoverFooter,
EuiFlexGroup,
EuiFlexItem,
+ EuiButtonIcon,
} 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';
+import { EmptyIcon } from '../custom_database_icon';
interface DataSourceDropDownHeaderProps {
- totalDataSourceCount: number;
- activeDataSourceCount?: number;
application?: ApplicationStart;
}
-export const NoDataSource: React.FC = ({
- activeDataSourceCount,
- totalDataSourceCount,
- application,
-}) => {
+export const NoDataSource: React.FC = ({ application }) => {
const [showPopover, setShowPopover] = useState(false);
- const label = ' No data sources';
const button = (
- }
size="s"
- color="primary"
- onClick={() => {
- setShowPopover(!showPopover);
- }}
- >
- {label}
-
+ onClick={() => setShowPopover(!showPopover)}
+ />
);
const redirectButton = (
= ({
{
}
@@ -82,8 +71,8 @@ export const NoDataSource: React.FC = ({
{
}
@@ -92,7 +81,6 @@ export const NoDataSource: React.FC = ({
return (
= ({
anchorPosition="downLeft"
data-test-subj={'dataSourceEmptyStatePopover'}
>
-
+
-
- {text}
-
-
-
-
- {redirectButton}
+
+ {text}
-
+
+ {redirectButton}
+
+
);
};
diff --git a/src/plugins/data_source_management/public/components/toast_button/index.ts b/src/plugins/data_source_management/public/components/toast_button/index.ts
index fd881fc3d882..eb1974ff2bd5 100644
--- a/src/plugins/data_source_management/public/components/toast_button/index.ts
+++ b/src/plugins/data_source_management/public/components/toast_button/index.ts
@@ -3,3 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/
export { getReloadButton } from './reload_button';
+export { getManageDataSourceButton } from './manage_data_source_button';
diff --git a/src/plugins/data_source_management/public/components/toast_button/manage_data_source_button.tsx b/src/plugins/data_source_management/public/components/toast_button/manage_data_source_button.tsx
new file mode 100644
index 000000000000..6222f74fdec4
--- /dev/null
+++ b/src/plugins/data_source_management/public/components/toast_button/manage_data_source_button.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import React from 'react';
+import { i18n } from '@osd/i18n';
+import { ApplicationStart } from 'opensearch-dashboards/public';
+import { DSM_APP_ID } from '../../plugin';
+
+export const getManageDataSourceButton = (application?: ApplicationStart) => {
+ return (
+ <>
+
+
+
+ application?.navigateToApp('management', {
+ path: `opensearch-dashboards/${DSM_APP_ID}`,
+ })
+ }
+ >
+ {i18n.translate('dataSourceMenu.manageDataSourceToastButtonLabel', {
+ defaultMessage: 'Manage data sources',
+ })}
+
+
+
+ >
+ );
+};
diff --git a/src/plugins/data_source_management/public/components/utils.test.ts b/src/plugins/data_source_management/public/components/utils.test.ts
index 3a9443b9183f..b2628e3d3062 100644
--- a/src/plugins/data_source_management/public/components/utils.test.ts
+++ b/src/plugins/data_source_management/public/components/utils.test.ts
@@ -87,8 +87,9 @@ describe('DataSourceManagement: Utils.ts', () => {
const { toasts } = notificationServiceMock.createStartContract();
test('should send warning when data source is not available', () => {
- handleNoAvailableDataSourceError(toasts);
- expect(toasts.addWarning).toHaveBeenCalledWith(`Data source is not available`);
+ const changeState = jest.fn();
+ handleNoAvailableDataSourceError(changeState, toasts);
+ expect(toasts.add).toBeCalledTimes(1);
});
});
diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts
index 9d04f6b7edf0..8f635f840aec 100644
--- a/src/plugins/data_source_management/public/components/utils.ts
+++ b/src/plugins/data_source_management/public/components/utils.ts
@@ -24,7 +24,7 @@ import { DataSourceOption } from './data_source_menu/types';
import { DataSourceGroupLabelOption } from './data_source_menu/types';
import { createGetterSetter } from '../../../opensearch_dashboards_utils/public';
import { toMountPoint } from '../../../opensearch_dashboards_react/public';
-import { getReloadButton } from './toast_button';
+import { getManageDataSourceButton, getReloadButton } from './toast_button';
export async function getDataSources(savedObjectsClient: SavedObjectsClientContract) {
return savedObjectsClient
@@ -87,12 +87,21 @@ export async function setFirstDataSourceAsDefault(
}
}
-export function handleNoAvailableDataSourceError(notifications: ToastsStart) {
- notifications.addWarning(
- i18n.translate('dataSource.noAvailableDataSourceError', {
- defaultMessage: `Data source is not available`,
- })
- );
+export function handleNoAvailableDataSourceError(
+ changeState: () => void,
+ notifications: ToastsStart,
+ application?: ApplicationStart,
+ callback?: (ds: DataSourceOption[]) => void
+) {
+ changeState();
+ if (callback) callback([]);
+ notifications.add({
+ title: i18n.translate('dataSource.noAvailableDataSourceError', {
+ defaultMessage: 'No data sources connected yet. Connect your data sources to get started.',
+ }),
+ text: toMountPoint(getManageDataSourceButton(application)),
+ color: 'warning',
+ });
}
export function getFilteredDataSources(