diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d8455859aa..e17bb7d1a207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Chrome] Introduce registerCollapsibleNavHeader to allow plugins to customize the rendering of nav menu header ([#5244](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5244)) - [Dynamic Configurations] Pass request headers when making application config calls ([#6164](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6164)) - [Discover] Options button to configure legacy mode and remove the top navigation option ([#6170](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6170)) +- [Multiple Datasource] Add default functionality for customer to choose default datasource ([#6058](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/6058)) ### 🐛 Bug Fixes diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx index 06d0af486bba..e5f73c503f6e 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx @@ -76,8 +76,10 @@ describe('Datasource Management: Edit Datasource Form', () => { @@ -245,7 +247,9 @@ describe('Datasource Management: Edit Datasource Form', () => { { expect(mockFn).toHaveBeenCalled(); }); + test('should set as the default datasource from header', () => { + // @ts-ignore + component.find('Header').prop('onClickSetDefault')(); + expect(mockFn).toHaveBeenCalled(); + }); + /* Save Changes */ test('should update the form with NoAuth on click save changes', async () => { await new Promise((resolve) => @@ -383,8 +393,10 @@ describe('With Registered Authentication', () => { @@ -422,8 +434,10 @@ describe('With Registered Authentication', () => { diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx index 2cb1db4515dd..6714881c4a11 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx @@ -50,9 +50,11 @@ import { extractRegisteredAuthTypeCredentials, getDefaultAuthMethod } from '../. export interface EditDataSourceProps { existingDataSource: DataSourceAttributes; existingDatasourceNamesList: string[]; + isDefault: boolean; handleSubmit: (formValues: DataSourceAttributes) => Promise; handleTestConnection: (formValues: DataSourceAttributes) => Promise; onDeleteDataSource?: () => Promise; + onSetDefaultDataSource: () => Promise; displayToastMessage: (info: ToastMessageItem) => void; } export interface EditDataSourceState { @@ -400,6 +402,12 @@ export class EditDataSourceForm extends React.Component { + if (this.props.onSetDefaultDataSource) { + await this.props.onSetDefaultDataSource(); + } + }; + onClickTestConnection = async () => { this.setState({ isLoading: true }); const isNewCredential = !!(this.state.auth.type !== this.props.existingDataSource.auth.type); @@ -634,6 +642,8 @@ export class EditDataSourceForm extends React.Component ); }; diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx index f679a7db6e67..5a23b72881a1 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx @@ -14,6 +14,7 @@ import { act } from 'react-dom/test-utils'; const headerTitleIdentifier = '[data-test-subj="editDataSourceTitle"]'; const deleteIconIdentifier = '[data-test-subj="editDatasourceDeleteIcon"]'; const confirmModalIdentifier = '[data-test-subj="editDatasourceDeleteConfirmModal"]'; +const setDefaultButtonIdentifier = '[data-test-subj="editSetDefaultDataSource"]'; describe('Datasource Management: Edit Datasource Header', () => { const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); @@ -31,6 +32,8 @@ describe('Datasource Management: Edit Datasource Header', () => { onClickDeleteIcon={mockFn} onClickTestConnection={mockFn} dataSourceName={dataSourceName} + onClickSetDefault={mockFn} + isDefault={false} /> ), { @@ -82,6 +85,8 @@ describe('Datasource Management: Edit Datasource Header', () => { onClickDeleteIcon={mockFn} onClickTestConnection={mockFn} dataSourceName={dataSourceName} + onClickSetDefault={mockFn} + isDefault={false} /> ), { @@ -97,4 +102,76 @@ describe('Datasource Management: Edit Datasource Header', () => { expect(component.find(deleteIconIdentifier).exists()).toBe(false); }); }); + describe('should render default icon as "Set as default" when isDefaultDataSourceState is false', () => { + const onClickSetDefault = jest.fn(); + const isDefaultDataSourceState = false; + beforeEach(() => { + component = mount( + wrapWithIntl( +
+ ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + + test('should render normally', () => { + expect(component.find(setDefaultButtonIdentifier).exists()).toBe(true); + }); + test('default button should show as "Set as default" and should be clickable', () => { + expect(component.find(setDefaultButtonIdentifier).first().text()).toBe('Set as default'); + expect(component.find(setDefaultButtonIdentifier).first().prop('disabled')).toBe(false); + expect(component.find(setDefaultButtonIdentifier).first().prop('iconType')).toBe('starEmpty'); + component.find(setDefaultButtonIdentifier).first().simulate('click'); + expect(onClickSetDefault).toHaveBeenCalled(); + }); + }); + describe('should render default icon as "Default" when isDefaultDataSourceState is true', () => { + const onClickSetDefault = jest.fn(); + const isDefaultDataSourceState = true; + beforeEach(() => { + component = mount( + wrapWithIntl( +
+ ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + + test('should render normally', () => { + expect(component.find(setDefaultButtonIdentifier).exists()).toBe(true); + }); + test('default button should show as "Default" and should be disabled.', () => { + expect(component.find(setDefaultButtonIdentifier).first().text()).toBe('Default'); + expect(component.find(setDefaultButtonIdentifier).first().prop('disabled')).toBe(true); + expect(component.find(setDefaultButtonIdentifier).first().prop('iconType')).toBe( + 'starFilled' + ); + }); + }); }); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx index 49c100b7ec9a..264647882574 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx @@ -14,6 +14,7 @@ import { EuiButtonIcon, EuiConfirmModal, EuiButton, + EuiButtonEmpty, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; @@ -25,22 +26,51 @@ export const Header = ({ isFormValid, onClickDeleteIcon, onClickTestConnection, + onClickSetDefault, dataSourceName, + isDefault, }: { showDeleteIcon: boolean; isFormValid: boolean; onClickDeleteIcon: () => void; onClickTestConnection: () => void; + onClickSetDefault: () => void; dataSourceName: string; + isDefault: boolean; }) => { /* State Variables */ const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [isDefaultDataSourceState, setIsDefaultDataSourceState] = useState(isDefault); const changeTitle = useOpenSearchDashboards().services.chrome .docTitle.change; changeTitle(dataSourceName); + const setDefaultAriaLabel = i18n.translate( + 'dataSourcesManagement.editDataSource.setDefaultDataSource', + { + defaultMessage: 'Set as a default Data Source.', + } + ); + + const renderDefaultIcon = () => { + return ( + { + onClickSetDefault(); + setIsDefaultDataSourceState(!isDefaultDataSourceState); + }} + disabled={isDefaultDataSourceState} + iconType={isDefaultDataSourceState ? 'starFilled' : 'starEmpty'} + aria-label={setDefaultAriaLabel} + data-test-subj="editSetDefaultDataSource" + > + {isDefaultDataSourceState ? 'Default' : 'Set as default'} + + ); + }; + const renderDeleteButton = () => { return ( <> @@ -144,6 +174,8 @@ export const Header = ({ {/* Right side buttons */} + {/* Test default button */} + {renderDefaultIcon()} {/* Test connection button */} {renderTestConnectionButton()} {/* Delete icon button */} diff --git a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.test.tsx index c1516a507d4a..6cab62b49c8c 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.test.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.test.tsx @@ -29,6 +29,7 @@ const notFoundIdentifier = '[data-test-subj="dataSourceNotFound"]'; describe('Datasource Management: Edit Datasource Wizard', () => { const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + const uiSettings = mockedContext.uiSettings; mockedContext.authenticationMethodRegistery.registerAuthenticationMethod( noAuthCredentialAuthMethod ); @@ -125,6 +126,16 @@ describe('Datasource Management: Edit Datasource Wizard', () => { component.update(); expect(utils.updateDataSourceById).toHaveBeenCalled(); }); + test('should set default data source', async () => { + spyOn(uiSettings, 'set').and.returnValue({}); + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('onSetDefaultDataSource')( + mockDataSourceAttributesWithAuth + ); + }); + expect(uiSettings.set).toHaveBeenCalled(); + }); test('should delete datasource successfully', async () => { spyOn(utils, 'deleteDataSourceById').and.returnValue({}); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx index bc2bac5b66b8..46e253b2b85b 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx @@ -38,6 +38,7 @@ export const EditDataSource: React.FunctionComponent { /* Initialization */ const { + uiSettings, savedObjects, setBreadcrumbs, http, @@ -83,6 +84,12 @@ export const EditDataSource: React.FunctionComponent { + await uiSettings.set('defaultDataSource', dataSourceID); + }; + + const isDefaultDataSource = uiSettings.get('defaultDataSource', null) === dataSourceID; + /* Handle submit - create data source*/ const handleSubmit = async (attributes: DataSourceAttributes) => { await updateDataSourceById(savedObjects.client, dataSourceID, attributes); @@ -128,7 +135,9 @@ export const EditDataSource: React.FunctionComponent