Skip to content

Commit

Permalink
Add default icon for selectable component and make sure the default d…
Browse files Browse the repository at this point in the history
…atasource shows automatically (opensearch-project#6327)

Signed-off-by: Yuanqi(Ella) Zhu <zhyuanqi@amazon.com>
  • Loading branch information
zhyuanqi authored Apr 4, 2024
1 parent fc3fef2 commit 726fb0e
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 55 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))
- [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303))

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,44 +83,72 @@ export class DataSourceSelectable extends React.Component<

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

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

const 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);
}

if (!this._isMounted) return;
this.setState({
...this.state,
dataSourceOptions,
});
}
})
.catch(() => {
this.props.notifications.addWarning(
i18n.translate('dataSource.fetchDataSourceError', {
defaultMessage: 'Unable to fetch existing data sources',
try {
let filteredDataSources: Array<SavedObject<DataSourceAttributes>> = [];
let dataSourceOptions: DataSourceOption[] = [];

// Fetch data sources with fields
const fetchedDataSources = await getDataSourcesWithFields(this.props.savedObjectsClient, [
'id',
'title',
'auth.type',
]);

if (fetchedDataSources?.length) {
filteredDataSources = this.props.dataSourceFilter
? fetchedDataSources.filter((ds) => this.props.dataSourceFilter!(ds))
: fetchedDataSources;
dataSourceOptions = filteredDataSources
.map((dataSource) => ({
id: dataSource.id,
label: dataSource.attributes?.title || '',
}))
.sort((a, b) => a.label.toLowerCase().localeCompare(b.label.toLowerCase()));
}

// 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,
...(option.id === selectedDataSource[0].id && { checked: 'on' }),
})
);
});

if (!this._isMounted) return;

this.setState({
...this.state,
dataSourceOptions: updatedDataSourceOptions,
selectedOption: selectedDataSource,
defaultDataSource,
});

this.props.onSelectedDataSources(selectedDataSource);
}
} catch (error) {
this.props.notifications.addWarning(
i18n.translate('dataSource.fetchDataSourceError', {
defaultMessage: 'Unable to fetch existing data sources',
})
);
}
}

onChange(options: SelectedDataSourceOption[]) {
Expand Down Expand Up @@ -168,7 +202,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 +214,16 @@ export class DataSourceSelectable extends React.Component<
onChange={(newOptions) => this.onChange(newOptions)}
singleSelection={true}
data-test-subj={'dataSourceSelectable'}
renderOption={(option) => (
<EuiFlexGroup alignItems="center">
<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
Loading

0 comments on commit 726fb0e

Please sign in to comment.