From c142517b4ee5e5a68c2ffd2348d3be2abb260ac6 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 8 May 2024 16:45:26 +0000 Subject: [PATCH 1/4] Fix merge conflict Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../public/components/constants.tsx | 5 ++ .../data_source_aggregated_view.test.tsx | 73 +++++++++++++++- .../data_source_aggregated_view.tsx | 24 ++++-- .../data_source_multi_selectable.tsx | 19 +++-- .../data_source_selectable.test.tsx | 81 ++++++++++++++---- .../data_source_selectable.tsx | 26 ++++-- .../no_data_source.test.tsx.snap | 85 ++++++++++++++++++- .../no_data_source/no_data_source.test.tsx | 32 +++++-- .../no_data_source/no_data_source.tsx | 24 +++++- .../public/components/utils.test.ts | 48 +++++++++-- .../public/components/utils.ts | 31 +++++-- 11 files changed, 376 insertions(+), 72 deletions(-) diff --git a/src/plugins/data_source_management/public/components/constants.tsx b/src/plugins/data_source_management/public/components/constants.tsx index 0d22aed50179..720e2d175867 100644 --- a/src/plugins/data_source_management/public/components/constants.tsx +++ b/src/plugins/data_source_management/public/components/constants.tsx @@ -12,3 +12,8 @@ export const LocalCluster: DataSourceOption = { }), id: '', }; + +export const NO_DATASOURCES_CONNECTED_MESSAGE = 'No data sources connected yet.'; +export const CONNECT_DATASOURCES_MESSAGE = 'Connect your data sources to get started.'; +export const NO_COMPATIBLE_DATASOURCES_MESSAGE = 'No compatible data sources are available.'; +export const ADD_COMPATIBLE_DATASOURCES_MESSAGE = 'Add a compatible data source.'; diff --git a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.test.tsx b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.test.tsx index f7493ff843be..43b22ed219ee 100644 --- a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.test.tsx @@ -5,8 +5,9 @@ import { ShallowWrapper, shallow } from 'enzyme'; import React from 'react'; +import { i18n } from '@osd/i18n'; import { DataSourceAggregatedView } from './data_source_aggregated_view'; -import { SavedObject, SavedObjectsClientContract } from '../../../../../core/public'; +import { IToasts, SavedObject, SavedObjectsClientContract } from '../../../../../core/public'; import { applicationServiceMock, notificationServiceMock, @@ -21,6 +22,12 @@ import { import * as utils from '../utils'; import { EuiSelectable, EuiSwitch } from '@elastic/eui'; import { DataSourceAttributes } from '../../types'; +import { + ADD_COMPATIBLE_DATASOURCES_MESSAGE, + CONNECT_DATASOURCES_MESSAGE, + NO_COMPATIBLE_DATASOURCES_MESSAGE, + NO_DATASOURCES_CONNECTED_MESSAGE, +} from '../constants'; describe('DataSourceAggregatedView: read all view (displayAllCompatibleDataSources is set to true)', () => { let component: ShallowWrapper, React.Component<{}, {}, any>>; @@ -409,6 +416,7 @@ describe('DataSourceAggregatedView empty state test due to filter out with local dataSourceFilter={filter} /> ); + const noCompatibleDataSourcesMessage = `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}`; expect(component).toMatchSnapshot(); await nextTick(); @@ -416,7 +424,7 @@ describe('DataSourceAggregatedView empty state test due to filter out with local expect(toasts.add.mock.calls[0][0]).toEqual({ color: 'warning', text: expect.any(Function), - title: 'No data sources connected yet. Connect your data sources to get started.', + title: noCompatibleDataSourcesMessage, }); expect(component.state('showEmptyState')).toBe(true); await nextTick(); @@ -502,3 +510,64 @@ describe('DataSourceAggregatedView error state test no matter hide local cluster } ); }); + +describe('DataSourceAggregatedView warning messages', () => { + const client = {} as any; + const uiSettings = uiSettingsServiceMock.createStartContract(); + const nextTick = () => new Promise((res) => process.nextTick(res)); + let toasts: IToasts; + const noDataSourcesConnectedMessage = `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`; + const noCompatibleDataSourcesMessage = `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}`; + + beforeEach(() => { + toasts = notificationServiceMock.createStartContract().toasts; + mockUiSettingsCalls(uiSettings, 'get', 'test1'); + }); + + it.each([ + { + findFunc: jest.fn().mockResolvedValue(getDataSourcesWithFieldsResponse), + defaultMessage: noCompatibleDataSourcesMessage, + activeDataSourceIds: ['test2'], + }, + { + findFunc: jest.fn().mockResolvedValue({ savedObjects: [] }), + defaultMessage: noDataSourcesConnectedMessage, + activeDataSourceIds: ['test2'], + }, + { + findFunc: jest.fn().mockResolvedValue(getDataSourcesWithFieldsResponse), + defaultMessage: noCompatibleDataSourcesMessage, + activeDataSourceIds: undefined, + }, + { + findFunc: jest.fn().mockResolvedValue({ savedObjects: [] }), + defaultMessage: noDataSourcesConnectedMessage, + activeDataSourceIds: undefined, + }, + ])( + 'should display correct warning message when no datasource selections are available and local cluster is hidden', + async ({ findFunc, defaultMessage, activeDataSourceIds }) => { + client.find = findFunc; + shallow( + false} + uiSettings={uiSettings} + /> + ); + await nextTick(); + + expect(toasts.add).toBeCalledWith( + expect.objectContaining({ + title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }), + }) + ); + } + ); +}); 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 00eacb2ea844..72db5bd5b4c4 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 @@ -45,6 +45,7 @@ interface DataSourceAggregatedViewState extends DataSourceBaseState { allDataSourcesIdToTitleMap: Map; switchChecked: boolean; defaultDataSource: string | null; + hasIncompatibleDataSources: boolean; } interface DataSourceOptionDisplay extends DataSourceOption { @@ -68,6 +69,7 @@ export class DataSourceAggregatedView extends React.Component< showError: false, switchChecked: false, defaultDataSource: null, + hasIncompatibleDataSources: false, }; } @@ -113,11 +115,12 @@ export class DataSourceAggregatedView extends React.Component< } if (allDataSourcesIdToTitleMap.size === 0) { - handleNoAvailableDataSourceError( - this.onEmptyState.bind(this), - this.props.notifications, - this.props.application - ); + handleNoAvailableDataSourceError({ + changeState: this.onEmptyState.bind(this, !!fetchedDataSources?.length), + notifications: this.props.notifications, + application: this.props.application, + hasIncompatibleDatasources: !!fetchedDataSources?.length, + }); return; } @@ -133,8 +136,8 @@ export class DataSourceAggregatedView extends React.Component< }); } - onEmptyState() { - this.setState({ showEmptyState: true }); + onEmptyState(hasIncompatibleDataSources: boolean) { + this.setState({ showEmptyState: true, hasIncompatibleDataSources }); } onError() { @@ -143,7 +146,12 @@ export class DataSourceAggregatedView 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_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 85506ec84b61..d0432ee2cb20 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 @@ -34,6 +34,7 @@ interface DataSourceMultiSeletableState extends DataSourceBaseState { dataSourceOptions: SelectedDataSourceOption[]; selectedOptions: SelectedDataSourceOption[]; defaultDataSource: string | null; + hasIncompatibleDatasources: boolean; } export class DataSourceMultiSelectable extends React.Component< @@ -51,6 +52,7 @@ export class DataSourceMultiSelectable extends React.Component< defaultDataSource: null, showEmptyState: false, showError: false, + hasIncompatibleDatasources: false, }; } @@ -90,12 +92,13 @@ 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 - ); + handleNoAvailableDataSourceError({ + changeState: this.onEmptyState.bind(this, !!fetchedDataSources?.length), + notifications: this.props.notifications, + application: this.props.application, + callback: this.props.onSelectedDataSources, + hasIncompatibleDatasources: !!fetchedDataSources?.length, + }); return; } @@ -115,8 +118,8 @@ export class DataSourceMultiSelectable extends React.Component< } } - onEmptyState() { - this.setState({ showEmptyState: true }); + onEmptyState(hasIncompatibleDatasources: boolean) { + this.setState({ showEmptyState: true, hasIncompatibleDatasources }); } onError() { 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 f1cef722c3c1..f1f4bb06e138 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 @@ -4,6 +4,7 @@ */ import { ShallowWrapper, shallow, mount } from 'enzyme'; +import { i18n } from '@osd/i18n'; import { SavedObjectsClientContract } from '../../../../../core/public'; import { notificationServiceMock } from '../../../../../core/public/mocks'; import React from 'react'; @@ -12,6 +13,12 @@ import { AuthType } from '../../types'; import { getDataSourcesWithFieldsResponse, mockResponseForSavedObjectsCalls } from '../../mocks'; import { render } from '@testing-library/react'; import * as utils from '../utils'; +import { + NO_DATASOURCES_CONNECTED_MESSAGE, + CONNECT_DATASOURCES_MESSAGE, + NO_COMPATIBLE_DATASOURCES_MESSAGE, + ADD_COMPATIBLE_DATASOURCES_MESSAGE, +} from '../constants'; describe('DataSourceSelectable', () => { let component: ShallowWrapper, React.Component<{}, {}, any>>; @@ -19,6 +26,8 @@ describe('DataSourceSelectable', () => { let client: SavedObjectsClientContract; const { toasts } = notificationServiceMock.createStartContract(); const nextTick = () => new Promise((res) => process.nextTick(res)); + const noDataSourcesConnectedMessage = `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`; + const noCompatibleDataSourcesMessage = `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}`; beforeEach(() => { client = { @@ -145,6 +154,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, + hasIncompatibleDatasources: false, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -167,6 +177,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, + hasIncompatibleDatasources: false, }); expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); @@ -345,6 +356,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, + hasIncompatibleDatasources: false, }); }); @@ -374,6 +386,7 @@ describe('DataSourceSelectable', () => { selectedOption: [], showEmptyState: false, showError: true, + hasIncompatibleDatasources: false, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -396,27 +409,59 @@ describe('DataSourceSelectable', () => { }, ], showError: true, + hasIncompatibleDatasources: false, }); 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(); - render( - false} - /> - ); - await nextTick(); - expect(toasts.add).toBeCalled(); - expect(onSelectedDataSource).toBeCalledWith([]); - }); + + it.each([ + { + findFunc: jest.fn().mockResolvedValue({ savedObjects: [] }), + defaultMessage: noDataSourcesConnectedMessage, + selectedOption: undefined, + }, + { + findFunc: jest.fn().mockResolvedValue({ savedObjects: [] }), + defaultMessage: noDataSourcesConnectedMessage, + selectedOption: [{ id: 'test2' }], + }, + { + findFunc: jest.fn().mockResolvedValue(getDataSourcesWithFieldsResponse), + defaultMessage: noCompatibleDataSourcesMessage, + selectedOption: undefined, + }, + { + findFunc: jest.fn().mockResolvedValue(getDataSourcesWithFieldsResponse), + defaultMessage: noCompatibleDataSourcesMessage, + selectedOption: [{ id: 'test2' }], + }, + ])( + 'should render correct message when there are no datasource options available and local cluster is hidden', + async ({ findFunc, selectedOption, defaultMessage }) => { + client.find = findFunc; + const onSelectedDataSource = jest.fn(); + render( + false} + /> + ); + await nextTick(); + + expect(toasts.add).toBeCalledWith( + expect.objectContaining({ + title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }), + }) + ); + 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 63b9eebf26c1..121f6724ce7a 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 @@ -56,6 +56,7 @@ interface DataSourceSelectableState extends DataSourceBaseState { isPopoverOpen: boolean; selectedOption?: DataSourceOption[]; defaultDataSource: string | null; + hasIncompatibleDatasources: boolean; } export class DataSourceSelectable extends React.Component< @@ -74,6 +75,7 @@ export class DataSourceSelectable extends React.Component< defaultDataSource: null, showEmptyState: false, showError: false, + hasIncompatibleDatasources: false, }; this.onChange.bind(this); @@ -187,12 +189,13 @@ export class DataSourceSelectable extends React.Component< } if (dataSourceOptions.length === 0) { - handleNoAvailableDataSourceError( - this.onEmptyState.bind(this), - this.props.notifications, - this.props.application, - this.props.onSelectedDataSources - ); + handleNoAvailableDataSourceError({ + changeState: this.onEmptyState.bind(this, !!fetchedDataSources?.length), + notifications: this.props.notifications, + application: this.props.application, + callback: this.props.onSelectedDataSources, + hasIncompatibleDatasources: !!fetchedDataSources?.length, + }); return; } @@ -214,8 +217,8 @@ export class DataSourceSelectable extends React.Component< } } - onEmptyState() { - this.setState({ showEmptyState: true }); + onEmptyState(hasIncompatibleDatasources: boolean) { + this.setState({ showEmptyState: true, hasIncompatibleDatasources }); } onError() { @@ -242,7 +245,12 @@ 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/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 ee8f2120012f..22719ee81b37 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 @@ -83,7 +83,90 @@ exports[`NoDataSource should render correctly with the provided totalDataSourceC `; -exports[`NoDataSource should render normally 1`] = ` +exports[`NoDataSource should render normally when hasIncompatibleDatasources is %b 1`] = ` + + } + closePopover={[Function]} + data-test-subj="dataSourceEmptyStatePopover" + display="inlineBlock" + hasArrow={true} + id="dataSourceEmptyStatePopover" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" +> + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NoDataSource should render normally when hasIncompatibleDatasources is %b 2`] = ` { const nextTick = () => new Promise((res) => process.nextTick(res)); it('should render correctly with the provided totalDataSourceCount', () => { - const wrapper = shallow(); + 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(); @@ -39,7 +45,11 @@ describe('NoDataSource', () => { const navigateToAppMock = applicationMock.navigateToApp; const container = render( - + ); await nextTick(); @@ -55,8 +65,16 @@ describe('NoDataSource', () => { }); }); - it('should render normally', () => { - component = shallow(); - expect(component).toMatchSnapshot(); - }); + it.each([false, true])( + 'should render normally when hasIncompatibleDatasources is %b', + (hasIncompatibleDatasources) => { + 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 d10efe8c4a7b..9b12f22506d1 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 @@ -20,12 +20,22 @@ import { FormattedMessage } from 'react-intl'; import { DataSourceDropDownHeader } from '../drop_down_header'; import { DSM_APP_ID } from '../../plugin'; import { EmptyIcon } from '../custom_database_icon'; +import { + ADD_COMPATIBLE_DATASOURCES_MESSAGE, + CONNECT_DATASOURCES_MESSAGE, + NO_COMPATIBLE_DATASOURCES_MESSAGE, + NO_DATASOURCES_CONNECTED_MESSAGE, +} from '../constants'; interface DataSourceDropDownHeaderProps { application?: ApplicationStart; + hasIncompatibleDatasources: boolean; } -export const NoDataSource: React.FC = ({ application }) => { +export const NoDataSource: React.FC = ({ + application, + hasIncompatibleDatasources, +}) => { const [showPopover, setShowPopover] = useState(false); const button = ( = ({ applicat { } @@ -72,7 +86,11 @@ export const NoDataSource: React.FC = ({ applicat { } 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 b2628e3d3062..19f9897f89c3 100644 --- a/src/plugins/data_source_management/public/components/utils.test.ts +++ b/src/plugins/data_source_management/public/components/utils.test.ts @@ -42,10 +42,17 @@ import { sigV4AuthMethod, usernamePasswordAuthMethod, } from '../types'; -import { HttpStart, SavedObject } from 'opensearch-dashboards/public'; +import { HttpStart, IToasts, SavedObject } from 'opensearch-dashboards/public'; +import { i18n } from '@osd/i18n'; import { AuthenticationMethod, AuthenticationMethodRegistry } from '../auth_registry'; import { deepEqual } from 'assert'; import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources'; +import { + ADD_COMPATIBLE_DATASOURCES_MESSAGE, + CONNECT_DATASOURCES_MESSAGE, + NO_COMPATIBLE_DATASOURCES_MESSAGE, + NO_DATASOURCES_CONNECTED_MESSAGE, +} from './constants'; const { savedObjects } = coreMock.createStart(); const { uiSettings } = coreMock.createStart(); @@ -84,13 +91,40 @@ describe('DataSourceManagement: Utils.ts', () => { }); describe('Handle no available data source error', () => { - const { toasts } = notificationServiceMock.createStartContract(); + let toasts: IToasts; + const noDataSourcesConnectedMessage = `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`; + const noCompatibleDataSourcesMessage = `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}`; - test('should send warning when data source is not available', () => { - const changeState = jest.fn(); - handleNoAvailableDataSourceError(changeState, toasts); - expect(toasts.add).toBeCalledTimes(1); - }); + beforeEach(() => { + toasts = notificationServiceMock.createStartContract().toasts; + }); + + test.each([ + { + hasIncompatibleDatasources: false, + defaultMessage: noDataSourcesConnectedMessage, + }, + { + hasIncompatibleDatasources: true, + defaultMessage: noCompatibleDataSourcesMessage, + }, + ])( + 'should send warning when data source is not available', + ({ hasIncompatibleDatasources, defaultMessage }) => { + const changeState = jest.fn(); + handleNoAvailableDataSourceError({ + changeState, + notifications: toasts, + hasIncompatibleDatasources, + }); + expect(toasts.add).toBeCalledTimes(1); + expect(toasts.add).toBeCalledWith( + expect.objectContaining({ + title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }), + }) + ); + } + ); }); describe('Get data source by ID', () => { diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index 8f635f840aec..a7dc7fde9554 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -25,6 +25,12 @@ import { DataSourceGroupLabelOption } from './data_source_menu/types'; import { createGetterSetter } from '../../../opensearch_dashboards_utils/public'; import { toMountPoint } from '../../../opensearch_dashboards_react/public'; import { getManageDataSourceButton, getReloadButton } from './toast_button'; +import { + ADD_COMPATIBLE_DATASOURCES_MESSAGE, + CONNECT_DATASOURCES_MESSAGE, + NO_COMPATIBLE_DATASOURCES_MESSAGE, + NO_DATASOURCES_CONNECTED_MESSAGE, +} from './constants'; export async function getDataSources(savedObjectsClient: SavedObjectsClientContract) { return savedObjectsClient @@ -87,18 +93,25 @@ export async function setFirstDataSourceAsDefault( } } -export function handleNoAvailableDataSourceError( - changeState: () => void, - notifications: ToastsStart, - application?: ApplicationStart, - callback?: (ds: DataSourceOption[]) => void -) { +export interface HandleNoAvailableDataSourceErrorProps { + changeState: () => void; + notifications: ToastsStart; + application?: ApplicationStart; + callback?: (ds: DataSourceOption[]) => void; + hasIncompatibleDatasources: boolean; +} + +export function handleNoAvailableDataSourceError(props: HandleNoAvailableDataSourceErrorProps) { + const { changeState, notifications, application, callback, hasIncompatibleDatasources } = props; + + const defaultMessage = hasIncompatibleDatasources + ? `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}` + : `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`; + changeState(); if (callback) callback([]); notifications.add({ - title: i18n.translate('dataSource.noAvailableDataSourceError', { - defaultMessage: 'No data sources connected yet. Connect your data sources to get started.', - }), + title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }), text: toMountPoint(getManageDataSourceButton(application)), color: 'warning', }); From 0c56977fab420ee1bbded5049c636d90e238cd22 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Mon, 6 May 2024 16:43:21 +0000 Subject: [PATCH 2/4] Refactor to incompatibleDataSourcesExist Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../data_source_aggregated_view.tsx | 12 ++++++------ .../data_source_multi_selectable.tsx | 10 +++++----- .../data_source_selectable.test.tsx | 10 +++++----- .../data_source_selectable.tsx | 12 ++++++------ .../__snapshots__/no_data_source.test.tsx.snap | 4 ++-- .../no_data_source/no_data_source.test.tsx | 6 +++--- .../components/no_data_source/no_data_source.tsx | 8 ++++---- .../public/components/utils.test.ts | 8 ++++---- .../public/components/utils.ts | 6 +++--- 9 files changed, 38 insertions(+), 38 deletions(-) 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 72db5bd5b4c4..8d8cd2ed0ef9 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 @@ -45,7 +45,7 @@ interface DataSourceAggregatedViewState extends DataSourceBaseState { allDataSourcesIdToTitleMap: Map; switchChecked: boolean; defaultDataSource: string | null; - hasIncompatibleDataSources: boolean; + incompatibleDataSourcesExist: boolean; } interface DataSourceOptionDisplay extends DataSourceOption { @@ -69,7 +69,7 @@ export class DataSourceAggregatedView extends React.Component< showError: false, switchChecked: false, defaultDataSource: null, - hasIncompatibleDataSources: false, + incompatibleDataSourcesExist: false, }; } @@ -119,7 +119,7 @@ export class DataSourceAggregatedView extends React.Component< changeState: this.onEmptyState.bind(this, !!fetchedDataSources?.length), notifications: this.props.notifications, application: this.props.application, - hasIncompatibleDatasources: !!fetchedDataSources?.length, + incompatibleDataSourcesExist: !!fetchedDataSources?.length, }); return; } @@ -136,8 +136,8 @@ export class DataSourceAggregatedView extends React.Component< }); } - onEmptyState(hasIncompatibleDataSources: boolean) { - this.setState({ showEmptyState: true, hasIncompatibleDataSources }); + onEmptyState(incompatibleDataSourcesExist: boolean) { + this.setState({ showEmptyState: true, incompatibleDataSourcesExist }); } onError() { @@ -149,7 +149,7 @@ export class DataSourceAggregatedView extends React.Component< 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 d0432ee2cb20..481df093d741 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 @@ -34,7 +34,7 @@ interface DataSourceMultiSeletableState extends DataSourceBaseState { dataSourceOptions: SelectedDataSourceOption[]; selectedOptions: SelectedDataSourceOption[]; defaultDataSource: string | null; - hasIncompatibleDatasources: boolean; + incompatibleDataSourcesExist: boolean; } export class DataSourceMultiSelectable extends React.Component< @@ -52,7 +52,7 @@ export class DataSourceMultiSelectable extends React.Component< defaultDataSource: null, showEmptyState: false, showError: false, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }; } @@ -97,7 +97,7 @@ export class DataSourceMultiSelectable extends React.Component< notifications: this.props.notifications, application: this.props.application, callback: this.props.onSelectedDataSources, - hasIncompatibleDatasources: !!fetchedDataSources?.length, + incompatibleDataSourcesExist: !!fetchedDataSources?.length, }); return; } @@ -118,8 +118,8 @@ export class DataSourceMultiSelectable extends React.Component< } } - onEmptyState(hasIncompatibleDatasources: boolean) { - this.setState({ showEmptyState: true, hasIncompatibleDatasources }); + onEmptyState(incompatibleDataSourcesExist: boolean) { + this.setState({ showEmptyState: true, incompatibleDataSourcesExist }); } onError() { 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 f1f4bb06e138..6521aaddb258 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 @@ -154,7 +154,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -177,7 +177,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }); expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); @@ -356,7 +356,7 @@ describe('DataSourceSelectable', () => { }, ], showError: false, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }); }); @@ -386,7 +386,7 @@ describe('DataSourceSelectable', () => { selectedOption: [], showEmptyState: false, showError: true, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -409,7 +409,7 @@ describe('DataSourceSelectable', () => { }, ], showError: true, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }); expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); 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 121f6724ce7a..92f6243362bd 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 @@ -56,7 +56,7 @@ interface DataSourceSelectableState extends DataSourceBaseState { isPopoverOpen: boolean; selectedOption?: DataSourceOption[]; defaultDataSource: string | null; - hasIncompatibleDatasources: boolean; + incompatibleDataSourcesExist: boolean; } export class DataSourceSelectable extends React.Component< @@ -75,7 +75,7 @@ export class DataSourceSelectable extends React.Component< defaultDataSource: null, showEmptyState: false, showError: false, - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, }; this.onChange.bind(this); @@ -194,7 +194,7 @@ export class DataSourceSelectable extends React.Component< notifications: this.props.notifications, application: this.props.application, callback: this.props.onSelectedDataSources, - hasIncompatibleDatasources: !!fetchedDataSources?.length, + incompatibleDataSourcesExist: !!fetchedDataSources?.length, }); return; } @@ -217,8 +217,8 @@ export class DataSourceSelectable extends React.Component< } } - onEmptyState(hasIncompatibleDatasources: boolean) { - this.setState({ showEmptyState: true, hasIncompatibleDatasources }); + onEmptyState(incompatibleDataSourcesExist: boolean) { + this.setState({ showEmptyState: true, incompatibleDataSourcesExist }); } onError() { @@ -248,7 +248,7 @@ export class DataSourceSelectable extends React.Component< 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 22719ee81b37..f7f3e41b9cb5 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 @@ -83,7 +83,7 @@ exports[`NoDataSource should render correctly with the provided totalDataSourceC `; -exports[`NoDataSource should render normally when hasIncompatibleDatasources is %b 1`] = ` +exports[`NoDataSource should render normally when incompatibleDataSourcesExist is %b 1`] = ` `; -exports[`NoDataSource should render normally when hasIncompatibleDatasources is %b 2`] = ` +exports[`NoDataSource should render normally when incompatibleDataSourcesExist is %b 2`] = ` { }); it.each([false, true])( - 'should render normally when hasIncompatibleDatasources is %b', - (hasIncompatibleDatasources) => { + 'should render normally when incompatibleDataSourcesExist is %b', + (incompatibleDataSourcesExist) => { 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 9b12f22506d1..a34099370e18 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 @@ -29,12 +29,12 @@ import { interface DataSourceDropDownHeaderProps { application?: ApplicationStart; - hasIncompatibleDatasources: boolean; + incompatibleDataSourcesExist: boolean; } export const NoDataSource: React.FC = ({ application, - hasIncompatibleDatasources, + incompatibleDataSourcesExist, }) => { const [showPopover, setShowPopover] = useState(false); const button = ( @@ -74,7 +74,7 @@ export const NoDataSource: React.FC = ({ = ({ { test.each([ { - hasIncompatibleDatasources: false, + incompatibleDataSourcesExist: false, defaultMessage: noDataSourcesConnectedMessage, }, { - hasIncompatibleDatasources: true, + incompatibleDataSourcesExist: true, defaultMessage: noCompatibleDataSourcesMessage, }, ])( 'should send warning when data source is not available', - ({ hasIncompatibleDatasources, defaultMessage }) => { + ({ incompatibleDataSourcesExist, defaultMessage }) => { const changeState = jest.fn(); handleNoAvailableDataSourceError({ changeState, notifications: toasts, - hasIncompatibleDatasources, + incompatibleDataSourcesExist, }); expect(toasts.add).toBeCalledTimes(1); expect(toasts.add).toBeCalledWith( diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index a7dc7fde9554..b04c9a51dc99 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -98,13 +98,13 @@ export interface HandleNoAvailableDataSourceErrorProps { notifications: ToastsStart; application?: ApplicationStart; callback?: (ds: DataSourceOption[]) => void; - hasIncompatibleDatasources: boolean; + incompatibleDataSourcesExist: boolean; } export function handleNoAvailableDataSourceError(props: HandleNoAvailableDataSourceErrorProps) { - const { changeState, notifications, application, callback, hasIncompatibleDatasources } = props; + const { changeState, notifications, application, callback, incompatibleDataSourcesExist } = props; - const defaultMessage = hasIncompatibleDatasources + const defaultMessage = incompatibleDataSourcesExist ? `${NO_COMPATIBLE_DATASOURCES_MESSAGE} ${ADD_COMPATIBLE_DATASOURCES_MESSAGE}` : `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`; From 796c8e6690ace802d2371f826c8095b20dd29cba Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 16:50:34 +0000 Subject: [PATCH 3/4] Changeset file for PR #6678 created/updated --- changelogs/fragments/6678.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/6678.yml diff --git a/changelogs/fragments/6678.yml b/changelogs/fragments/6678.yml new file mode 100644 index 000000000000..46e9568f5653 --- /dev/null +++ b/changelogs/fragments/6678.yml @@ -0,0 +1,2 @@ +fix: +- [MDS] Add a new message to data source components when there are no compatible datasources ([#6678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6678)) \ No newline at end of file From d8678b5087e93b7c4603287a8ea81899b1036ff4 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 7 May 2024 15:12:31 +0000 Subject: [PATCH 4/4] Move required args to the top Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../data_source_selectable/data_source_selectable.tsx | 2 +- .../public/components/no_data_source/no_data_source.tsx | 2 +- src/plugins/data_source_management/public/components/utils.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 92f6243362bd..fd1c685676f0 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 @@ -54,9 +54,9 @@ interface DataSourceSelectableProps { interface DataSourceSelectableState extends DataSourceBaseState { dataSourceOptions: DataSourceOption[]; isPopoverOpen: boolean; - selectedOption?: DataSourceOption[]; defaultDataSource: string | null; incompatibleDataSourcesExist: boolean; + selectedOption?: DataSourceOption[]; } export class DataSourceSelectable extends React.Component< 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 a34099370e18..6b48a7a7a0b4 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 @@ -28,8 +28,8 @@ import { } from '../constants'; interface DataSourceDropDownHeaderProps { - application?: ApplicationStart; incompatibleDataSourcesExist: boolean; + application?: ApplicationStart; } export const NoDataSource: React.FC = ({ diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index b04c9a51dc99..7a4ec06d5cfb 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -96,9 +96,9 @@ export async function setFirstDataSourceAsDefault( export interface HandleNoAvailableDataSourceErrorProps { changeState: () => void; notifications: ToastsStart; + incompatibleDataSourcesExist: boolean; application?: ApplicationStart; callback?: (ds: DataSourceOption[]) => void; - incompatibleDataSourcesExist: boolean; } export function handleNoAvailableDataSourceError(props: HandleNoAvailableDataSourceErrorProps) {