Skip to content

Commit e2c0df2

Browse files
raintygaoopensearch-changeset-bot[bot]BionIT
authored
[Multiple Datasource] Add data source selection service to support storing and getting selected data source updates (#6827)
* add data source selection service Signed-off-by: tygao <[email protected]> * export generateComponentId in util Signed-off-by: tygao <[email protected]> * update tests and type Signed-off-by: tygao <[email protected]> * update tests Signed-off-by: tygao <[email protected]> * update bind in components Signed-off-by: tygao <[email protected]> * Changeset file for PR #6827 created/updated * test: add tests for service Signed-off-by: tygao <[email protected]> * move dataSourceSelection out of componoent props Signed-off-by: tygao <[email protected]> * update service class name Signed-off-by: tygao <[email protected]> * use getter to replace dataSourceSelection props Signed-off-by: tygao <[email protected]> * add fallback for getter Signed-off-by: tygao <[email protected]> * test: add selection test case for component Signed-off-by: tygao <[email protected]> --------- Signed-off-by: tygao <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Co-authored-by: Lu Yu <[email protected]>
1 parent bfe0e14 commit e2c0df2

26 files changed

+574
-45
lines changed

changelogs/fragments/6827.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
feat:
2+
- Add data source selection service to support storing and getting selected data source updates ([#6827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6827))

examples/multiple_data_source_examples/public/components/data_source_view_example.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CoreStart, MountPoint } from 'opensearch-dashboards/public';
1818
import {
1919
DataSourceManagementPluginSetup,
2020
DataSourceViewConfig,
21+
DataSourceSelectionService,
2122
} from 'src/plugins/data_source_management/public';
2223
import { ComponentProp } from './types';
2324
import { COLUMNS } from './constants';
@@ -88,6 +89,7 @@ export const DataSourceViewExample = ({
8889
setSelectedDataSources(ds);
8990
},
9091
}}
92+
dataSourceSelection={new DataSourceSelectionService()}
9193
/>
9294
);
9395
}, [setActionMenu, notifications, savedObjects]);

src/plugins/data_source_management/public/components/data_source_aggregated_view/__snapshots__/data_source_aggregated_view.test.tsx.snap

+103
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.test.tsx

+57
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ import {
2828
NO_COMPATIBLE_DATASOURCES_MESSAGE,
2929
NO_DATASOURCES_CONNECTED_MESSAGE,
3030
} from '../constants';
31+
import { DataSourceSelectionService } from '../../service/data_source_selection_service';
3132

