Skip to content

Commit

Permalink
add invalid id handling for DataSourceView
Browse files Browse the repository at this point in the history
Signed-off-by: yujin-emma <yujin.emma.work@gmail.com>
  • Loading branch information
yujin-emma committed May 3, 2024
1 parent 17bf1ff commit 1a3d93c
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 71 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,13 @@ describe('DataSourceView', () => {
onSelectedDataSources={jest.fn()}
/>
);
await nextTick();
expect(component).toMatchSnapshot();
expect(toasts.add).toBeCalledTimes(1);
expect(toasts.add).toBeCalledWith({
color: 'danger',
text: '',
title: 'Data source test1 is not available ',
});
expect(utils.getDataSourceById).toBeCalledTimes(1);
});

Expand Down Expand Up @@ -162,6 +167,7 @@ describe('DataSourceView', () => {
dataSourceFilter={(ds) => false}
/>
);
await nextTick();
expect(onSelectedDataSource).toBeCalledWith([]);
});

Expand All @@ -179,7 +185,11 @@ describe('DataSourceView', () => {
const button = await container.findByTestId('dataSourceViewErrorPopover');
button.click();
expect(component).toMatchSnapshot();
expect(toasts.add).toBeCalledTimes(1);
expect(toasts.add).toBeCalledWith({
color: 'danger',
text: '',
title: 'Data source any id is not available ',
});
expect(utils.getDataSourceById).toBeCalledTimes(1);
expect(container.getByTestId('dataSourceViewErrorHeaderLink')).toBeVisible();
});
Expand All @@ -199,8 +209,12 @@ describe('DataSourceView', () => {
const button = await container.findByTestId('dataSourceViewErrorPopover');
button.click();
expect(component).toMatchSnapshot();
expect(toasts.add).toBeCalledTimes(1);
expect(utils.getDataSourceById).toBeCalledTimes(1);
expect(toasts.add).toBeCalledWith({
color: 'danger',
text: '',
title: 'Data source any id is not available ',
});
expect(container.getByTestId('dataSourceViewErrorHeaderLink')).toBeVisible();
const errorHeaderLink = await container.findByTestId('dataSourceViewErrorHeaderLink');
errorHeaderLink.click();
Expand Down Expand Up @@ -234,6 +248,7 @@ describe('DataSourceView', () => {
showEmptyState: false,
showError: false,
defaultDataSource: null,
defaultDataSourceOption: null,
});
});

Expand Down Expand Up @@ -261,10 +276,11 @@ describe('DataSourceView', () => {
showEmptyState: false,
showError: false,
defaultDataSource: null,
defaultDataSourceOption: null,
});
});

