Skip to content

Commit

Permalink
Modify seletable picker in datasource management to show the default …
Browse files Browse the repository at this point in the history
…datasource

Signed-off-by: Yuanqi(Ella) Zhu <zhyuanqi@amazon.com>
  • Loading branch information
zhyuanqi committed Apr 4, 2024
1 parent 557fbcf commit 1d7e948
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Multiple Datasource] Add multi data source support to sample vega visualizations ([#6218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6218))
- [Multiple Datasource] Fetch data source title for DataSourceView when only id is provided ([#6315](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6315)
- [Workspace] Add permission control logic ([#6052](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6052))
- [Multiple Datasource] Add default icon for selectable component and make sure the default datasource shows automatically ([#6327](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6327))
- [Multiple Datasource] Pass selected data sources to plugin consumers when the multi-select component initially loads ([#6333](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6333))

### 🐛 Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@

import React from 'react';
import { EuiHeaderLinks } from '@elastic/eui';
import { IUiSettingsClient } from 'src/core/public';
import { DataSourceMenu } from './data_source_menu';
import { DataSourceMenuProps } from './types';
import { MountPointPortal } from '../../../../opensearch_dashboards_react/public';

export function createDataSourceMenu<T>() {
export function createDataSourceMenu<T>(uiSettings: IUiSettingsClient) {
return (props: DataSourceMenuProps<T>) => {
if (props.setMenuMountPoint) {
return (
<MountPointPortal setMountPoint={props.setMenuMountPoint}>
<EuiHeaderLinks data-test-subj="top-nav" gutterSize="xs">
<DataSourceMenu {...props} />
<DataSourceMenu {...props} uiSettings={uiSettings} />
</EuiHeaderLinks>
</MountPointPortal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { DataSourceSelectable } from '../data_source_selectable';

export function DataSourceMenu<T>(props: DataSourceMenuProps<T>): ReactElement | null {
const { componentType, componentConfig } = props;
const { componentType, componentConfig, uiSettings } = props;

function renderDataSourceView(config: DataSourceViewConfig): ReactElement | null {
const { activeOption, fullWidth, savedObjects, notifications } = config;
Expand Down Expand Up @@ -75,6 +75,7 @@ export function DataSourceMenu<T>(props: DataSourceMenuProps<T>): ReactElement |
dataSourceFilter={dataSourceFilter}
hideLocalCluster={hideLocalCluster || false}
fullWidth={fullWidth}
uiSettings={uiSettings}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
NotificationsStart,
SavedObjectsClientContract,
SavedObject,
IUiSettingsClient,
} from '../../../../../core/public';
import { DataSourceAttributes } from '../../types';

Expand All @@ -23,6 +24,7 @@ export interface DataSourceBaseConfig {
export interface DataSourceMenuProps<T = any> {
componentType: DataSourceComponentType;
componentConfig: T;
uiSettings?: IUiSettingsClient;
setMenuMountPoint?: (menuMount: MountPoint | undefined) => void;
}

Expand Down

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 @@ -11,6 +11,7 @@ import { DataSourceSelectable } from './data_source_selectable';
import { AuthType } from '../../types';
import { getDataSourcesWithFieldsResponse, mockResponseForSavedObjectsCalls } from '../../mocks';
import { render } from '@testing-library/react';
import * as utils from '../utils';

describe('DataSourceSelectable', () => {
let component: ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
Expand Down Expand Up @@ -109,6 +110,7 @@ describe('DataSourceSelectable', () => {

it('should callback if changed state', async () => {
const onSelectedDataSource = jest.fn();
spyOn(utils, 'getDefaultDataSource').and.returnValue([{ id: 'test2', label: 'test2' }]);
const container = mount(
<DataSourceSelectable
savedObjectsClient={client}
Expand All @@ -125,19 +127,20 @@ describe('DataSourceSelectable', () => {
const containerInstance = container.instance();

containerInstance.onChange([{ id: 'test2', label: 'test2' }]);
expect(onSelectedDataSource).toBeCalledTimes(0);
expect(onSelectedDataSource).toBeCalledTimes(1);
expect(containerInstance.state).toEqual({
dataSourceOptions: [
{
id: 'test2',
label: 'test2',
},
],
defaultDataSource: null,
isPopoverOpen: false,
selectedOption: [
{
id: '',
label: 'Local cluster',
id: 'test2',
label: 'test2',
},
],
});
Expand All @@ -151,6 +154,7 @@ describe('DataSourceSelectable', () => {
label: 'test2',
},
],
defaultDataSource: null,
isPopoverOpen: false,
selectedOption: [
{
Expand All @@ -160,7 +164,9 @@ describe('DataSourceSelectable', () => {
},
],
});

expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]);
expect(onSelectedDataSource).toBeCalledTimes(1);
expect(onSelectedDataSource).toHaveBeenCalled();
expect(utils.getDefaultDataSource).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ import {
EuiButtonEmpty,
EuiSelectable,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiBadge,
} from '@elastic/eui';
import { SavedObjectsClientContract, ToastsStart } from 'opensearch-dashboards/public';
import { getDataSourcesWithFields } from '../utils';
import {
IUiSettingsClient,
SavedObjectsClientContract,
ToastsStart,
} from 'opensearch-dashboards/public';
import { getDataSourcesWithFields, getDefaultDataSource } from '../utils';
import { LocalCluster } from '../data_source_selector/data_source_selector';
import { SavedObject } from '../../../../../core/public';
import { DataSourceAttributes } from '../../types';
Expand All @@ -29,16 +36,18 @@ interface DataSourceSelectableProps {
fullWidth: boolean;
selectedOption?: DataSourceOption[];
dataSourceFilter?: (dataSource: SavedObject<DataSourceAttributes>) => boolean;
uiSettings?: IUiSettingsClient;
}

interface DataSourceSelectableState {
dataSourceOptions: SelectedDataSourceOption[];
isPopoverOpen: boolean;
selectedOption?: SelectedDataSourceOption[];
defaultDataSource: string | null;
}

interface SelectedDataSourceOption extends DataSourceOption {
checked?: boolean;
checked?: string;
}

export class DataSourceSelectable extends React.Component<
Expand All @@ -53,11 +62,8 @@ export class DataSourceSelectable extends React.Component<
this.state = {
dataSourceOptions: [],
isPopoverOpen: false,
selectedOption: this.props.selectedOption
? this.props.selectedOption
: this.props.hideLocalCluster
? []
: [LocalCluster],
selectedOption: [],
defaultDataSource: null,
};

this.onChange.bind(this);
Expand All @@ -77,35 +83,58 @@ export class DataSourceSelectable extends React.Component<

async componentDidMount() {
this._isMounted = true;
let filteredDataSources: Array<SavedObject<DataSourceAttributes>> = [];
let dataSourceOptions: DataSourceOption[] = [];
getDataSourcesWithFields(this.props.savedObjectsClient, ['id', 'title', 'auth.type'])
.then((fetchedDataSources) => {
if (fetchedDataSources?.length) {
let filteredDataSources: Array<SavedObject<DataSourceAttributes>> = [];
filteredDataSources = fetchedDataSources;
// Filter the existing datasource by the filter function provided.
if (this.props.dataSourceFilter) {
filteredDataSources = fetchedDataSources.filter((ds) =>
this.props.dataSourceFilter!(ds)
);
}

if (filteredDataSources.length === 0) {
filteredDataSources = fetchedDataSources;
}

const dataSourceOptions = filteredDataSources
dataSourceOptions = filteredDataSources
.map((dataSource) => ({
id: dataSource.id,
label: dataSource.attributes?.title || '',
}))
.sort((a, b) => a.label.toLowerCase().localeCompare(b.label.toLowerCase()));
if (!this.props.hideLocalCluster) {
dataSourceOptions.unshift(LocalCluster);
}
}

// Add local cluster to the list of data sources if it is not hidden.
if (!this.props.hideLocalCluster) {
dataSourceOptions.unshift(LocalCluster);
}

const defaultDataSource = this.props.uiSettings?.get('defaultDataSource', null) ?? null;
const selectedDataSource = getDefaultDataSource(
filteredDataSources,
LocalCluster,
this.props.uiSettings,
this.props.hideLocalCluster,
this.props.selectedOption
);
if (selectedDataSource.length === 0) {
this.props.notifications.addWarning('No connected data source available.');
} else {
// Update the checked status of the selected data source.
const updatedDataSourceOptions: SelectedDataSourceOption[] = dataSourceOptions.map(
(option) => ({
...option,
checked: option.id === selectedDataSource[0].id ? 'on' : undefined,
})
);
if (!this._isMounted) return;
this.setState({
...this.state,
dataSourceOptions,
dataSourceOptions: updatedDataSourceOptions,
selectedOption: selectedDataSource,
defaultDataSource,
});
this.props.onSelectedDataSources(selectedDataSource);
}
})
.catch(() => {
Expand Down Expand Up @@ -168,7 +197,7 @@ export class DataSourceSelectable extends React.Component<
data-test-subj={'dataSourceSelectableContextMenuPopover'}
>
<EuiContextMenuPanel>
<EuiPanel color="transparent" paddingSize="s">
<EuiPanel color="transparent" paddingSize="s" style={{ width: '300px' }}>
<EuiSpacer size="s" />
<EuiSelectable
aria-label="Search"
Expand All @@ -180,6 +209,16 @@ export class DataSourceSelectable extends React.Component<
onChange={(newOptions) => this.onChange(newOptions)}
singleSelection={true}
data-test-subj={'dataSourceSelectable'}
renderOption={(option) => (
<EuiFlexGroup alignItems="center">

Check warning on line 213 in src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx#L213

Added line #L213 was not covered by tests
<EuiFlexItem grow={1}>{option.label}</EuiFlexItem>
{option.id === this.state.defaultDataSource && (
<EuiFlexItem grow={false}>
<EuiBadge iconSide="left">Default</EuiBadge>
</EuiFlexItem>
)}
</EuiFlexGroup>
)}
>
{(list, search) => (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
noAuthCredentialAuthMethod,
} from '../types';
import { AuthenticationMethodRegistry } from '../auth_registry';
import { DataSourceOption } from './data_source_selector/data_source_selector';
import { DataSourceOption } from './data_source_menu/types';

export async function getDataSources(savedObjectsClient: SavedObjectsClientContract) {
return savedObjectsClient
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data_source_management/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class DataSourceManagementPlugin
registerAuthenticationMethod,
ui: {
DataSourceSelector: createDataSourceSelector(uiSettings),
getDataSourceMenu: <T>() => createDataSourceMenu<T>(),
getDataSourceMenu: <T>() => createDataSourceMenu<T>(uiSettings),

Check warning on line 107 in src/plugins/data_source_management/public/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/plugin.ts#L107

Added line #L107 was not covered by tests
},
};
}
Expand Down

0 comments on commit 1d7e948

Please sign in to comment.