diff --git a/src/plugins/data_source_management/public/components/data_source_selector/__snapshots__/data_source_selector.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_selector/__snapshots__/data_source_selector.test.tsx.snap index 2b8e3eba860d..7161ee957596 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/__snapshots__/data_source_selector.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_selector/__snapshots__/data_source_selector.test.tsx.snap @@ -13,6 +13,7 @@ exports[`DataSourceSelector should render normally with local cluster is hidden options={Array []} placeholder="Select a data source" prepend="Data source" + renderOption={[Function]} selectedOptions={Array []} singleSelection={ Object { @@ -43,6 +44,7 @@ exports[`DataSourceSelector should render normally with local cluster not hidden } placeholder="Select a data source" prepend="Data source" + renderOption={[Function]} selectedOptions={ Array [ Object { @@ -92,6 +94,7 @@ exports[`DataSourceSelector: check dataSource options should always place local } placeholder="Select a data source" prepend="Data source" + renderOption={[Function]} selectedOptions={ Array [ Object { @@ -137,6 +140,57 @@ exports[`DataSourceSelector: check dataSource options should filter options if c } placeholder="Select a data source" prepend="Data source" + renderOption={[Function]} + selectedOptions={ + Array [ + Object { + "id": "", + "label": "Local cluster", + }, + ] + } + singleSelection={ + Object { + "asPlainText": true, + } + } + sortMatchesBy="none" +/> +`; + +exports[`DataSourceSelector: check dataSource options should get default datasource if uiSettings exists 1`] = ` + +`; + +exports[`DataSourceSelector: check dataSource options should show default datasource if configured 1`] = ` + { let client: SavedObjectsClientContract; + let uiSettings: IUiSettingsClient; const { toasts } = notificationServiceMock.createStartContract(); beforeEach(() => { @@ -27,7 +29,7 @@ describe('create data source selector', () => { hideLocalCluster: false, fullWidth: false, }; - const TestComponent = createDataSourceSelector(); + const TestComponent = createDataSourceSelector(uiSettings); const component = render(); expect(component).toMatchSnapshot(); expect(client.find).toBeCalledWith({ diff --git a/src/plugins/data_source_management/public/components/data_source_selector/create_data_source_selector.tsx b/src/plugins/data_source_management/public/components/data_source_selector/create_data_source_selector.tsx index ff6b7503a0bb..485d192668a5 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/create_data_source_selector.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selector/create_data_source_selector.tsx @@ -4,8 +4,11 @@ */ import React from 'react'; +import { IUiSettingsClient } from 'src/core/public'; import { DataSourceSelector, DataSourceSelectorProps } from './data_source_selector'; -export function createDataSourceSelector() { - return (props: DataSourceSelectorProps) => ; +export function createDataSourceSelector(uiSettings: IUiSettingsClient) { + return (props: DataSourceSelectorProps) => ( + + ); } diff --git a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.test.tsx b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.test.tsx index 86eb892e8cd8..bb4547a47a6b 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.test.tsx @@ -5,11 +5,16 @@ import { ShallowWrapper, shallow } from 'enzyme'; import { DataSourceSelector } from './data_source_selector'; -import { SavedObjectsClientContract } from '../../../../../core/public'; +import { SavedObjectsClientContract, IUiSettingsClient } from '../../../../../core/public'; import { notificationServiceMock } from '../../../../../core/public/mocks'; import React from 'react'; -import { getDataSourcesWithFieldsResponse, mockResponseForSavedObjectsCalls } from '../../mocks'; +import { + getDataSourcesWithFieldsResponse, + mockManagementPlugin, + mockResponseForSavedObjectsCalls, +} from '../../mocks'; import { AuthType } from 'src/plugins/data_source/common/data_sources'; +import * as utils from '../utils'; describe('DataSourceSelector', () => { let component: ShallowWrapper, React.Component<{}, {}, any>>; @@ -69,6 +74,8 @@ describe('DataSourceSelector: check dataSource options', () => { let client: SavedObjectsClientContract; const { toasts } = notificationServiceMock.createStartContract(); const nextTick = () => new Promise((res) => process.nextTick(res)); + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + const uiSettings = mockedContext.uiSettings; beforeEach(async () => { client = { @@ -170,4 +177,49 @@ describe('DataSourceSelector: check dataSource options', () => { expect(component).toMatchSnapshot(); expect(toasts.addWarning).toBeCalledTimes(0); }); + + it('should get default datasource if uiSettings exists', async () => { + spyOn(uiSettings, 'get').and.returnValue({}); + component = shallow( + + ); + + component.instance().componentDidMount!(); + await nextTick(); + expect(component).toMatchSnapshot(); + expect(uiSettings.get).toBeCalledWith('defaultDataSource', null); + expect(toasts.addWarning).toBeCalledTimes(0); + }); + + it('should show default datasource if configured', async () => { + spyOn(utils, 'getFilterDataSources').and.returnValue([]); + spyOn(utils, 'getDefaultDataSource').and.returnValue([]); + spyOn(uiSettings, 'get').and.returnValue({}); + component = shallow( + + ); + + component.instance().componentDidMount!(); + await nextTick(); + expect(component).toMatchSnapshot(); + expect(utils.getFilterDataSources).toHaveBeenCalled(); + expect(utils.getDefaultDataSource).toHaveBeenCalled(); + expect(toasts.addWarning).toBeCalledTimes(0); + }); }); diff --git a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx index 3e9f4c377160..96e2731a0dec 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx @@ -5,9 +5,10 @@ import React from 'react'; import { i18n } from '@osd/i18n'; -import { EuiComboBox } from '@elastic/eui'; +import { EuiComboBox, EuiBadge, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { SavedObjectsClientContract, ToastsStart, SavedObject } from 'opensearch-dashboards/public'; -import { getDataSourcesWithFields } from '../utils'; +import { IUiSettingsClient } from 'src/core/public'; +import { getDataSourcesWithFields, getDefaultDataSource, getFilterDataSources } from '../utils'; import { DataSourceAttributes } from '../../types'; export const LocalCluster: DataSourceOption = { @@ -29,6 +30,8 @@ export interface DataSourceSelectorProps { removePrepend?: boolean; dataSourceFilter?: (dataSource: SavedObject) => boolean; compressed?: boolean; + uiSettings?: IUiSettingsClient; + showDefault?: (source: DataSourceOption) => void; } interface DataSourceSelectorState { @@ -76,6 +79,20 @@ export class DataSourceSelector extends React.Component< allDataSources: fetchedDataSources, }); } + if (this.props.showDefault) { + const dataSources = getFilterDataSources( + this.state.allDataSources, + this.props.dataSourceFilter + ); + const defaultDataSource = getDefaultDataSource( + dataSources, + LocalCluster, + this.props.uiSettings, + this.props.hideLocalCluster, + this.props.defaultOption + ); + this.props.showDefault(defaultDataSource); + } }) .catch(() => { this.props.notifications.addWarning( @@ -100,15 +117,17 @@ export class DataSourceSelector extends React.Component< ? 'Select a data source' : this.props.placeholderText; - const dataSources = this.props.dataSourceFilter - ? this.state.allDataSources.filter((ds) => this.props.dataSourceFilter!(ds)) - : this.state.allDataSources; + const dataSources = getFilterDataSources( + this.state.allDataSources, + this.props.dataSourceFilter + ); const options = dataSources.map((ds) => ({ id: ds.id, label: ds.attributes?.title || '' })); if (!this.props.hideLocalCluster) { options.unshift(LocalCluster); } + const defaultDataSource = this.props.uiSettings?.get('defaultDataSource', null) ?? null; return ( ( + + {option.label} + {option.id === defaultDataSource && ( + + Default + + )} + + )} /> ); } diff --git a/src/plugins/data_source_management/public/components/utils.test.ts b/src/plugins/data_source_management/public/components/utils.test.ts index f2b1f709cb18..57b42440ac99 100644 --- a/src/plugins/data_source_management/public/components/utils.test.ts +++ b/src/plugins/data_source_management/public/components/utils.test.ts @@ -16,6 +16,8 @@ import { updateDataSourceById, handleSetDefaultDatasource, setFirstDataSourceAsDefault, + getFilterDataSources, + getDefaultDataSource, } from './utils'; import { coreMock } from '../../../../core/public/mocks'; import { @@ -28,6 +30,7 @@ import { mockResponseForSavedObjectsCalls, mockUiSettingsCalls, getSingleDataSourceResponse, + getDataSource, } from '../mocks'; import { AuthType, @@ -35,9 +38,10 @@ import { sigV4AuthMethod, usernamePasswordAuthMethod, } from '../types'; -import { HttpStart } from 'opensearch-dashboards/public'; +import { HttpStart, SavedObject } from 'opensearch-dashboards/public'; import { AuthenticationMethod, AuthenticationMethodRegistry } from '../auth_registry'; import { deepEqual } from 'assert'; +import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources'; // Import the missing 'SigV4Content' here const { savedObjects } = coreMock.createStart(); const { uiSettings } = coreMock.createStart(); @@ -495,4 +499,84 @@ describe('DataSourceManagement: Utils.ts', () => { expect(deepEqual(registedAuthTypeCredentials, expectExtractedAuthCredentials)); }); }); + + describe('Check on get filter datasource', () => { + test('should return all data sources when no filter is provided', () => { + const dataSources: Array> = [ + { + id: '1', + type: '', + references: [], + attributes: { + title: 'DataSource 1', + endpoint: '', + auth: { type: AuthType.NoAuth, credentials: undefined }, + name: AuthType.NoAuth, + }, + }, + ]; + + const result = getFilterDataSources(dataSources); + + expect(result).toEqual(dataSources); + }); + + test('should return filtered data sources when a filter is provided', () => { + const filter = (dataSource: SavedObject) => dataSource.id === '2'; + const result = getFilterDataSources(getDataSource, filter); + + expect(result).toEqual([ + { + id: '2', + type: '', + references: [], + attributes: { + title: 'DataSource 1', + endpoint: '', + auth: { type: AuthType.NoAuth, credentials: undefined }, + name: AuthType.NoAuth, + }, + }, + ]); + }); + }); + describe('getDefaultDataSource', () => { + const LocalCluster = { id: 'local', label: 'Local Cluster' }; + const hideLocalCluster = false; + const defaultOption = [{ id: '2', label: 'Default Option' }]; + + it('should return the default option if it exists in the data sources', () => { + const result = getDefaultDataSource( + getDataSource, + LocalCluster, + uiSettings, + hideLocalCluster, + defaultOption + ); + expect(result).toEqual(defaultOption[0]); + }); + + it('should return local cluster if it exists and no default options in the data sources', () => { + mockUiSettingsCalls(uiSettings, 'get', null); + const result = getDefaultDataSource( + getDataSource, + LocalCluster, + uiSettings, + hideLocalCluster + ); + expect(result).toEqual(LocalCluster); + }); + + it('should return the default datasource if hideLocalCluster is false', () => { + mockUiSettingsCalls(uiSettings, 'get', '1'); + const result = getDefaultDataSource(getDataSource, LocalCluster, uiSettings, true); + expect(result).toEqual({ id: '1', label: 'DataSource 1' }); + }); + + it('should return an empty default data source if no default option, hideLocalCluster is ture and no default datasource', () => { + mockUiSettingsCalls(uiSettings, 'get', null); + const result = getDefaultDataSource(getDataSource, LocalCluster, uiSettings, true); + expect(result).toEqual({ id: '', label: '' }); + }); + }); }); diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index b911203cd288..cdb8120128c9 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -16,6 +16,7 @@ import { noAuthCredentialAuthMethod, } from '../types'; import { AuthenticationMethodRegistry } from '../auth_registry'; +import { DataSourceOption } from './data_source_selector/data_source_selector'; export async function getDataSources(savedObjectsClient: SavedObjectsClientContract) { return savedObjectsClient @@ -78,6 +79,41 @@ export async function setFirstDataSourceAsDefault( } } +export function getFilterDataSources( + dataSources: Array>, + filter?: (dataSource: SavedObject) => boolean +) { + return filter ? dataSources.filter((ds) => filter!(ds)) : dataSources; +} + +export function getDefaultDataSource( + dataSources: Array>, + LocalCluster: DataSourceOption, + uiSettings?: IUiSettingsClient, + hideLocalCluster?: boolean, + defaultOption?: DataSourceOption[] +) { + let defaultShowingDataSource: DataSourceOption; + + if (defaultOption && dataSources.find((dataSource) => dataSource.id === defaultOption?.[0].id)) { + defaultShowingDataSource = defaultOption?.[0]; + } else if ( + uiSettings && + uiSettings?.get('defaultDataSource', null) !== null && + dataSources.find((dataSource) => dataSource.id === uiSettings?.get('defaultDataSource')) + ) { + const ds = dataSources.find( + (dataSource) => dataSource.id === uiSettings?.get('defaultDataSource') + ); + defaultShowingDataSource = { id: ds!.id, label: ds!.attributes?.title || '' }; + } else if (!hideLocalCluster) { + defaultShowingDataSource = LocalCluster; + } else { + defaultShowingDataSource = { id: '', label: '' }; + } + return defaultShowingDataSource; +} + export async function getDataSourceById( id: string, savedObjectsClient: SavedObjectsClientContract diff --git a/src/plugins/data_source_management/public/mocks.ts b/src/plugins/data_source_management/public/mocks.ts index d04fc2a362d3..910d56de61a8 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -78,6 +78,42 @@ export const getSingleDataSourceResponse = { ], }; +export const getDataSource = [ + { + id: '1', + type: '', + references: [], + attributes: { + title: 'DataSource 1', + endpoint: '', + auth: { type: AuthType.NoAuth, credentials: undefined }, + name: AuthType.NoAuth, + }, + }, + { + id: '2', + type: '', + references: [], + attributes: { + title: 'DataSource 1', + endpoint: '', + auth: { type: AuthType.NoAuth, credentials: undefined }, + name: AuthType.NoAuth, + }, + }, + { + id: '3', + type: '', + references: [], + attributes: { + title: 'DataSource 1', + endpoint: '', + auth: { type: AuthType.NoAuth, credentials: undefined }, + name: AuthType.NoAuth, + }, + }, +]; + /* Mock data responses - JSON*/ export const getDataSourcesResponse = { savedObjects: [ diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts index 9e6da39dc08b..c6a978ae7b61 100644 --- a/src/plugins/data_source_management/public/plugin.ts +++ b/src/plugins/data_source_management/public/plugin.ts @@ -57,6 +57,7 @@ export class DataSourceManagementPlugin { management, indexPatternManagement, dataSource }: DataSourceManagementSetupDependencies ) { const opensearchDashboardsSection = management.sections.section.opensearchDashboards; + const uiSettings = core.uiSettings; if (!opensearchDashboardsSection) { throw new Error('`opensearchDashboards` management section not found.'); @@ -102,7 +103,7 @@ export class DataSourceManagementPlugin return { registerAuthenticationMethod, ui: { - DataSourceSelector: createDataSourceSelector(), + DataSourceSelector: createDataSourceSelector(uiSettings), getDataSourceMenu: () => createDataSourceMenu(), }, }; diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index 3d3655b3bd64..41f08a1ea08e 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -39,6 +39,7 @@ import { ApplicationStart, ChromeStart, CoreStart, + IUiSettingsClient, NotificationsStart, SavedObjectsStart, ScopedHistory, @@ -48,7 +49,6 @@ import { DataSourceSelector } from '../../data_source_management/public'; import { DevToolApp } from './dev_tool'; import { DevToolsSetupDependencies } from './plugin'; import { addHelpMenuToAppChrome } from './utils/util'; - interface DevToolsWrapperProps { devTools: readonly DevToolApp[]; activeDevTool: DevToolApp; @@ -57,6 +57,7 @@ interface DevToolsWrapperProps { notifications: NotificationsStart; dataSourceEnabled: boolean; hideLocalCluster: boolean; + uiSettings: IUiSettingsClient; } interface MountedDevToolDescriptor { @@ -73,6 +74,7 @@ function DevToolsWrapper({ notifications: { toasts }, dataSourceEnabled, hideLocalCluster, + uiSettings, }: DevToolsWrapperProps) { const mountedTool = useRef(null); @@ -140,6 +142,7 @@ function DevToolsWrapper({ disabled={!dataSourceEnabled} hideLocalCluster={hideLocalCluster} fullWidth={false} + uiSettings={uiSettings} /> ) : null} @@ -213,7 +216,7 @@ function setBreadcrumbs(chrome: ChromeStart) { } export function renderApp( - { application, chrome, docLinks, savedObjects, notifications }: CoreStart, + { application, chrome, docLinks, savedObjects, notifications, uiSettings }: CoreStart, element: HTMLElement, history: ScopedHistory, devTools: readonly DevToolApp[], @@ -251,6 +254,7 @@ export function renderApp( notifications={notifications} dataSourceEnabled={dataSourceEnabled} hideLocalCluster={hideLocalCluster} + uiSettings={uiSettings} /> )} /> diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js index cb13d06bfb18..a36f231b38ca 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.js +++ b/src/plugins/home/public/application/components/tutorial_directory.js @@ -236,6 +236,7 @@ class TutorialDirectoryUi extends React.Component { onSelectedDataSource={this.onSelectedDataSourceChange} disabled={!isDataSourceEnabled} hideLocalCluster={isLocalClusterHidden} + uiSettings={getServices().uiSettings} /> ) : null;