From 19d4ba69c0ffde755022693da0d992f4bd8c1a0e Mon Sep 17 00:00:00 2001 From: "Yuanqi(Ella) Zhu" Date: Thu, 21 Mar 2024 18:09:54 +0000 Subject: [PATCH] Make sure customer always have a default datasource Signed-off-by: Yuanqi(Ella) Zhu --- CHANGELOG.md | 1 + .../create_data_source_wizard.test.tsx | 3 +- .../create_data_source_wizard.tsx | 4 +++ .../data_source_table.test.tsx | 7 ++++- .../data_source_table/data_source_table.tsx | 25 +++++++++++++++- .../edit_data_source.test.tsx | 7 ++++- .../edit_data_source/edit_data_source.tsx | 7 +++++ .../public/components/utils.test.ts | 23 ++++++++++++++ .../public/components/utils.ts | 30 ++++++++++++++++++- .../data_source_management/public/mocks.ts | 9 ++++++ 10 files changed, 111 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b2abfe2c19e..4d23c021bfd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Workspace] Validate if workspace exists when setup inside a workspace ([#6154](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6154)) - [Workspace] Register a workspace dropdown menu at the top of left nav bar ([#6150](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6150)) - [Multiple Datasource] Add icon in datasource table page to show the default datasource ([#6231](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6231)) +- [Multiple Datasource] Make sure customer always have a default datasource ([#6237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6237)) ### 🐛 Bug Fixes diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.test.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.test.tsx index 1fe6e4f5d499..bc47fbaa12e3 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.test.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.test.tsx @@ -53,7 +53,7 @@ describe('Datasource Management: Create Datasource Wizard', () => { test('should create datasource successfully', async () => { spyOn(utils, 'createSingleDataSource').and.returnValue({}); - + spyOn(utils, 'handleSetDefaultDatasourceDuringCreation').and.returnValue({}); await act(async () => { // @ts-ignore await component.find(formIdentifier).first().prop('handleSubmit')( @@ -62,6 +62,7 @@ describe('Datasource Management: Create Datasource Wizard', () => { }); expect(utils.createSingleDataSource).toHaveBeenCalled(); expect(history.push).toBeCalledWith(''); + expect(utils.handleSetDefaultDatasourceDuringCreation).toHaveBeenCalled(); }); test('should fail to create datasource', async () => { diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx index 06b77efd9b94..9409f9ef9c15 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx @@ -21,6 +21,7 @@ import { getDataSources, testConnection, fetchDataSourceVersion, + handleSetDefaultDatasourceDuringCreation, } from '../utils'; import { LoadingMask } from '../loading_mask'; @@ -35,6 +36,7 @@ export const CreateDataSourceWizard: React.FunctionComponent().services; /* State Variables */ @@ -76,6 +78,8 @@ export const CreateDataSourceWizard: React.FunctionComponent { it('should delete confirm modal confirm button work normally', async () => { spyOn(utils, 'deleteMultipleDataSources').and.returnValue(Promise.resolve({})); - + spyOn(utils, 'handleSetDefaultDatasourceAfterDeletion').and.returnValue({}); act(() => { // @ts-ignore component.find(tableIdentifier).props().selection.onSelectionChange(getMappedDataSources); @@ -143,10 +145,12 @@ describe('DataSourceTable', () => { }); component.update(); expect(component.find(confirmModalIdentifier).exists()).toBe(false); + expect(utils.handleSetDefaultDatasourceAfterDeletion).toHaveBeenCalled(); }); it('should delete datasources & fail', async () => { spyOn(utils, 'deleteMultipleDataSources').and.returnValue(Promise.reject({})); + spyOn(utils, 'handleSetDefaultDatasourceAfterDeletion').and.returnValue({}); act(() => { // @ts-ignore component.find(tableIdentifier).props().selection.onSelectionChange(getMappedDataSources); @@ -162,6 +166,7 @@ describe('DataSourceTable', () => { }); component.update(); expect(utils.deleteMultipleDataSources).toHaveBeenCalled(); + expect(utils.handleSetDefaultDatasourceAfterDeletion).not.toHaveBeenCalled(); // @ts-ignore expect(component.find(confirmModalIdentifier).exists()).toBe(false); }); diff --git a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx index b27f957fe142..a8b0652f8ecf 100644 --- a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx +++ b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx @@ -29,7 +29,11 @@ import { } from '../../../../opensearch_dashboards_react/public'; import { DataSourceManagementContext, DataSourceTableItem, ToastMessageItem } from '../../types'; import { CreateButton } from '../create_button'; -import { deleteMultipleDataSources, getDataSources } from '../utils'; +import { + deleteMultipleDataSources, + getDataSources, + handleSetDefaultDatasourceAfterDeletion, +} from '../utils'; import { LoadingMask } from '../loading_mask'; /* Table config */ @@ -228,6 +232,9 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => { deleteMultipleDataSources(savedObjects.client, selectedDataSources) .then(() => { + // Check if default data source is deleted or not. + // if yes, then set the first existing datasource as default datasource. + setDefaultDataSource(); setSelectedDataSources([]); // Fetch data sources fetchDataSources(); @@ -245,6 +252,22 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => { }); }; + const setDefaultDataSource = async () => { + try { + for (const dataSource of selectedDataSources) { + if (uiSettings.get('defaultDataSource') === dataSource.id) { + await handleSetDefaultDatasourceAfterDeletion(savedObjects.client, uiSettings); + } + } + } catch (e) { + setIsLoading(false); + handleDisplayToastMessage({ + id: 'dataSourcesManagement.editDataSource.setDefaultDataSourceFailMsg', + defaultMessage: 'Unable to set the Data Source to default. Please try it again.', + }); + } + }; + /* Table selection handlers */ const onSelectionChange = (selected: DataSourceTableItem[]) => { setSelectedDataSources(selected); 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 6cab62b49c8c..c295a036c8b7 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 @@ -138,7 +138,8 @@ describe('Datasource Management: Edit Datasource Wizard', () => { }); test('should delete datasource successfully', async () => { spyOn(utils, 'deleteDataSourceById').and.returnValue({}); - + spyOn(utils, 'handleSetDefaultDatasourceAfterDeletion').and.returnValue({}); + spyOn(uiSettings, 'get').and.returnValue('test1'); await act(async () => { // @ts-ignore await component.find(formIdentifier).first().prop('onDeleteDataSource')( @@ -147,9 +148,12 @@ describe('Datasource Management: Edit Datasource Wizard', () => { }); expect(utils.deleteDataSourceById).toHaveBeenCalled(); expect(history.push).toBeCalledWith(''); + expect(utils.handleSetDefaultDatasourceAfterDeletion).toHaveBeenCalled(); }); test('should fail to delete datasource', async () => { spyOn(utils, 'deleteDataSourceById').and.throwError('error'); + spyOn(utils, 'handleSetDefaultDatasourceAfterDeletion').and.returnValue({}); + spyOn(uiSettings, 'get').and.returnValue('test1'); await act(async () => { // @ts-ignore await component.find(formIdentifier).first().prop('onDeleteDataSource')( @@ -158,6 +162,7 @@ describe('Datasource Management: Edit Datasource Wizard', () => { }); component.update(); expect(utils.deleteDataSourceById).toHaveBeenCalled(); + expect(utils.handleSetDefaultDatasourceAfterDeletion).not.toHaveBeenCalled(); }); test('should test connection', () => { spyOn(utils, 'testConnection'); 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 46e253b2b85b..82595f7a9ac6 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 @@ -17,6 +17,7 @@ import { getDataSources, testConnection, updateDataSourceById, + handleSetDefaultDatasourceAfterDeletion, } from '../utils'; import { getEditBreadcrumbs } from '../breadcrumbs'; import { EditDataSourceForm } from './components/edit_form/edit_data_source_form'; @@ -109,6 +110,12 @@ export const EditDataSource: React.FunctionComponent { describe('Get data source', () => { @@ -274,7 +278,26 @@ describe('DataSourceManagement: Utils.ts', () => { expect(getDefaultAuthMethod(authenticationMethodRegistery)?.name).toBe(AuthType.NoAuth); }); }); + describe('handleSetDefaultDatasourceAfterDeletion', () => { + test('should remove defaultDataSource setting and set new defaultDataSource if data sources exist', async () => { + mockResponseForSavedObjectsCalls(savedObjects.client, 'find', getDataSourcesResponse); + mockUiSettingsCalls(uiSettings, 'get', 'test'); + await handleSetDefaultDatasourceAfterDeletion(savedObjects.client, uiSettings); + expect(uiSettings.set).toHaveBeenCalled(); + }); + }); + describe('handleSetDefaultDatasourceDuringCreation', () => { + test('should set defaultDataSource if only one data source exists', async () => { + mockResponseForSavedObjectsCalls( + savedObjects.client, + 'find', + getDataSourcesResponse.savedObjects.slice(0, 1) + ); + await handleSetDefaultDatasourceDuringCreation(savedObjects.client, uiSettings); + expect(uiSettings.set).toHaveBeenCalled(); + }); + }); describe('Check extractRegisteredAuthTypeCredentials method', () => { test('Should extract credential field successfully', () => { const authTypeToBeTested = 'Some Auth Type'; diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index 10ce2bb6bf43..1cc069339926 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -3,7 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { HttpStart, SavedObjectsClientContract, SavedObject } from 'src/core/public'; +import { + HttpStart, + SavedObjectsClientContract, + SavedObject, + IUiSettingsClient, +} from 'src/core/public'; import { DataSourceAttributes, DataSourceTableItem, @@ -49,6 +54,29 @@ export async function getDataSourcesWithFields( return response?.savedObjects; } +export async function handleSetDefaultDatasourceAfterDeletion( + savedObjects: SavedObjectsClientContract, + uiSettings: IUiSettingsClient +) { + uiSettings.remove('defaultDataSource'); + const listOfDataSources: DataSourceTableItem[] = await getDataSources(savedObjects); + if (Array.isArray(listOfDataSources) && listOfDataSources.length >= 1) { + const datasourceId = listOfDataSources[0].id; + await uiSettings.set('defaultDataSource', datasourceId); + } +} + +export async function handleSetDefaultDatasourceDuringCreation( + savedObjects: SavedObjectsClientContract, + uiSettings: IUiSettingsClient +) { + const listOfDataSources: DataSourceTableItem[] = await getDataSources(savedObjects); + if (Array.isArray(listOfDataSources) && listOfDataSources.length === 1) { + const datasourceId = listOfDataSources[0].id; + await uiSettings.set('defaultDataSource', datasourceId); + } +} + 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 e472860893ef..bca9f44a855d 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -6,6 +6,7 @@ import React from 'react'; import { throwError } from 'rxjs'; import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; +import { IUiSettingsClient } from 'src/core/public'; import { AuthType, DataSourceAttributes } from './types'; import { coreMock } from '../../../core/public/mocks'; import { @@ -263,6 +264,14 @@ export const mockErrorResponseForSavedObjectsCalls = ( ); }; +export const mockUiSettingsCalls = ( + uiSettings: IUiSettingsClient, + uiSettingsMethodName: 'get' | 'set', + response: any +) => { + (uiSettings[uiSettingsMethodName] as jest.Mock).mockReturnValue(response); +}; + export interface TestPluginReturn { setup: DataSourceManagementPluginSetup; doStart: () => DataSourceManagementPluginStart;