it('should showError when pass in invalid data source id ', async () => {
it('should showError when pass in invalid data source id ', async () => {
spyOn(utils, 'getDataSourceById').and.throwError('Data source is not available');
spyOn(uiSettings, 'get').and.returnValue('test2');

Expand All @@ -286,6 +302,7 @@ describe('DataSourceView', () => {
showEmptyState: false,
showError: true,
defaultDataSource: null,
defaultDataSourceOption: null,
});
});

Expand All @@ -304,6 +321,7 @@ describe('DataSourceView', () => {
}}
/>
);
await nextTick();
const instance = container.instance();
await nextTick();
expect(instance.state).toEqual({
Expand All @@ -312,6 +330,7 @@ describe('DataSourceView', () => {
showEmptyState: false,
showError: true,
defaultDataSource: null,
defaultDataSourceOption: null,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
ApplicationStart,
} from 'opensearch-dashboards/public';
import { IUiSettingsClient } from 'src/core/public';
import { DataSource } from 'src/plugins/data/public';
import { DataSourceBaseState, DataSourceOption } from '../data_source_menu/types';
import {
DataSourceViewErrorWithDefaultParams,
Expand Down Expand Up @@ -48,7 +47,8 @@ interface DataSourceViewProps {
interface DataSourceViewState extends DataSourceBaseState {
selectedOption: DataSourceOption[];
isPopoverOpen: boolean;
defaultDataSource: DataSourceOption | null;
defaultDataSource: string | null;
defaultDataSourceOption: DataSourceOption | null;
}

export class DataSourceView extends React.Component<DataSourceViewProps, DataSourceViewState> {
Expand All @@ -63,90 +63,125 @@ export class DataSourceView extends React.Component<DataSourceViewProps, DataSou
showEmptyState: false,
showError: false,
defaultDataSource: null,
defaultDataSourceOption: null,
};
}

componentWillUnmount() {
this._isMounted = false;
}
async componentDidMount() {
this._isMounted = true;
updateSelectedOptionState(defaultDataSource: string | null, dataSourceOption: DataSourceOption) {
this.setState({
selectedOption: [dataSourceOption],
defaultDataSource,
});
this.props.onSelectedDataSources?.([dataSourceOption]);
}

const selectedOption = this.props.selectedOption;
const option = selectedOption[0];
const optionId = option.id;
// handleLegitLocalCluster(defaultDataSource: string | null) {
// this.setState({
// selectedOption: [LocalCluster],
// defaultDataSource,
// });
// this.props.onSelectedDataSources?.([LocalCluster]);
// }

const defaultDataSourceId = this.props.uiSettings?.get('defaultDataSource', null) ?? null;
const defaultDataSourceObj = defaultDataSourceId
? await this.getDefaultDataSourceObj(defaultDataSourceId)
: null;
const filteredDefaultDataSourceOption = this.getFilteredDataSource(defaultDataSourceObj);
clearSelectedOptionState() {
this.setState({ selectedOption: [] });
this.props.onSelectedDataSources?.([]);
}

if (optionId === '' && !this.props.hideLocalCluster) {
this.setState({
selectedOption: [LocalCluster],
defaultDataSource: filteredDefaultDataSourceOption,
});
if (this.props.onSelectedDataSources) {
this.props.onSelectedDataSources([LocalCluster]);
}
return;
async getAndSetDefaultDataSourceOption(dataSourceId: string, defaultDataSource: string | null) {
try {
const defaultDataSourceObj = defaultDataSource
? await this.getDefaultDataSourceObj(defaultDataSource)
: null;
const filteredDefaultDataSourceOption = this.getFilteredDataSource(defaultDataSourceObj);
this.setState({ defaultDataSourceOption: filteredDefaultDataSourceOption });
} catch (error) {
this.handleInvalidDataSourceError(dataSourceId);

Check warning on line 102 in src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx#L102

Added line #L102 was not covered by tests
}
}

async getSelectedDataSource(dataSourceId: string, defaultDataSource: string | null) {
try {
return await getDataSourceById(dataSourceId, this.props.savedObjectsClient!);
} catch (error) {
await this.getAndSetDefaultDataSourceOption(dataSourceId, defaultDataSource);
this.handleInvalidDataSourceError(dataSourceId);
}
}

if (
(optionId === '' && this.props.hideLocalCluster) ||
(this.props.dataSourceFilter && !this.props.dataSourceFilter(this.props.selectedOption))
) {
handleInvalidDataSourceError(dataSourceId: string) {
const handleDataSourceViewErrorParams = this.getDataSourceViewErrorParams(
dataSourceId,
this.state.defaultDataSourceOption
);
handleDataSourceViewErrorWithSwitchToDefaultOption(handleDataSourceViewErrorParams);
}

async handleDataSourceLabelOrBackupWithDefaultOption(
dataSourceId: string,
defaultDataSource: string | null
) {
const selectedDataSource = await this.getSelectedDataSource(dataSourceId, defaultDataSource);
if (!this._isMounted) return;
const filteredSelectedDataSourceOption = this.getFilteredDataSource(selectedDataSource);
// if the selectedOption has been filtered out, treat it as invalid id error and early return
if (filteredSelectedDataSourceOption) {
this.setState({
selectedOption: [],
selectedOption: [filteredSelectedDataSourceOption],
defaultDataSource,
});
if (this.props.onSelectedDataSources) {
this.props.onSelectedDataSources([]);
}
return;
}

const handleDataSourceViewErrorParams = {
await this.getAndSetDefaultDataSourceOption(dataSourceId, defaultDataSource);
this.handleInvalidDataSourceError(dataSourceId);
}

getDataSourceViewErrorParams(
dataSourceId: string,
filteredDefaultDataSourceOption: DataSourceOption | null
) {
return {
changeState: this.onError.bind(this, filteredDefaultDataSourceOption),
notifications: this.props.notifications!,
failedDataSourceId: optionId,
failedDataSourceId: dataSourceId,
defaultDataSourceOption: filteredDefaultDataSourceOption,
handleSwitch: this.handleSwitchDefaultDatasource.bind(this, filteredDefaultDataSourceOption),
callback: this.props.onSelectedDataSources,
} as DataSourceViewErrorWithDefaultParams;
}
async componentDidMount() {
this._isMounted = true;

if (!option.label) {
try {
const selectedDataSource = await getDataSourceById(
optionId,
this.props.savedObjectsClient!
);
if (!this._isMounted) return;
const filteredSelectedDataSourceOption = this.getFilteredDataSource(selectedDataSource);
const selectedOption = this.props.selectedOption;
const option = selectedOption[0];
const optionId = option.id;
const defaultDataSource = this.props.uiSettings?.get('defaultDataSource', null) ?? null;

// if the selectedOption has been filtered out, treat it as invalid id error and early return
if (!filteredSelectedDataSourceOption) {
handleDataSourceViewErrorWithSwitchToDefaultOption(handleDataSourceViewErrorParams);
return;
}
this.setState({
selectedOption: [filteredSelectedDataSourceOption],
defaultDataSource: filteredDefaultDataSourceOption,
});
if (this.props.onSelectedDataSources) {
this.props.onSelectedDataSources([{ id: optionId, label: selectedDataSource.title }]);
}
} catch (error) {
// handle fetch data source error and provide switch to default option
handleDataSourceViewErrorWithSwitchToDefaultOption(handleDataSourceViewErrorParams);
// 1. id and label both exist, set defaultDataSource directly and early return
if (this.isCompletDataSourceOption(option)) {
this.updateSelectedOptionState(defaultDataSource, option);
return;
}
// 2. handle local cluster
if (option && optionId === '') {
// 2.1 handle legit
if (!this.props.hideLocalCluster) {
this.updateSelectedOptionState(defaultDataSource, LocalCluster);
return;

Check warning on line 174 in src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx#L173-L174

Added lines #L173 - L174 were not covered by tests
}
} else if (this.props.onSelectedDataSources) {
this.setState({
...this.state,
defaultDataSource: filteredDefaultDataSourceOption,
});
this.props.onSelectedDataSources([option]);
this.clearSelectedOptionState();
return;
}
// 3. only label exit
this.handleDataSourceLabelOrBackupWithDefaultOption(optionId, defaultDataSource);
}

isCompletDataSourceOption(option: DataSourceOption): boolean {
return !!(option && option.id && option.label);
}

/**
Expand All @@ -166,10 +201,10 @@ export class DataSourceView extends React.Component<DataSourceViewProps, DataSou

/**
* set the showError state and also default data source option when handle the error at get data source
* @param defaultDataSourceOption
* @param defaultDataSourceOption provided as required since switch to default option happened with the error
*/
onError(defaultDataSourceOption: DataSourceOption | null) {
this.setState({ showError: true, defaultDataSource: defaultDataSourceOption });
this.setState({ showError: true, defaultDataSourceOption });
}

onClick() {
Expand All @@ -196,7 +231,7 @@ export class DataSourceView extends React.Component<DataSourceViewProps, DataSou
// reset the state to close popover and error, selectedOption will be replaced by default option
if (!defaultDataSourceOption) return;
this.setState({
selectedOption: [defaultDataSourceOption!],
selectedOption: [defaultDataSourceOption],
showError: false,
isPopoverOpen: false,
});
Expand All @@ -210,7 +245,7 @@ export class DataSourceView extends React.Component<DataSourceViewProps, DataSou
dataSourceId={this.props.selectedOption[0].id}
showSwitchButton={!!this.state.defaultDataSource}
handleSwitchDefaultDatasource={() =>
this.handleSwitchDefaultDatasource(this.state.defaultDataSource)
this.handleSwitchDefaultDatasource(this.state.defaultDataSourceOption)

Check warning on line 248 in src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/components/data_source_view/data_source_view.tsx#L248

Added line #L248 was not covered by tests
}
/>
);
Expand Down Expand Up @@ -253,7 +288,7 @@ export class DataSourceView extends React.Component<DataSourceViewProps, DataSou
renderOption={(option) => (
<DataSourceItem
option={option}
defaultDataSource={this.state.defaultDataSource?.id || null}
defaultDataSource={this.state.defaultDataSource}
className={'dataSourceView'}
/>
)}
Expand Down

0 comments on commit 1a3d93c

Please sign in to comment.