Skip to content

Commit

Permalink
Make sure customer always have a default datasource
Browse files Browse the repository at this point in the history
Signed-off-by: Yuanqi(Ella) Zhu <[email protected]>
  • Loading branch information
zhyuanqi committed Mar 25, 2024
1 parent f27e80a commit fe3b323
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [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] Add TLS configuration for multiple data sources ([#6171](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6171))
- [Multiple Datasource] Make sure customer always have a default datasource ([#6237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6237))

### 🐛 Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')(
Expand All @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getDataSources,
testConnection,
fetchDataSourceVersion,
handleSetDefaultDatasourceDuringCreation,
} from '../utils';
import { LoadingMask } from '../loading_mask';

Expand All @@ -35,6 +36,7 @@ export const CreateDataSourceWizard: React.FunctionComponent<CreateDataSourceWiz
setBreadcrumbs,
http,
notifications: { toasts },
uiSettings,
} = useOpenSearchDashboards<DataSourceManagementContext>().services;

/* State Variables */
Expand Down Expand Up @@ -76,6 +78,8 @@ export const CreateDataSourceWizard: React.FunctionComponent<CreateDataSourceWiz
const version = await fetchDataSourceVersion(http, attributes);
attributes.dataSourceVersion = version.dataSourceVersion;
await createSingleDataSource(savedObjects.client, attributes);
// Set the first create data source as default data source.
await handleSetDefaultDatasourceDuringCreation(savedObjects.client, uiSettings);
props.history.push('');
} catch (e) {
setIsLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ describe('DataSourceTable', () => {

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);
Expand All @@ -143,10 +143,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);
Expand All @@ -162,6 +164,7 @@ describe('DataSourceTable', () => {
});
component.update();
expect(utils.deleteMultipleDataSources).toHaveBeenCalled();
expect(utils.handleSetDefaultDatasourceAfterDeletion).not.toHaveBeenCalled();
// @ts-ignore
expect(component.find(confirmModalIdentifier).exists()).toBe(false);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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();
Expand All @@ -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({

Check warning on line 264 in src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx#L263-L264

Added lines #L263 - L264 were not covered by tests
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')(
Expand All @@ -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')(
Expand All @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -109,6 +110,12 @@ export const EditDataSource: React.FunctionComponent<RouteComponentProps<{ id: s
setIsLoading(true);
try {
await deleteDataSourceById(props.match.params.id, savedObjects.client);

// If deleted datasource is the default datasource, then set the first existing
// datasource as default datasource.
if (uiSettings.get('defaultDataSource') === dataSourceID) {
await handleSetDefaultDatasourceAfterDeletion(savedObjects.client, uiSettings);
}
props.history.push('');
} catch (e) {
setIsLoading(false);
Expand Down
28 changes: 28 additions & 0 deletions src/plugins/data_source_management/public/components/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
isValidUrl,
testConnection,
updateDataSourceById,
handleSetDefaultDatasourceAfterDeletion,
handleSetDefaultDatasourceDuringCreation,
} from './utils';
import { coreMock } from '../../../../core/public/mocks';
import {
Expand All @@ -24,6 +26,8 @@ import {
mockDataSourceAttributesWithAuth,
mockErrorResponseForSavedObjectsCalls,
mockResponseForSavedObjectsCalls,
mockUiSettingsCalls,
getSingleDataSourceResponse,
} from '../mocks';
import {
AuthType,
Expand All @@ -36,6 +40,7 @@ import { AuthenticationMethod, AuthenticationMethodRegistery } from '../auth_reg
import { deepEqual } from 'assert';

const { savedObjects } = coreMock.createStart();
const { uiSettings } = coreMock.createStart();

describe('DataSourceManagement: Utils.ts', () => {
describe('Get data source', () => {
Expand Down Expand Up @@ -274,7 +279,30 @@ 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).toHaveBeenCalledWith('defaultDataSource', 'test');
});
});
describe('handleSetDefaultDatasourceDuringCreation', () => {
beforeEach(() => {
jest.clearAllMocks(); // Reset all mock calls before each test
});
test('should not set defaultDataSource if more than one data source exists', async () => {
mockResponseForSavedObjectsCalls(savedObjects.client, 'find', getDataSourcesResponse);
await handleSetDefaultDatasourceDuringCreation(savedObjects.client, uiSettings);
expect(uiSettings.set).not.toHaveBeenCalled();
});
test('should set defaultDataSource if only one data source exists', async () => {
mockResponseForSavedObjectsCalls(savedObjects.client, 'find', getSingleDataSourceResponse);
await handleSetDefaultDatasourceDuringCreation(savedObjects.client, uiSettings);
expect(uiSettings.set).toHaveBeenCalled();
});
});
describe('Check extractRegisteredAuthTypeCredentials method', () => {
test('Should extract credential field successfully', () => {
const authTypeToBeTested = 'Some Auth Type';
Expand Down
30 changes: 29 additions & 1 deletion src/plugins/data_source_management/public/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -49,6 +54,29 @@ export async function getDataSourcesWithFields(
return response?.savedObjects;
}

export async function handleSetDefaultDatasourceAfterDeletion(
savedObjectsClient: SavedObjectsClientContract,
uiSettings: IUiSettingsClient
) {
uiSettings.remove('defaultDataSource');
const listOfDataSources: DataSourceTableItem[] = await getDataSources(savedObjectsClient);
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
Expand Down
24 changes: 24 additions & 0 deletions src/plugins/data_source_management/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -62,6 +63,21 @@ export const mockManagementPlugin = {
docLinks,
};

export const getSingleDataSourceResponse = {
savedObjects: [
{
id: 'test',
type: 'data-source',
description: 'test datasource',
title: 'test',
get(field: string) {
const me: any = this || {};
return me[field];
},
},
],
};

/* Mock data responses - JSON*/
export const getDataSourcesResponse = {
savedObjects: [
Expand Down Expand Up @@ -263,6 +279,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;
Expand Down

0 comments on commit fe3b323

Please sign in to comment.