diff --git a/CHANGELOG.md b/CHANGELOG.md index bbcc7f0a9c96..13fe245175ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Dynamic Configurations] Improve dynamic configurations by adding cache and simplifying client fetch ([#6364](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6364)) - [MD] Add OpenSearch cluster group label to top of single selectable dropdown ([#6400](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6400)) - [Workspace] Support workspace in saved objects client in server side. ([#6365](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6365)) +- [MD] Add dropdown header to data source single selector ([#6431](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6431)) ### 🐛 Bug Fixes diff --git a/src/plugins/data_source_management/public/components/__snapshots__/drop_down_header.test.tsx.snap b/src/plugins/data_source_management/public/components/__snapshots__/drop_down_header.test.tsx.snap new file mode 100644 index 000000000000..ceae0b915add --- /dev/null +++ b/src/plugins/data_source_management/public/components/__snapshots__/drop_down_header.test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DataSourceDropDownHeader should render correctly with the provided dataSourceCount 1`] = ` + + + + DATA SOURCE( + 5 + ) + +
+ + + Manage + + + + +`; 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 56fd7a7a7cb3..f99cc6558bca 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 @@ -10,11 +10,13 @@ import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; import { DataSourceMenu } from './data_source_menu'; import { DataSourceMenuProps } from './types'; import { MountPointPortal } from '../../../../opensearch_dashboards_react/public'; +import { applicationService } from '../utils'; export function createDataSourceMenu( uiSettings: IUiSettingsClient, dataSourcePluginSetup: DataSourcePluginSetup ) { + const application = applicationService.getApplication(); return (props: DataSourceMenuProps) => { const { hideLocalCluster } = dataSourcePluginSetup; if (props.setMenuMountPoint) { @@ -25,13 +27,19 @@ export function createDataSourceMenu( {...props} uiSettings={uiSettings} hideLocalCluster={hideLocalCluster} + application={application} /> ); } 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 fdc5d2c7508b..d0db133108bc 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,8 +19,7 @@ import { import { DataSourceSelectable } from '../data_source_selectable'; export function DataSourceMenu(props: DataSourceMenuProps): ReactElement | null { - const { componentType, componentConfig, uiSettings, hideLocalCluster } = props; - + const { componentType, componentConfig, uiSettings, hideLocalCluster, application } = props; function renderDataSourceView(config: DataSourceViewConfig): ReactElement | null { const { activeOption, fullWidth, savedObjects, notifications } = config; return ( @@ -70,6 +69,7 @@ export function DataSourceMenu(props: DataSourceMenuProps): ReactElement | hideLocalCluster={hideLocalCluster || false} fullWidth={fullWidth} uiSettings={uiSettings} + application={application} /> ); } 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 17aa35d8b8d0..446824d14084 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 @@ -8,6 +8,7 @@ import { SavedObjectsClientContract, SavedObject, IUiSettingsClient, + ApplicationStart, } from '../../../../../core/public'; import { DataSourceAttributes } from '../../types'; @@ -31,6 +32,7 @@ export interface DataSourceMenuProps { componentConfig: T; hideLocalCluster?: boolean; uiSettings?: IUiSettingsClient; + application?: ApplicationStart; 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 851d37b3d71b..bdd3ad57aa3e 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 @@ -24,6 +24,7 @@ exports[`DataSourceSelectable should filter options if configured 1`] = ` display="inlineBlock" hasArrow={true} id="dataSourceSelectableContextMenuPopover" + initialFocus=".euiSelectableSearch" isOpen={false} ownFocus={true} panelPaddingSize="none" @@ -41,6 +42,12 @@ exports[`DataSourceSelectable should filter options if configured 1`] = ` } } > + + @@ -109,6 +116,7 @@ exports[`DataSourceSelectable should render normally with local cluster is hidde display="inlineBlock" hasArrow={true} id="dataSourceSelectableContextMenuPopover" + initialFocus=".euiSelectableSearch" isOpen={false} ownFocus={true} panelPaddingSize="none" @@ -126,6 +134,12 @@ exports[`DataSourceSelectable should render normally with local cluster is hidde } } > + + @@ -173,6 +187,7 @@ exports[`DataSourceSelectable should render normally with local cluster not hidd display="inlineBlock" hasArrow={true} id="dataSourceSelectableContextMenuPopover" + initialFocus=".euiSelectableSearch" isOpen={false} ownFocus={true} panelPaddingSize="none" @@ -190,6 +205,12 @@ exports[`DataSourceSelectable should render normally with local cluster not hidd } } > + + @@ -286,6 +307,34 @@ Object { class="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--transparent euiPanel--noShadow" style="width: 300px;" > +
+
+ DATA SOURCE( + 3 + ) +
+
+
+ +
+
+
diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx index 78b63a57144c..26c441d0e4bf 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.test.tsx @@ -431,4 +431,50 @@ describe('DataSourceSelectable', () => { const optionsProp = component.find(EuiSelectable).prop('options'); expect(optionsProp).toEqual([]); }); + + it('should render group lablel normally after onChange', async () => { + const onSelectedDataSource = jest.fn(); + component = shallow( + + ); + const componentInstance = component.instance(); + + componentInstance.componentDidMount!(); + await nextTick(); + const optionsPropBefore = component.find(EuiSelectable).prop('options'); + expect(optionsPropBefore).toEqual([ + opensearchClusterGroupLabel, + { + id: 'test1', + label: 'test1', + checked: 'on', + }, + { + id: 'test2', + label: 'test2', + }, + { + id: 'test3', + label: 'test3', + }, + ]); + componentInstance.onChange([ + opensearchClusterGroupLabel, + { id: 'test2', label: 'test2', checked: 'on' }, + ]); + await nextTick(); + const optionsPropAfter = component.find(EuiSelectable).prop('options'); + expect(optionsPropAfter).toEqual([ + opensearchClusterGroupLabel, + { id: 'test2', label: 'test2', checked: 'on' }, + ]); + }); }); 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 40a7edce7558..71e9ee22f375 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 @@ -15,8 +15,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiBadge, + EuiHorizontalRule, } from '@elastic/eui'; import { + ApplicationStart, IUiSettingsClient, SavedObjectsClientContract, ToastsStart, @@ -26,6 +28,7 @@ import { LocalCluster } from '../data_source_selector/data_source_selector'; import { SavedObject } from '../../../../../core/public'; import { DataSourceAttributes } from '../../types'; import { DataSourceGroupLabelOption, DataSourceOption } from '../data_source_menu/types'; +import { DataSourceDropDownHeader } from '../drop_down_header'; interface DataSourceSelectableProps { savedObjectsClient: SavedObjectsClientContract; @@ -34,6 +37,7 @@ interface DataSourceSelectableProps { disabled: boolean; hideLocalCluster: boolean; fullWidth: boolean; + application?: ApplicationStart; selectedOption?: DataSourceOption[]; dataSourceFilter?: (dataSource: SavedObject) => boolean; uiSettings?: IUiSettingsClient; @@ -198,6 +202,7 @@ export class DataSourceSelectable extends React.Component< onChange(options: SelectedDataSourceOption[]) { if (!this._isMounted) return; + options = options.filter((option) => !option.hasOwnProperty('isGroupLabel')); const selectedDataSource = options.find(({ checked }) => checked); this.setState({ dataSourceOptions: options }); @@ -247,6 +252,7 @@ export class DataSourceSelectable extends React.Component< return ( + + { diff --git a/src/plugins/data_source_management/public/components/drop_down_header.test.tsx b/src/plugins/data_source_management/public/components/drop_down_header.test.tsx new file mode 100644 index 000000000000..0a639e4d3310 --- /dev/null +++ b/src/plugins/data_source_management/public/components/drop_down_header.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { DataSourceDropDownHeader } from './drop_down_header'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { DSM_APP_ID } from '../plugin'; + +describe('DataSourceDropDownHeader', () => { + it('should render correctly with the provided dataSourceCount', () => { + const dataSourceCount = 5; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it('should call application.navigateToApp when the "Manage" link is clicked', () => { + const dataSourceCount = 5; + const navigateToAppMock = jest.fn(); + const applicationMock = coreMock.createStart().application; + + const wrapper = shallow( + + ); + wrapper.find('EuiLink').simulate('click'); + expect(navigateToAppMock).toHaveBeenCalledWith('management', { + path: `opensearch-dashboards/${DSM_APP_ID}`, + }); + }); +}); diff --git a/src/plugins/data_source_management/public/components/drop_down_header.tsx b/src/plugins/data_source_management/public/components/drop_down_header.tsx new file mode 100644 index 000000000000..d4939bfe3948 --- /dev/null +++ b/src/plugins/data_source_management/public/components/drop_down_header.tsx @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import React from 'react'; +import { ApplicationStart } from 'opensearch-dashboards/public'; +import { DSM_APP_ID } from '../plugin'; + +interface DataSourceOptionItemProps { + dataSourceCount: number; + application?: ApplicationStart; +} + +export const DataSourceDropDownHeader: React.FC = ({ + dataSourceCount, + application, +}) => { + return ( + + + DATA SOURCE({dataSourceCount}) +
+ + + application?.navigateToApp('management', { + path: `opensearch-dashboards/${DSM_APP_ID}`, + }) + } + > + Manage + + + + + ); +}; diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index b8c76bcf858c..7a8b327d8b64 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -8,6 +8,7 @@ import { SavedObjectsClientContract, SavedObject, IUiSettingsClient, + ApplicationStart, } from 'src/core/public'; import { DataSourceAttributes, @@ -257,3 +258,11 @@ export const extractRegisteredAuthTypeCredentials = ( return registeredCredentials; }; + +let application: ApplicationStart; +export const applicationService = { + init: (applicationStart: ApplicationStart) => { + application = applicationStart; + }, + getApplication: () => application, +}; diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts index abcc532b8a7e..40626f13b450 100644 --- a/src/plugins/data_source_management/public/plugin.ts +++ b/src/plugins/data_source_management/public/plugin.ts @@ -4,7 +4,7 @@ */ import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; -import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ApplicationStart, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { PLUGIN_NAME } from '../common'; import { createDataSourceSelector } from './components/data_source_selector/create_data_source_selector'; @@ -21,6 +21,7 @@ import { noAuthCredentialAuthMethod, sigV4AuthMethod, usernamePasswordAuthMethod import { DataSourceSelectorProps } from './components/data_source_selector/data_source_selector'; import { createDataSourceMenu } from './components/data_source_menu/create_data_source_menu'; import { DataSourceMenuProps } from './components/data_source_menu'; +import { applicationService } from './components/utils'; export interface DataSourceManagementSetupDependencies { management: ManagementSetup; @@ -40,7 +41,7 @@ export interface DataSourceManagementPluginStart { getAuthenticationMethodRegistry: () => IAuthenticationMethodRegistry; } -const DSM_APP_ID = 'dataSources'; +export const DSM_APP_ID = 'dataSourceManagement'; export class DataSourceManagementPlugin implements @@ -111,6 +112,7 @@ export class DataSourceManagementPlugin public start(core: CoreStart) { this.started = true; + applicationService.init(core.application); return { getAuthenticationMethodRegistry: () => this.authMethodsRegistry, };