From b275b4e3a1d857ba056109e213f605db0babc94c Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:13:25 -0700 Subject: [PATCH] [Multiple Datasource] Add error state to all data source menu components to show error component and consolidate all fetch errors (#6440) (#6516) * add error state to all components Signed-off-by: Lu Yu * add change log Signed-off-by: Lu Yu * resolve conflict and fix tests Signed-off-by: Lu Yu * move import up Signed-off-by: Lu Yu --------- Signed-off-by: Lu Yu Co-authored-by: ZilongX <99905560+ZilongX@users.noreply.github.com> (cherry picked from commit b9c703bc39c915cb5c240f0930acd9d1a43f4e68) Signed-off-by: github-actions[bot] # Conflicts: # CHANGELOG.md Co-authored-by: github-actions[bot] --- .../data_source_aggregated_view.tsx | 20 ++++--- .../data_source_error_menu.tsx | 16 +++++ .../data_source_error_menu/index.ts | 5 ++ .../create_data_source_menu.test.tsx.snap | 60 +++++-------------- .../components/data_source_menu/types.ts | 4 ++ .../data_source_multi_selectable.tsx | 23 ++++--- .../data_source_selectable.test.tsx | 7 ++- .../data_source_selectable.tsx | 26 ++++++-- .../data_source_view.test.tsx.snap | 46 +------------- .../data_source_view/data_source_view.tsx | 23 +++---- .../public/components/utils.test.ts | 4 +- .../public/components/utils.ts | 25 ++++---- 12 files changed, 126 insertions(+), 133 deletions(-) create mode 100644 src/plugins/data_source_management/public/components/data_source_error_menu/data_source_error_menu.tsx create mode 100644 src/plugins/data_source_management/public/components/data_source_error_menu/index.ts 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 7c039c2f64f3..dda3da78363b 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 @@ -13,9 +13,11 @@ import { } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { SavedObjectsClientContract, ToastsStart } from 'opensearch-dashboards/public'; -import { getDataSourcesWithFields } from '../utils'; +import { getDataSourcesWithFields, handleDataSourceFetchError } from '../utils'; import { SavedObject } from '../../../../../core/public'; import { DataSourceAttributes } from '../../types'; +import { DataSourceErrorMenu } from '../data_source_error_menu'; +import { DataSourceBaseState } from '../data_source_menu/types'; interface DataSourceAggregatedViewProps { savedObjectsClient: SavedObjectsClientContract; @@ -27,7 +29,7 @@ interface DataSourceAggregatedViewProps { displayAllCompatibleDataSources: boolean; } -interface DataSourceAggregatedViewState { +interface DataSourceAggregatedViewState extends DataSourceBaseState { isPopoverOpen: boolean; allDataSourcesIdToTitleMap: Map; } @@ -44,6 +46,7 @@ export class DataSourceAggregatedView extends React.Component< this.state = { isPopoverOpen: false, allDataSourcesIdToTitleMap: new Map(), + showError: false, }; } @@ -89,15 +92,18 @@ export class DataSourceAggregatedView extends React.Component< } }) .catch(() => { - this.props.notifications.addWarning( - i18n.translate('dataSource.fetchDataSourceError', { - defaultMessage: 'Unable to fetch existing data sources', - }) - ); + handleDataSourceFetchError(this.onError.bind(this), this.props.notifications); }); } + onError() { + this.setState({ showError: true }); + } + render() { + if (this.state.showError) { + return ; + } const button = ( { + return ( + <> + + Error + + ); +}; diff --git a/src/plugins/data_source_management/public/components/data_source_error_menu/index.ts b/src/plugins/data_source_management/public/components/data_source_error_menu/index.ts new file mode 100644 index 000000000000..1bc0b8eb36e3 --- /dev/null +++ b/src/plugins/data_source_management/public/components/data_source_error_menu/index.ts @@ -0,0 +1,5 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +export { DataSourceErrorMenu } from './data_source_error_menu'; diff --git a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap index 31ae3a99d9cd..c520768a6890 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap @@ -5,65 +5,33 @@ Object { "asFragment": [Function], "baseElement":
+
- + Error
, "container":
+
- + Error
, diff --git a/src/plugins/data_source_management/public/components/data_source_menu/types.ts b/src/plugins/data_source_management/public/components/data_source_menu/types.ts index e5f34a3a2979..483f08c524bc 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/types.ts +++ b/src/plugins/data_source_management/public/components/data_source_menu/types.ts @@ -28,6 +28,10 @@ export interface DataSourceBaseConfig { disabled?: boolean; } +export interface DataSourceBaseState { + showError: boolean; +} + export interface DataSourceMenuProps { componentType: DataSourceComponentType; componentConfig: T; 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 1a1d958b618c..25122a801e84 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 @@ -5,10 +5,11 @@ import React from 'react'; import { SavedObjectsClientContract, ToastsStart } from 'opensearch-dashboards/public'; -import { i18n } from '@osd/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { DataSourceFilterGroup, SelectedDataSourceOption } from './data_source_filter_group'; -import { getDataSourcesWithFields } from '../utils'; +import { getDataSourcesWithFields, handleDataSourceFetchError } from '../utils'; +import { DataSourceBaseState } from '../data_source_menu/types'; +import { DataSourceErrorMenu } from '../data_source_error_menu'; export interface DataSourceMultiSeletableProps { savedObjectsClient: SavedObjectsClientContract; @@ -19,7 +20,7 @@ export interface DataSourceMultiSeletableProps { uiSettings?: IUiSettingsClient; } -interface DataSourceMultiSeletableState { +interface DataSourceMultiSeletableState extends DataSourceBaseState { dataSourceOptions: SelectedDataSourceOption[]; selectedOptions: SelectedDataSourceOption[]; defaultDataSource: string | null; @@ -38,6 +39,7 @@ export class DataSourceMultiSelectable extends React.Component< dataSourceOptions: [], selectedOptions: [], defaultDataSource: null, + showError: false, }; } @@ -84,14 +86,18 @@ export class DataSourceMultiSelectable extends React.Component< this.props.onSelectedDataSources(selectedOptions); } catch (error) { - this.props.notifications.addWarning( - i18n.translate('dataSource.fetchDataSourceError', { - defaultMessage: 'Unable to fetch existing data sources', - }) + handleDataSourceFetchError( + this.onError.bind(this), + this.props.notifications, + this.props.onSelectedDataSources ); } } + onError() { + this.setState({ showError: true }); + } + onChange(selectedOptions: SelectedDataSourceOption[]) { if (!this._isMounted) return; this.setState({ @@ -101,6 +107,9 @@ export class DataSourceMultiSelectable extends React.Component< } render() { + if (this.state.showError) { + return ; + } return ( { label: 'test2', }, ], + showError: false, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -165,6 +166,7 @@ describe('DataSourceSelectable', () => { label: 'test2', }, ], + showError: false, }); expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); @@ -341,6 +343,7 @@ describe('DataSourceSelectable', () => { label: 'test2', }, ], + showError: false, }); }); @@ -362,12 +365,13 @@ describe('DataSourceSelectable', () => { const containerInstance = container.instance(); - expect(onSelectedDataSource).toBeCalledTimes(0); + expect(onSelectedDataSource).toBeCalledWith([]); expect(containerInstance.state).toEqual({ dataSourceOptions: [], defaultDataSource: null, isPopoverOpen: false, selectedOption: [], + showError: true, }); containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]); @@ -388,6 +392,7 @@ describe('DataSourceSelectable', () => { label: 'test2', }, ], + showError: true, }); 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 cf7c88526065..47e54fae671f 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 @@ -25,11 +25,17 @@ import { getDataSourcesWithFields, getDefaultDataSource, getFilteredDataSources, + handleDataSourceFetchError, } from '../utils'; import { LocalCluster } from '../data_source_selector/data_source_selector'; import { SavedObject } from '../../../../../core/public'; import { DataSourceAttributes } from '../../types'; -import { DataSourceGroupLabelOption, DataSourceOption } from '../data_source_menu/types'; +import { + DataSourceBaseState, + DataSourceGroupLabelOption, + DataSourceOption, +} from '../data_source_menu/types'; +import { DataSourceErrorMenu } from '../data_source_error_menu'; import { DataSourceItem } from '../data_source_item'; import './data_source_selectable.scss'; import { DataSourceDropDownHeader } from '../drop_down_header'; @@ -47,7 +53,7 @@ interface DataSourceSelectableProps { uiSettings?: IUiSettingsClient; } -interface DataSourceSelectableState { +interface DataSourceSelectableState extends DataSourceBaseState { dataSourceOptions: DataSourceOption[]; isPopoverOpen: boolean; selectedOption?: DataSourceOption[]; @@ -74,6 +80,7 @@ export class DataSourceSelectable extends React.Component< isPopoverOpen: false, selectedOption: [], defaultDataSource: null, + showError: false, }; this.onChange.bind(this); @@ -192,14 +199,18 @@ export class DataSourceSelectable extends React.Component< // handle default data source if there is no valid active option this.handleDefaultDataSource(dataSourceOptions, defaultDataSource); } catch (error) { - this.props.notifications.addWarning( - i18n.translate('dataSource.fetchDataSourceError', { - defaultMessage: 'Unable to fetch existing data sources', - }) + handleDataSourceFetchError( + this.onError.bind(this), + this.props.notifications, + this.props.onSelectedDataSources ); } } + onError() { + this.setState({ showError: true }); + } + onChange(options: DataSourceOption[]) { if (!this._isMounted) return; const optionsWithoutGroupLabel = options.filter( @@ -231,6 +242,9 @@ export class DataSourceSelectable extends React.Component< }; render() { + if (this.state.showError) { + return ; + } const button = ( <> `; -exports[`DataSourceView should call notification warning when there is data source fetch error 1`] = ` - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="dataSourceViewContextMenuPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - > - - - -`; +exports[`DataSourceView should call notification warning when there is data source fetch error 1`] = ``; exports[`DataSourceView should render normally with local cluster not hidden 1`] = ` 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 ab48e925d18f..420bb5927145 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 @@ -8,13 +8,14 @@ import { i18n } from '@osd/i18n'; import { EuiPopover, EuiButtonEmpty, EuiButtonIcon, EuiContextMenu } from '@elastic/eui'; import { SavedObjectsClientContract, ToastsStart } from 'opensearch-dashboards/public'; import { IUiSettingsClient } from 'src/core/public'; -import { DataSourceOption } from '../data_source_menu/types'; +import { DataSourceBaseState, DataSourceOption } from '../data_source_menu/types'; +import { MenuPanelItem } from '../../types'; +import { DataSourceErrorMenu } from '../data_source_error_menu'; import { getDataSourceById, handleDataSourceFetchError, handleNoAvailableDataSourceError, } from '../utils'; -import { MenuPanelItem } from '../../types'; import { LocalCluster } from '../constants'; interface DataSourceViewProps { @@ -28,7 +29,7 @@ interface DataSourceViewProps { onSelectedDataSources?: (dataSources: DataSourceOption[]) => void; } -interface DataSourceViewState { +interface DataSourceViewState extends DataSourceBaseState { selectedOption: DataSourceOption[]; isPopoverOpen: boolean; } @@ -42,6 +43,7 @@ export class DataSourceView extends React.Component; + } const { panels } = this.getPanels(); const button = ( 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 397f55333ed2..cc553037188b 100644 --- a/src/plugins/data_source_management/public/components/utils.test.ts +++ b/src/plugins/data_source_management/public/components/utils.test.ts @@ -76,7 +76,9 @@ describe('DataSourceManagement: Utils.ts', () => { const { toasts } = notificationServiceMock.createStartContract(); test('should send warning when data source fetch failed', () => { - handleDataSourceFetchError(toasts); + const changeStateMock = jest.fn(); + handleDataSourceFetchError(changeStateMock, toasts); + expect(changeStateMock).toBeCalledWith({ showError: true }); expect(toasts.addWarning).toHaveBeenCalledWith(`Failed to fetch data source`); }); }); diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index 98c23cc140c3..a9f428f5cfa7 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -2,7 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - +import { i18n } from '@osd/i18n'; import { HttpStart, SavedObjectsClientContract, @@ -11,7 +11,6 @@ import { ToastsStart, ApplicationStart, } from 'src/core/public'; -import { i18n } from '@osd/i18n'; import { deepFreeze } from '@osd/std'; import { DataSourceAttributes, @@ -85,14 +84,6 @@ export async function setFirstDataSourceAsDefault( } } -export function handleDataSourceFetchError(notifications: ToastsStart) { - notifications.addWarning( - i18n.translate('dataSource.fetchDataSourceError', { - defaultMessage: `Failed to fetch data source`, - }) - ); -} - export function handleNoAvailableDataSourceError(notifications: ToastsStart) { notifications.addWarning( i18n.translate('dataSource.noAvailableDataSourceError', { @@ -284,6 +275,20 @@ export const extractRegisteredAuthTypeCredentials = ( return registeredCredentials; }; +export const handleDataSourceFetchError = ( + changeState: (state: { showError: boolean }) => void, + notifications: ToastsStart, + callback?: (ds: DataSourceOption[]) => void +) => { + changeState({ showError: true }); + if (callback) callback([]); + notifications.addWarning( + i18n.translate('dataSource.fetchDataSourceError', { + defaultMessage: 'Failed to fetch data source', + }) + ); +}; + interface DataSourceOptionGroupLabel { [key: string]: DataSourceGroupLabelOption; }