3233
describe('DataSourceAggregatedView: read all view (displayAllCompatibleDataSources is set to true)', () => {
3334
let component: ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
3435
let client: SavedObjectsClientContract;
3536
const { toasts } = notificationServiceMock.createStartContract();
3637
const uiSettings = uiSettingsServiceMock.createStartContract();
3738
const application = applicationServiceMock.createStartContract();
39+
const dataSourceSelection = new DataSourceSelectionService();
3840
const nextTick = () => new Promise((res) => process.nextTick(res));
3941

4042
beforeEach(() => {
@@ -44,6 +46,7 @@ describe('DataSourceAggregatedView: read all view (displayAllCompatibleDataSourc
4446
mockResponseForSavedObjectsCalls(client, 'find', getDataSourcesWithFieldsResponse);
4547
mockUiSettingsCalls(uiSettings, 'get', 'test1');
4648
jest.spyOn(utils, 'getApplication').mockReturnValue(application);
49+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
4750
});
4851

4952
it.each([
@@ -163,6 +166,7 @@ describe('DataSourceAggregatedView: read active view (displayAllCompatibleDataSo
163166
let client: SavedObjectsClientContract;
164167
const { toasts } = notificationServiceMock.createStartContract();
165168
const uiSettings = uiSettingsServiceMock.createStartContract();
169+
const dataSourceSelection = new DataSourceSelectionService();
166170
const nextTick = () => new Promise((res) => process.nextTick(res));
167171

168172
beforeEach(() => {
@@ -171,6 +175,7 @@ describe('DataSourceAggregatedView: read active view (displayAllCompatibleDataSo
171175
} as any;
172176
mockResponseForSavedObjectsCalls(client, 'find', getDataSourcesWithFieldsResponse);
173177
mockUiSettingsCalls(uiSettings, 'get', 'test1');
178+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
174179
});
175180

176181
it.each([
@@ -284,6 +289,7 @@ describe('DataSourceAggregatedView empty state test with local cluster hiding',
284289
const { toasts } = notificationServiceMock.createStartContract();
285290
const uiSettings = uiSettingsServiceMock.createStartContract();
286291
const application = applicationServiceMock.createStartContract();
292+
const dataSourceSelection = new DataSourceSelectionService();
287293
const nextTick = () => new Promise((res) => process.nextTick(res));
288294

289295
beforeEach(() => {
@@ -293,6 +299,7 @@ describe('DataSourceAggregatedView empty state test with local cluster hiding',
293299
mockResponseForSavedObjectsCalls(client, 'find', {});
294300
mockUiSettingsCalls(uiSettings, 'get', 'test1');
295301
jest.spyOn(utils, 'getApplication').mockReturnValue(application);
302+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
296303
});
297304

298305
afterEach(() => {
@@ -369,6 +376,7 @@ describe('DataSourceAggregatedView empty state test due to filter out with local
369376
const { toasts } = notificationServiceMock.createStartContract();
370377
const uiSettings = uiSettingsServiceMock.createStartContract();
371378
const application = applicationServiceMock.createStartContract();
379+
const dataSourceSelection = new DataSourceSelectionService();
372380
const nextTick = () => new Promise((res) => process.nextTick(res));
373381

374382
beforeEach(() => {
@@ -378,6 +386,7 @@ describe('DataSourceAggregatedView empty state test due to filter out with local
378386
mockResponseForSavedObjectsCalls(client, 'find', getDataSourcesWithFieldsResponse);
379387
mockUiSettingsCalls(uiSettings, 'get', 'test1');
380388
jest.spyOn(utils, 'getApplication').mockReturnValue(application);
389+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
381390
});
382391

383392
afterEach(() => {
@@ -439,6 +448,7 @@ describe('DataSourceAggregatedView error state test no matter hide local cluster
439448
const { toasts } = notificationServiceMock.createStartContract();
440449
const uiSettings = uiSettingsServiceMock.createStartContract();
441450
const application = applicationServiceMock.createStartContract();
451+
const dataSourceSelection = new DataSourceSelectionService();
442452
const nextTick = () => new Promise((res) => process.nextTick(res));
443453

444454
beforeEach(() => {
@@ -448,6 +458,7 @@ describe('DataSourceAggregatedView error state test no matter hide local cluster
448458
mockErrorResponseForSavedObjectsCalls(client, 'find');
449459
mockUiSettingsCalls(uiSettings, 'get', 'test1');
450460
jest.spyOn(utils, 'getApplication').mockReturnValue(application);
461+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
451462
});
452463

453464
afterEach(() => {
@@ -514,6 +525,7 @@ describe('DataSourceAggregatedView error state test no matter hide local cluster
514525
describe('DataSourceAggregatedView warning messages', () => {
515526
const client = {} as any;
516527
const uiSettings = uiSettingsServiceMock.createStartContract();
528+
const dataSourceSelection = new DataSourceSelectionService();
517529
const nextTick = () => new Promise((res) => process.nextTick(res));
518530
let toasts: IToasts;
519531
const noDataSourcesConnectedMessage = `${NO_DATASOURCES_CONNECTED_MESSAGE} ${CONNECT_DATASOURCES_MESSAGE}`;
@@ -522,6 +534,7 @@ describe('DataSourceAggregatedView warning messages', () => {
522534
beforeEach(() => {
523535
toasts = notificationServiceMock.createStartContract().toasts;
524536
mockUiSettingsCalls(uiSettings, 'get', 'test1');
537+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
525538
});
526539

527540
it.each([
@@ -571,3 +584,47 @@ describe('DataSourceAggregatedView warning messages', () => {
571584
}
572585
);
573586
});
587+
588+
describe('DataSourceAggregatedView: dataSourceSelection)', () => {
589+
let client: SavedObjectsClientContract;
590+
const { toasts } = notificationServiceMock.createStartContract();
591+
const uiSettings = uiSettingsServiceMock.createStartContract();
592+
const dataSourceSelection = new DataSourceSelectionService();
593+
dataSourceSelection.selectDataSource = jest.fn();
594+
const nextTick = () => new Promise((res) => process.nextTick(res));
595+
const activeDataSourceIds = ['test1', 'test2'];
596+
const selectedOptions = [
597+
{ checked: 'on', disabled: true, id: 'test1', label: 'test1' },
598+
{ checked: 'on', disabled: true, id: 'test2', label: 'test2' },
599+
];
600+
const componentId = 'component-id';
601+
beforeEach(() => {
602+
client = {
603+
find: jest.fn().mockResolvedValue([]),
604+
} as any;
605+
mockResponseForSavedObjectsCalls(client, 'find', getDataSourcesWithFieldsResponse);
606+
mockUiSettingsCalls(uiSettings, 'get', 'test1');
607+
jest.spyOn(utils, 'getDataSourceSelection').mockReturnValue(dataSourceSelection);
608+
jest.spyOn(utils, 'generateComponentId').mockReturnValue(componentId);
609+
});
610+
611+
it('should render normally and call selectDataSource', async () => {
612+
const component = shallow(
613+
<DataSourceAggregatedView
614+
fullWidth={false}
615+
hideLocalCluster={false}
616+
savedObjectsClient={client}
617+
notifications={toasts}
618+
displayAllCompatibleDataSources={false}
619+
activeDataSourceIds={activeDataSourceIds}
620+
uiSettings={uiSettings}
621+
/>
622+
);
623+
624+
// Should render normally
625+
expect(component).toMatchSnapshot();
626+
await nextTick();
627+
628+
expect(dataSourceSelection.selectDataSource).toHaveBeenCalledWith(componentId, selectedOptions);
629+
});
630+
});

src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
getDataSourcesWithFields,
1717
handleDataSourceFetchError,
1818
handleNoAvailableDataSourceError,
19+
generateComponentId,
20+
getDataSourceSelection,
1921
} from '../utils';
2022
import { SavedObject } from '../../../../../core/public';
2123
import { DataSourceAttributes } from '../../types';
@@ -46,6 +48,7 @@ interface DataSourceAggregatedViewState extends DataSourceBaseState {
4648
switchChecked: boolean;
4749
defaultDataSource: string | null;
4850
incompatibleDataSourcesExist: boolean;
51+
componentId: string;
4952
}
5053

5154
interface DataSourceOptionDisplay extends DataSourceOption {
@@ -70,11 +73,13 @@ export class DataSourceAggregatedView extends React.Component<
7073
switchChecked: false,
7174
defaultDataSource: null,
7275
incompatibleDataSourcesExist: false,
76+
componentId: generateComponentId(),
7377
};
7478
}
7579

7680
componentWillUnmount() {
7781
this._isMounted = false;
82+
getDataSourceSelection().remove(this.state.componentId);
7883
}
7984

8085
onDataSourcesClick() {
@@ -188,7 +193,11 @@ export class DataSourceAggregatedView extends React.Component<
188193
});
189194
}
190195

191-
const numSelectedItems = items.filter((item) => item.checked === 'on').length;
196+
const selectedItems = items.filter((item) => item.checked === 'on');
197+
// For read-only cases, also need to set default selected result.
198+
getDataSourceSelection().selectDataSource(this.state.componentId, selectedItems);
199+
200+
const numSelectedItems = selectedItems.length;
192201

193202
const titleComponent = (
194203
<DataSourceDropDownHeader

src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.test.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import { act, render } from '@testing-library/react';
1515
import { DataSourceComponentType, DataSourceSelectableConfig } from './types';
1616
import { ReactWrapper } from 'enzyme';
1717
import * as utils from '../utils';
18+
import { DataSourceSelectionService } from '../../service/data_source_selection_service';
1819

1920
describe('create data source menu', () => {
2021
let client: SavedObjectsClientContract;
2122
const notifications = notificationServiceMock.createStartContract();
2223
const { uiSettings } = coreMock.createSetup();
24+
const dataSourceSelection = new DataSourceSelectionService();
2325

2426
beforeAll(() => {
2527
jest
@@ -47,6 +49,8 @@ describe('create data source menu', () => {
4749
spyOn(utils, 'getApplication').and.returnValue({ id: 'test2' });
4850
spyOn(utils, 'getUiSettings').and.returnValue(uiSettings);
4951
spyOn(utils, 'getHideLocalCluster').and.returnValue({ enabled: true });
52+
spyOn(utils, 'getDataSourceSelection').and.returnValue(dataSourceSelection);
53+
5054
const TestComponent = createDataSourceMenu<DataSourceSelectableConfig>();
5155

5256
const component = render(<TestComponent {...props} />);
@@ -74,6 +78,7 @@ describe('create data source menu', () => {
7478
spyOn(utils, 'getApplication').and.returnValue({ id: 'test2' });
7579
spyOn(utils, 'getUiSettings').and.returnValue(uiSettings);
7680
spyOn(utils, 'getHideLocalCluster').and.returnValue({ enabled: true });
81+
spyOn(utils, 'getDataSourceSelection').and.returnValue(dataSourceSelection);
7782
const TestComponent = createDataSourceMenu<DataSourceSelectableConfig>();
7883
await act(async () => {
7984
component = render(<TestComponent {...props} />);
@@ -98,6 +103,7 @@ describe('when setMenuMountPoint is provided', () => {
98103
let client: SavedObjectsClientContract;
99104
const notifications = notificationServiceMock.createStartContract();
100105
const { uiSettings } = coreMock.createSetup();
106+
const dataSourceSelection = new DataSourceSelectionService();
101107

102108
const refresh = () => {
103109
new Promise(async (resolve) => {
@@ -141,6 +147,7 @@ describe('when setMenuMountPoint is provided', () => {
141147
spyOn(utils, 'getApplication').and.returnValue({ id: 'test2' });
142148
spyOn(utils, 'getUiSettings').and.returnValue(uiSettings);
143149
spyOn(utils, 'getHideLocalCluster').and.returnValue({ enabled: true });
150+
spyOn(utils, 'getDataSourceSelection').and.returnValue(dataSourceSelection);
144151

145152
const TestComponent = createDataSourceMenu<DataSourceSelectableConfig>();
146153
const component = render(<TestComponent {...props} />);

src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export function createDataSourceMenu<T>() {
1414
const application = getApplication();
1515
const uiSettings = getUiSettings();
1616
const hideLocalCluster = getHideLocalCluster().enabled;
17-
return (props: DataSourceMenuProps<T>) => {
17+
return (
18+
props: Omit<DataSourceMenuProps<T>, 'uiSettings' | 'hideLocalCluster' | 'application'>
19+
) => {
1820
if (props.setMenuMountPoint) {
1921
return (
2022
<MountPointPortal setMountPoint={props.setMenuMountPoint}>

0 commit comments

Comments
 (0)