Skip to content

Commit

Permalink
Modify toast + popover warning to include incompatible datasources
Browse files Browse the repository at this point in the history
Signed-off-by: Huy Nguyen <[email protected]>
  • Loading branch information
huyaboo committed Apr 29, 2024
1 parent de6a889 commit 67e6976
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.';
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,6 +21,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<any, Readonly<{}>, React.Component<{}, {}, any>>;
Expand Down Expand Up @@ -269,3 +276,64 @@ describe('DataSourceAggregatedView: read active view (displayAllCompatibleDataSo
}
);
});

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(
<DataSourceAggregatedView
fullWidth={false}
hideLocalCluster={true}
savedObjectsClient={client}
notifications={toasts}
displayAllCompatibleDataSources={!!!activeDataSourceIds}
activeDataSourceIds={activeDataSourceIds}
dataSourceFilter={(_) => false}
uiSettings={uiSettings}
/>
);
await nextTick();

expect(toasts.add).toBeCalledWith(
expect.objectContaining({
title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }),
})
);
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface DataSourceAggregatedViewState extends DataSourceBaseState {
allDataSourcesIdToTitleMap: Map<string, any>;
switchChecked: boolean;
defaultDataSource: string | null;
hasIncompatibleDataSources: boolean;
}

interface DataSourceOptionDisplay extends DataSourceOption {
Expand All @@ -68,6 +69,7 @@ export class DataSourceAggregatedView extends React.Component<
showError: false,
switchChecked: false,
defaultDataSource: null,
hasIncompatibleDataSources: false,
};
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -133,8 +136,8 @@ export class DataSourceAggregatedView extends React.Component<
});
}

onEmptyState() {
this.setState({ showEmptyState: true });
onEmptyState(hasIncompatibleDataSources: boolean) {
this.setState({ showEmptyState: true, hasIncompatibleDataSources });
}

onError() {
Expand All @@ -143,7 +146,12 @@ export class DataSourceAggregatedView extends React.Component<

render() {
if (this.state.showEmptyState) {
return <NoDataSource application={this.props.application} />;
return (
<NoDataSource
application={this.props.application}
hasIncompatibleDatasources={this.state.hasIncompatibleDataSources}
/>
);
}
if (this.state.showError) {
return <DataSourceErrorMenu application={this.props.application} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface DataSourceMultiSeletableState extends DataSourceBaseState {
dataSourceOptions: SelectedDataSourceOption[];
selectedOptions: SelectedDataSourceOption[];
defaultDataSource: string | null;
hasIncompatibleDatasources: boolean;
}

export class DataSourceMultiSelectable extends React.Component<
Expand All @@ -51,6 +52,7 @@ export class DataSourceMultiSelectable extends React.Component<
defaultDataSource: null,
showEmptyState: false,
showError: false,
hasIncompatibleDatasources: false,
};
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -115,8 +118,8 @@ export class DataSourceMultiSelectable extends React.Component<
}
}

onEmptyState() {
this.setState({ showEmptyState: true });
onEmptyState(hasIncompatibleDatasources: boolean) {
this.setState({ showEmptyState: true, hasIncompatibleDatasources });
}

onError() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -12,13 +13,21 @@ 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<any, Readonly<{}>, React.Component<{}, {}, any>>;

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 = {
Expand Down Expand Up @@ -145,6 +154,7 @@ describe('DataSourceSelectable', () => {
},
],
showError: false,
hasIncompatibleDatasources: false,
});

containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]);
Expand All @@ -167,6 +177,7 @@ describe('DataSourceSelectable', () => {
},
],
showError: false,
hasIncompatibleDatasources: false,
});

expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]);
Expand Down Expand Up @@ -345,6 +356,7 @@ describe('DataSourceSelectable', () => {
},
],
showError: false,
hasIncompatibleDatasources: false,
});
});

Expand Down Expand Up @@ -374,6 +386,7 @@ describe('DataSourceSelectable', () => {
selectedOption: [],
showEmptyState: false,
showError: true,
hasIncompatibleDatasources: false,
});

containerInstance.onChange([{ id: 'test2', label: 'test2', checked: 'on' }]);
Expand All @@ -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(
<DataSourceSelectable
savedObjectsClient={client}
notifications={toasts}
onSelectedDataSources={onSelectedDataSource}
disabled={false}
hideLocalCluster={true}
fullWidth={false}
selectedOption={[{ id: 'test2' }]}
dataSourceFilter={(ds) => 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(
<DataSourceSelectable
savedObjectsClient={client}
notifications={toasts}
onSelectedDataSources={onSelectedDataSource}
disabled={false}
hideLocalCluster={true}
fullWidth={false}
selectedOption={selectedOption}
dataSourceFilter={(ds) => false}
/>
);
await nextTick();

expect(toasts.add).toBeCalledWith(
expect.objectContaining({
title: i18n.translate('dataSource.noAvailableDataSourceError', { defaultMessage }),
})
);
expect(onSelectedDataSource).toBeCalledWith([]);
}
);
});
Loading

0 comments on commit 67e6976

Please sign in to comment.