Skip to content

Commit

Permalink
[Discover] support custom callback to combine multiple dataset select…
Browse files Browse the repository at this point in the history
…ions (opensearch-project#8494)

Signed-off-by: Joshua Li <[email protected]>
  • Loading branch information
joshuali925 authored Oct 8, 2024
1 parent 3ae4cdc commit dd67d96
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ export interface DatasetTypeConfig {
* with this Dataset
*/
getSearchOptions?: () => DatasetSearchOptions;
/**
* Combines a list of user selected data structures into a single one to use in discover.
* @see https://github.com/opensearch-project/OpenSearch-Dashboards/issues/8362.
*/
combineDataStructures?: (dataStructures: DataStructure[]) => DataStructure | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export const DatasetExplorer = ({
</EuiTitle>
{current.multiSelect ? (
<DatasetTable
services={services}
path={path}
setPath={setPath}
index={index}
Expand Down
32 changes: 24 additions & 8 deletions src/plugins/data/public/ui/dataset_selector/dataset_table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React, { ComponentProps } from 'react';
import { IntlProvider } from 'react-intl';
import { DataStructure } from '../../../common';
import { CoreStart } from 'src/core/public';
import { DataPublicPluginStart, IDataPluginServices } from '../..';
import { DataStorage, DataStructure } from '../../../common';
import { queryServiceMock } from '../../query/mocks';
import { getQueryService } from '../../services';
import { DatasetTable } from './dataset_table';
Expand Down Expand Up @@ -35,7 +37,26 @@ describe('DataSetTable', () => {
},
];

const mockServices: IDataPluginServices = {
appName: 'testApp',
uiSettings: {} as CoreStart['uiSettings'],
savedObjects: {} as CoreStart['savedObjects'],
notifications: ({
toasts: {
addSuccess: jest.fn(),
addError: jest.fn(),
},
} as unknown) as CoreStart['notifications'],
http: {} as CoreStart['http'],
storage: {} as DataStorage,
data: {} as DataPublicPluginStart,
overlays: ({
openModal: jest.fn(),
} as unknown) as CoreStart['overlays'],
};

const mockProps: ComponentProps<typeof DatasetTable> = {
services: mockServices,
path: mockPath,
setPath: jest.fn(),
index: 2,
Expand Down Expand Up @@ -79,17 +100,12 @@ describe('DataSetTable', () => {
});

it('calls selectDataStructure with undefined when all items are deselected', async () => {
const propsWithSelection = {
...mockProps,
explorerDataset: { id: 'child1,child2', title: 'Child 1,Child 2', type: 'index' },
};
renderWithIntl(<DatasetTable {...propsWithSelection} />);
renderWithIntl(<DatasetTable {...mockProps} />);

const checkbox1 = screen.getByTestId('checkboxSelectRow-child1');
const checkbox2 = screen.getByTestId('checkboxSelectRow-child2');

fireEvent.click(checkbox1);
fireEvent.click(checkbox2);
fireEvent.click(checkbox1);

await waitFor(() => {
expect(mockProps.selectDataStructure).toHaveBeenCalledWith(undefined, mockPath.slice(0, 3));
Expand Down
56 changes: 33 additions & 23 deletions src/plugins/data/public/ui/dataset_selector/dataset_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
import { EuiBasicTable, EuiFieldSearch, EuiLink, EuiText } from '@elastic/eui';
import React, { useRef, useState } from 'react';
import { FormattedMessage } from '@osd/i18n/react';
import { i18n } from '@osd/i18n';
import { DataStructure } from '../../../common';
import { getQueryService } from '../../services';
import { DataStructureFetchOptions } from '../../query';
import { DatasetTypeConfig, DataStructureFetchOptions } from '../../query';
import { IDataPluginServices } from '../..';

interface DatasetTableProps {
services: IDataPluginServices;
path: DataStructure[];
setPath: (newPath: DataStructure[]) => void;
index: number;
Expand All @@ -28,7 +31,6 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
const [loading, setLoading] = useState(false);
const searchRef = useRef<HTMLInputElement | null>(null);

const initialSelectedIds = props.explorerDataset?.id.split(',');
const dataStructures = props.path[props.index].children || [];
const paginationToken = props.path[props.index].paginationToken;

Expand All @@ -45,6 +47,34 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
.finally(() => setLoading(false));
};

const onSelectionChange = (items: DataStructure[]) => {
if (items.length === 0) {
props.selectDataStructure(undefined, props.path.slice(0, props.index + 1));
return;
}
if (!items.every((item) => item.type === items[0].type)) {
props.services.notifications.toasts.addWarning(
i18n.translate(
'data.explorer.datasetSelector.advancedSelector.datasetTable.multipleItemTypeMessage',
{
defaultMessage: 'All selected datasets must be of the same type.',
}
)
);
return;
}
const typeConfig = datasetService.getType(props.path[1].id);
const combineDataStructures: NonNullable<DatasetTypeConfig['combineDataStructures']> =
typeConfig?.combineDataStructures ??
((ds) => ({
id: ds.map((item) => item.id).join(','),
title: ds.map((item) => item.title).join(','),
type: ds[0].type,
}));

props.selectDataStructure(combineDataStructures(items), props.path.slice(0, props.index + 1));
};

return (
<div className="datasetTable">
<EuiFieldSearch
Expand All @@ -59,27 +89,7 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
columns={[{ field: 'title', name: 'Name' }]}
loading={loading}
isSelectable
selection={{
onSelectionChange: (items) => {
if (items.length === 0) {
props.selectDataStructure(undefined, props.path.slice(0, props.index + 1));
return;
}
if (!items.every((item) => item.type === items[0].type)) {
throw new Error('All items must be of the same type');
}
const newItem: DataStructure = {
id: items.map((item) => item.id).join(','),
title: items.map((item) => item.title).join(','),
type: items[0].type,
};
props.selectDataStructure(newItem, props.path.slice(0, props.index + 1));
},
initialSelected:
(initialSelectedIds?.length &&
dataStructures?.filter((item) => initialSelectedIds.includes(item.id))) ||
[],
}}
selection={{ onSelectionChange }}
/>

{paginationToken && (
Expand Down

0 comments on commit dd67d96

Please sign in to comment.