diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 40d643b014fd..853a785d26ce 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -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 diff --git a/src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.tsx b/src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.tsx index 2e76c2cf23c6..484f3e6630d8 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.tsx +++ b/src/plugins/data_source_management/public/components/data_source_menu/create_data_source_menu.tsx @@ -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() { +export function createDataSourceMenu(uiSettings: IUiSettingsClient) { return (props: DataSourceMenuProps) => { if (props.setMenuMountPoint) { return ( - + ); diff --git a/src/plugins/data_source_management/public/components/data_source_menu/data_source_menu.tsx b/src/plugins/data_source_management/public/components/data_source_menu/data_source_menu.tsx index b56063e5d25f..b1f7ed1eaadd 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/data_source_menu.tsx +++ b/src/plugins/data_source_management/public/components/data_source_menu/data_source_menu.tsx @@ -19,7 +19,7 @@ import { import { DataSourceSelectable } from '../data_source_selectable'; export function DataSourceMenu(props: DataSourceMenuProps): ReactElement | null { - const { componentType, componentConfig } = props; + const { componentType, componentConfig, uiSettings } = props; function renderDataSourceView(config: DataSourceViewConfig): ReactElement | null { const { activeOption, fullWidth, savedObjects, notifications } = config; @@ -75,6 +75,7 @@ export function DataSourceMenu(props: DataSourceMenuProps): ReactElement | dataSourceFilter={dataSourceFilter} hideLocalCluster={hideLocalCluster || false} fullWidth={fullWidth} + uiSettings={uiSettings} /> ); } diff --git a/src/plugins/data_source_management/public/components/data_source_menu/types.ts b/src/plugins/data_source_management/public/components/data_source_menu/types.ts index 4121edc0c863..ba274f5178de 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/types.ts +++ b/src/plugins/data_source_management/public/components/data_source_menu/types.ts @@ -7,6 +7,7 @@ import { NotificationsStart, SavedObjectsClientContract, SavedObject, + IUiSettingsClient, } from '../../../../../core/public'; import { DataSourceAttributes } from '../../types'; @@ -23,6 +24,7 @@ export interface DataSourceBaseConfig { export interface DataSourceMenuProps { componentType: DataSourceComponentType; componentConfig: T; + uiSettings?: IUiSettingsClient; setMenuMountPoint?: (menuMount: MountPoint | undefined) => void; } diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap index 2cb8dff8a94d..f76958715e77 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap @@ -35,6 +35,11 @@ exports[`DataSourceSelectable should filter options if configured 1`] = ` - Local cluster + } @@ -170,6 +183,11 @@ exports[`DataSourceSelectable should render normally with local cluster not hidd
{ let component: ShallowWrapper, React.Component<{}, {}, any>>; @@ -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( { const containerInstance = container.instance(); containerInstance.onChange([{ id: 'test2', label: 'test2' }]); - expect(onSelectedDataSource).toBeCalledTimes(0); + expect(onSelectedDataSource).toBeCalledTimes(1); expect(containerInstance.state).toEqual({ dataSourceOptions: [ { @@ -133,11 +135,12 @@ describe('DataSourceSelectable', () => { label: 'test2', }, ], + defaultDataSource: null, isPopoverOpen: false, selectedOption: [ { - id: '', - label: 'Local cluster', + id: 'test2', + label: 'test2', }, ], }); @@ -151,6 +154,7 @@ describe('DataSourceSelectable', () => { label: 'test2', }, ], + defaultDataSource: null, isPopoverOpen: false, selectedOption: [ { @@ -160,7 +164,9 @@ describe('DataSourceSelectable', () => { }, ], }); + expect(onSelectedDataSource).toBeCalledWith([{ id: 'test2', label: 'test2' }]); - expect(onSelectedDataSource).toBeCalledTimes(1); + expect(onSelectedDataSource).toHaveBeenCalled(); + expect(utils.getDefaultDataSource).toHaveBeenCalled(); }); }); diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx index e0fccf9aa226..3c52f0982d20 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx @@ -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'; @@ -29,16 +36,18 @@ interface DataSourceSelectableProps { fullWidth: boolean; selectedOption?: DataSourceOption[]; dataSourceFilter?: (dataSource: SavedObject) => 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< @@ -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); @@ -77,35 +83,60 @@ export class DataSourceSelectable extends React.Component< async componentDidMount() { this._isMounted = true; + let filteredDataSources: Array> = []; + let dataSourceOptions: DataSourceOption[] = []; getDataSourcesWithFields(this.props.savedObjectsClient, ['id', 'title', 'auth.type']) .then((fetchedDataSources) => { if (fetchedDataSources?.length) { - let filteredDataSources: Array> = []; + 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(() => { @@ -168,7 +199,7 @@ export class DataSourceSelectable extends React.Component< data-test-subj={'dataSourceSelectableContextMenuPopover'} > - + this.onChange(newOptions)} singleSelection={true} data-test-subj={'dataSourceSelectable'} + renderOption={(option) => ( + + {option.label} + {option.id === this.state.defaultDataSource && ( + + Default + + )} + + )} > {(list, search) => ( <> diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index 50960936e222..6cfaaa9a081a 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -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 diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts index c6a978ae7b61..d07911dacfc4 100644 --- a/src/plugins/data_source_management/public/plugin.ts +++ b/src/plugins/data_source_management/public/plugin.ts @@ -104,7 +104,7 @@ export class DataSourceManagementPlugin registerAuthenticationMethod, ui: { DataSourceSelector: createDataSourceSelector(uiSettings), - getDataSourceMenu: () => createDataSourceMenu(), + getDataSourceMenu: () => createDataSourceMenu(uiSettings), }, }; }