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 <[email protected]>
  • Loading branch information
zhyuanqi committed Apr 4, 2024
1 parent 557fbcf commit b504d71
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 31 deletions.
2 changes: 1 addition & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@
# vis_builder.enabled: false

# Set the value of this setting to true to enable multiple data source feature.
#data_source.enabled: false
data_source.enabled: true
# Set the value of this setting to true to hide local cluster in data source feature.
#data_source.hideLocalCluster: false
# Set the value of these settings to customize crypto materials to encryption saved credentials
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,60 @@ 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);
}
}

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

if (!this._isMounted) return;
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 {
const updatedDataSourceOptions: SelectedDataSourceOption[] = dataSourceOptions.map(
(option) => {
if (option.id === selectedDataSource[0].id) {
return { ...option, checked: 'on' };
} else {
return option;
}
}
);
this.setState({
...this.state,
dataSourceOptions,
dataSourceOptions: updatedDataSourceOptions,
selectedOption: selectedDataSource,
defaultDataSource,
});
this.props.onSelectedDataSources(selectedDataSource);
}
})
.catch(() => {
Expand Down Expand Up @@ -168,7 +199,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 +211,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 215 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#L215

Added line #L215 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 b504d71

Please sign in to comment.