From 1c09ca61e01b4908b1c9a58d789549c05fb9dde3 Mon Sep 17 00:00:00 2001 From: Zhongnan Su Date: Mon, 22 May 2023 13:03:34 -0700 Subject: [PATCH] [Multiple Datasource] Support Amazon OpenSearch Serverless (#3957) (#4066) * [Multiple Datasource]Support Amazon OpenSearch Serverless in SigV4 * remove experimental text in yml * Refactor create data source form for authentication Signed-off-by: Su (cherry picked from commit e73779084f34b28e2cf69f28a8563e44e09f168d) --- config/opensearch_dashboards.yml | 3 +- package.json | 4 +- .../data_source/common/data_sources/types.ts | 6 + .../data_source/opensearch_dashboards.json | 3 +- .../server/client/configure_client.test.ts | 24 ++ .../server/client/configure_client.ts | 3 +- .../server/client/configure_client_utils.ts | 3 +- .../legacy/configure_legacy_client.test.ts | 45 +- .../server/legacy/configure_legacy_client.ts | 8 +- .../data_source_connection_validator.ts | 12 +- .../server/routes/test_connection.ts | 14 +- ...ata_source_saved_objects_client_wrapper.ts | 11 +- .../create_data_source_form.test.tsx | 2 + .../create_form/create_data_source_form.tsx | 386 +++++++++++------- .../create_data_source_wizard.test.tsx | 13 + .../create_data_source_wizard.tsx | 1 + .../edit_form/edit_data_source_form.tsx | 64 ++- .../update_aws_credential_modal.tsx | 14 + .../validation/datasource_form_validation.ts | 7 + .../data_source_management/public/types.ts | 17 + yarn.lock | 16 +- 21 files changed, 461 insertions(+), 195 deletions(-) diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 1d751769f701..d7e0d390b0fc 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -229,8 +229,7 @@ # functionality in Visualization. # vis_builder.enabled: false -# Set the value of this setting to true to enable the experimental multiple data source -# support feature. Use with caution. +# Set the value of this setting to true to enable multiple data source feature. #data_source.enabled: false # Set the value of these settings to customize crypto materials to encryption saved credentials # in data sources. diff --git a/package.json b/package.json index 8051961e3905..0a34972bf9af 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "@hapi/vision": "^6.1.0", "@hapi/wreck": "^17.1.0", "@opensearch-project/opensearch": "^1.1.0", - "@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.1.0", + "@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.2.0", "@osd/ace": "1.0.0", "@osd/analytics": "1.0.0", "@osd/apm-config-loader": "1.0.0", @@ -172,7 +172,7 @@ "dns-sync": "^0.2.1", "elastic-apm-node": "^3.7.0", "elasticsearch": "^16.7.0", - "http-aws-es": "6.0.0", + "http-aws-es": "npm:@zhongnansu/http-aws-es@6.0.1", "execa": "^4.0.2", "expiry-js": "0.1.7", "fast-deep-equal": "^3.1.1", diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 366e5a0f3f55..8763c5306c15 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -25,6 +25,7 @@ export interface SigV4Content extends SavedObjectAttributes { accessKey: string; secretKey: string; region: string; + service?: SigV4ServiceName; } export interface UsernamePasswordTypedContent extends SavedObjectAttributes { @@ -37,3 +38,8 @@ export enum AuthType { UsernamePasswordType = 'username_password', SigV4 = 'sigv4', } + +export enum SigV4ServiceName { + OpenSearch = 'es', + OpenSearchServerless = 'aoss', +} diff --git a/src/plugins/data_source/opensearch_dashboards.json b/src/plugins/data_source/opensearch_dashboards.json index 71183a411c79..871858403cf3 100644 --- a/src/plugins/data_source/opensearch_dashboards.json +++ b/src/plugins/data_source/opensearch_dashboards.json @@ -5,5 +5,6 @@ "server": true, "ui": true, "requiredPlugins": [], - "optionalPlugins": [] + "optionalPlugins": [], + "extraPublicDirs": ["common/data_sources"] } diff --git a/src/plugins/data_source/server/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index 93b9f4e2055a..32aacb5ead72 100644 --- a/src/plugins/data_source/server/client/configure_client.test.ts +++ b/src/plugins/data_source/server/client/configure_client.test.ts @@ -167,6 +167,30 @@ describe('configureClient', () => { expect(decodeAndDecryptSpy).toHaveBeenCalledTimes(2); }); + test('configure client with auth.type == sigv4, service == aoss, should successfully call new Client()', async () => { + savedObjectsMock.get.mockReset().mockResolvedValueOnce({ + id: DATA_SOURCE_ID, + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + attributes: { + ...dataSourceAttr, + auth: { + type: AuthType.SigV4, + credentials: { ...sigV4AuthContent, service: 'aoss' }, + }, + }, + references: [], + }); + + jest.spyOn(cryptographyMock, 'decodeAndDecrypt').mockResolvedValue({ + decryptedText: 'accessKey', + encryptionContext: { endpoint: 'http://localhost' }, + }); + + await configureClient(dataSourceClientParams, clientPoolSetup, config, logger); + + expect(ClientMock).toHaveBeenCalledTimes(1); + }); + test('configure test client for non-exist datasource should not call saved object api, nor decode any credential', async () => { const decodeAndDecryptSpy = jest.spyOn(cryptographyMock, 'decodeAndDecrypt').mockResolvedValue({ decryptedText: 'password', diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index db0107dadd4f..b1ebea601ab3 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -160,7 +160,7 @@ const getBasicAuthClient = ( }; const getAWSClient = (credential: SigV4Content, clientOptions: ClientOptions): Client => { - const { accessKey, secretKey, region } = credential; + const { accessKey, secretKey, region, service } = credential; const credentialProvider = (): Promise => { return new Promise((resolve) => { @@ -172,6 +172,7 @@ const getAWSClient = (credential: SigV4Content, clientOptions: ClientOptions): C ...AwsSigv4Signer({ region, getCredentials: credentialProvider, + service, }), ...clientOptions, }); diff --git a/src/plugins/data_source/server/client/configure_client_utils.ts b/src/plugins/data_source/server/client/configure_client_utils.ts index 67f40267dd33..4405f8f14559 100644 --- a/src/plugins/data_source/server/client/configure_client_utils.ts +++ b/src/plugins/data_source/server/client/configure_client_utils.ts @@ -91,7 +91,7 @@ export const getAWSCredential = async ( cryptography: CryptographyServiceSetup ): Promise => { const { endpoint } = dataSource; - const { accessKey, secretKey, region } = dataSource.auth.credentials! as SigV4Content; + const { accessKey, secretKey, region, service } = dataSource.auth.credentials! as SigV4Content; const { decryptedText: accessKeyText, @@ -122,6 +122,7 @@ export const getAWSCredential = async ( region, accessKey: accessKeyText, secretKey: secretKeyText, + service, }; return credential; diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts index c047da70b285..59c110d06dc5 100644 --- a/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts @@ -6,7 +6,7 @@ import { SavedObjectsClientContract } from '../../../../core/server'; import { loggingSystemMock, savedObjectsClientMock } from '../../../../core/server/mocks'; import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; -import { AuthType, DataSourceAttributes } from '../../common/data_sources'; +import { AuthType, DataSourceAttributes, SigV4Content } from '../../common/data_sources'; import { DataSourcePluginConfigType } from '../../config'; import { cryptographyServiceSetupMock } from '../cryptography_service.mocks'; import { CryptographyServiceSetup } from '../cryptography_service'; @@ -27,6 +27,7 @@ describe('configureLegacyClient', () => { let clientPoolSetup: OpenSearchClientPoolSetup; let configOptions: ConfigOptions; let dataSourceAttr: DataSourceAttributes; + let sigV4AuthContent: SigV4Content; let mockOpenSearchClientInstance: { close: jest.Mock; @@ -71,6 +72,12 @@ describe('configureLegacyClient', () => { }, } as DataSourceAttributes; + sigV4AuthContent = { + region: 'us-east-1', + accessKey: 'accessKey', + secretKey: 'secretKey', + }; + clientPoolSetup = { getClientFromPool: jest.fn(), addClientToPool: jest.fn(), @@ -157,6 +164,42 @@ describe('configureLegacyClient', () => { expect(mockResult).toBeDefined(); }); + test('configure client with auth.type == sigv4 and service param, should call new Client() with service param', async () => { + savedObjectsMock.get.mockReset().mockResolvedValueOnce({ + id: DATA_SOURCE_ID, + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + attributes: { + ...dataSourceAttr, + auth: { + type: AuthType.SigV4, + credentials: { ...sigV4AuthContent, service: 'aoss' }, + }, + }, + references: [], + }); + + parseClientOptionsMock.mockReturnValue(configOptions); + + jest.spyOn(cryptographyMock, 'decodeAndDecrypt').mockResolvedValue({ + decryptedText: 'accessKey', + encryptionContext: { endpoint: 'http://localhost' }, + }); + + await configureLegacyClient( + dataSourceClientParams, + callApiParams, + clientPoolSetup, + config, + logger + ); + + expect(parseClientOptionsMock).toHaveBeenCalled(); + expect(ClientMock).toHaveBeenCalledTimes(1); + expect(ClientMock).toHaveBeenCalledWith(expect.objectContaining({ service: 'aoss' })); + + expect(savedObjectsMock.get).toHaveBeenCalledTimes(1); + }); + test('configure client with auth.type == username_password and password contaminated', async () => { const decodeAndDecryptSpy = jest .spyOn(cryptographyMock, 'decodeAndDecrypt') diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.ts index 6e9522c1ec24..cbdeaf946a4d 100644 --- a/src/plugins/data_source/server/legacy/configure_legacy_client.ts +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.ts @@ -5,10 +5,9 @@ import { Client } from '@opensearch-project/opensearch-next'; import { Client as LegacyClient, ConfigOptions } from 'elasticsearch'; -import { Credentials } from 'aws-sdk'; +import { Credentials, Config } from 'aws-sdk'; import { get } from 'lodash'; import HttpAmazonESConnector from 'http-aws-es'; -import { Config } from 'aws-sdk'; import { Headers, LegacyAPICaller, @@ -27,7 +26,7 @@ import { CryptographyServiceSetup } from '../cryptography_service'; import { DataSourceClientParams, LegacyClientCallAPIParams } from '../types'; import { OpenSearchClientPoolSetup } from '../client'; import { parseClientOptions } from './client_config'; -import { createDataSourceError, DataSourceError } from '../lib/error'; +import { createDataSourceError } from '../lib/error'; import { getRootClient, getAWSCredential, @@ -195,13 +194,14 @@ const getBasicAuthClient = async ( }; const getAWSClient = (credential: SigV4Content, clientOptions: ConfigOptions): LegacyClient => { - const { accessKey, secretKey, region } = credential; + const { accessKey, secretKey, region, service } = credential; const client = new LegacyClient({ connectionClass: HttpAmazonESConnector, awsConfig: new Config({ region, credentials: new Credentials({ accessKeyId: accessKey, secretAccessKey: secretKey }), }), + service, ...clientOptions, }); return client; diff --git a/src/plugins/data_source/server/routes/data_source_connection_validator.ts b/src/plugins/data_source/server/routes/data_source_connection_validator.ts index ecec07dafcc4..f3233859e854 100644 --- a/src/plugins/data_source/server/routes/data_source_connection_validator.ts +++ b/src/plugins/data_source/server/routes/data_source_connection_validator.ts @@ -5,13 +5,19 @@ import { OpenSearchClient } from 'opensearch-dashboards/server'; import { createDataSourceError } from '../lib/error'; - +import { SigV4ServiceName } from '../../common/data_sources'; export class DataSourceConnectionValidator { - constructor(private readonly callDataCluster: OpenSearchClient) {} + constructor( + private readonly callDataCluster: OpenSearchClient, + private readonly dataSourceAttr: any + ) {} async validate() { try { - return await this.callDataCluster.info(); + // Amazon OpenSearch Serverless does not support .info() API + if (this.dataSourceAttr.auth?.credentials?.service === SigV4ServiceName.OpenSearchServerless) + return await this.callDataCluster.cat.indices(); + return await this.callDataCluster.info(); } catch (e) { throw createDataSourceError(e); } diff --git a/src/plugins/data_source/server/routes/test_connection.ts b/src/plugins/data_source/server/routes/test_connection.ts index f36702171b45..cba42517e535 100644 --- a/src/plugins/data_source/server/routes/test_connection.ts +++ b/src/plugins/data_source/server/routes/test_connection.ts @@ -5,7 +5,7 @@ import { schema } from '@osd/config-schema'; import { IRouter, OpenSearchClient } from 'opensearch-dashboards/server'; -import { AuthType, DataSourceAttributes } from '../../common/data_sources'; +import { AuthType, DataSourceAttributes, SigV4ServiceName } from '../../common/data_sources'; import { DataSourceConnectionValidator } from './data_source_connection_validator'; import { DataSourceServiceSetup } from '../data_source_service'; import { CryptographyServiceSetup } from '../cryptography_service'; @@ -40,6 +40,10 @@ export const registerTestConnectionRoute = ( region: schema.string(), accessKey: schema.string(), secretKey: schema.string(), + service: schema.oneOf([ + schema.literal(SigV4ServiceName.OpenSearch), + schema.literal(SigV4ServiceName.OpenSearchServerless), + ]), }), ]) ), @@ -61,9 +65,13 @@ export const registerTestConnectionRoute = ( testClientDataSourceAttr: dataSourceAttr as DataSourceAttributes, } ); - const dsValidator = new DataSourceConnectionValidator(dataSourceClient); - await dsValidator.validate(); + const dataSourceValidator = new DataSourceConnectionValidator( + dataSourceClient, + dataSourceAttr + ); + + await dataSourceValidator.validate(); return response.ok({ body: { diff --git a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts index 6abcaa6c7909..12d60b8da51e 100644 --- a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts +++ b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts @@ -301,7 +301,7 @@ export class DataSourceSavedObjectsClientWrapper { ); } - const { accessKey, secretKey, region } = credentials as SigV4Content; + const { accessKey, secretKey, region, service } = credentials as SigV4Content; if (!accessKey) { throw SavedObjectsErrorHelpers.createBadRequestError( @@ -320,6 +320,12 @@ export class DataSourceSavedObjectsClientWrapper { '"auth.credentials.region" attribute is required' ); } + + if (!service) { + throw SavedObjectsErrorHelpers.createBadRequestError( + '"auth.credentials.service" attribute is required' + ); + } break; default: throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid auth type: '${type}'`); @@ -457,7 +463,7 @@ export class DataSourceSavedObjectsClientWrapper { private async encryptSigV4Credential(auth: T, encryptionContext: EncryptionContext) { const { - credentials: { accessKey, secretKey, region }, + credentials: { accessKey, secretKey, region, service }, } = auth; return { @@ -466,6 +472,7 @@ export class DataSourceSavedObjectsClientWrapper { region, accessKey: await this.cryptography.encryptAndEncode(accessKey, encryptionContext), secretKey: await this.cryptography.encryptAndEncode(secretKey, encryptionContext), + service, }, }; } diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.test.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.test.tsx index efb3453b6f35..ad1d02c87db4 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.test.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.test.tsx @@ -27,6 +27,7 @@ describe('Datasource Management: Create Datasource form', () => { let component: ReactWrapper, React.Component<{}, {}, any>>; const mockSubmitHandler = jest.fn(); const mockTestConnectionHandler = jest.fn(); + const mockCancelHandler = jest.fn(); const getFields = (comp: ReactWrapper, React.Component<{}, {}, any>>) => { return { @@ -65,6 +66,7 @@ describe('Datasource Management: Create Datasource form', () => { ), diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx index 1c08da5d6371..8b36e13e7deb 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx @@ -5,7 +5,9 @@ import React from 'react'; import { + EuiBottomBar, EuiButton, + EuiButtonEmpty, EuiFieldPassword, EuiFieldText, EuiFlexGroup, @@ -19,13 +21,14 @@ import { } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; +import { SigV4Content, SigV4ServiceName } from '../../../../../../data_source/common/data_sources'; import { AuthType, credentialSourceOptions, DataSourceAttributes, DataSourceManagementContextValue, UsernamePasswordTypedContent, - SigV4Content, + sigV4ServiceOptions, } from '../../../../types'; import { Header } from '../header'; import { context as contextType } from '../../../../../../opensearch_dashboards_react/public'; @@ -41,6 +44,7 @@ export interface CreateDataSourceProps { existingDatasourceNamesList: string[]; handleSubmit: (formValues: DataSourceAttributes) => void; handleTestConnection: (formValues: DataSourceAttributes) => void; + handleCancel: () => void; } export interface CreateDataSourceState { /* Validation */ @@ -121,7 +125,31 @@ export class CreateDataSourceForm extends React.Component< }; onChangeAuthType = (e: React.ChangeEvent) => { - this.setState({ auth: { ...this.state.auth, type: e.target.value as AuthType } }); + const authType = e.target.value as AuthType; + this.setState({ + auth: { + ...this.state.auth, + type: authType, + credentials: { + ...this.state.auth.credentials, + service: + (this.state.auth.credentials.service as SigV4ServiceName) || + SigV4ServiceName.OpenSearch, + }, + }, + }); + }; + + onChangeSigV4ServiceName = (e: React.ChangeEvent) => { + this.setState({ + auth: { + ...this.state.auth, + credentials: { + ...this.state.auth.credentials, + service: e.target.value as SigV4ServiceName, + }, + }, + }); }; onChangeUsername = (e: { target: { value: any } }) => { @@ -267,6 +295,7 @@ export class CreateDataSourceForm extends React.Component< region: this.state.auth.credentials.region, accessKey: this.state.auth.credentials.accessKey, secretKey: this.state.auth.credentials.secretKey, + service: this.state.auth.credentials.service || SigV4ServiceName.OpenSearch, } as SigV4Content; } @@ -391,6 +420,19 @@ export class CreateDataSourceForm extends React.Component< data-test-subj="createDataSourceFormRegionField" /> + + this.onChangeSigV4ServiceName(e)} + name="ServiceName" + data-test-subj="createDataSourceFormAuthTypeSelect" + /> + { return ( - - {this.renderHeader()} - - - {/* Endpoint section */} - {this.renderSectionHeader( - 'dataSourceManagement.connectToDataSource.connectionDetails', - 'Connection Details' - )} - - - {/* Title */} - - + + {this.renderHeader()} + + + {/* Endpoint section */} + {this.renderSectionHeader( + 'dataSourceManagement.connectToDataSource.connectionDetails', + 'Connection Details' + )} + + + {/* Title */} + - + error={this.state.formErrorsByField.title} + > + + - {/* Description */} - - - - - {/* Endpoint URL */} - - + + + + {/* Endpoint URL */} + - + error={this.state.formErrorsByField.endpoint} + > + + - {/* Authentication Section: */} + {/* Authentication Section: */} - + - {this.renderSectionHeader( - 'dataSourceManagement.connectToDataSource.authenticationHeader', - 'Authentication Method' - )} - + {this.renderSectionHeader( + 'dataSourceManagement.connectToDataSource.authenticationHeader', + 'Authentication Method' + )} + - - - - + + - - - - - {/* Credential source */} - - - this.onChangeAuthType(e)} - name="Credential" - data-test-subj="createDataSourceFormAuthTypeSelect" - /> - - - {/* Create New credentials */} - {this.state.auth.type === AuthType.UsernamePasswordType - ? this.renderCreateNewCredentialsForm(this.state.auth.type) - : null} - - {this.state.auth.type === AuthType.SigV4 - ? this.renderCreateNewCredentialsForm(this.state.auth.type) - : null} - - - - - - {/* Test Connection button*/} - - - - - {/* Create Data Source button*/} - - + - - - - - - + + + + + {/* Credential source */} + + + this.onChangeAuthType(e)} + name="Credential" + data-test-subj="createDataSourceFormAuthTypeSelect" + /> + + + {/* Create New credentials */} + {this.state.auth.type === AuthType.UsernamePasswordType + ? this.renderCreateNewCredentialsForm(this.state.auth.type) + : null} + + {this.state.auth.type === AuthType.SigV4 + ? this.renderCreateNewCredentialsForm(this.state.auth.type) + : null} + + + + + + {/* Test Connection button*/} + + + + + + + + + + + {this.renderBottomBar()} + + ); + }; + + renderBottomBar = () => { + return ( + + + + + + + + + + + + + + + + ); }; 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 162af4c891f7..adfbe8808637 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 @@ -46,6 +46,7 @@ describe('Datasource Management: Create Datasource Wizard', () => { }); component.update(); }); + test('should create datasource successfully', async () => { spyOn(utils, 'createSingleDataSource').and.returnValue({}); @@ -58,6 +59,7 @@ describe('Datasource Management: Create Datasource Wizard', () => { expect(utils.createSingleDataSource).toHaveBeenCalled(); expect(history.push).toBeCalledWith(''); }); + test('should fail to create datasource', async () => { spyOn(utils, 'createSingleDataSource').and.throwError('error'); await act(async () => { @@ -93,7 +95,17 @@ describe('Datasource Management: Create Datasource Wizard', () => { component.update(); expect(utils.testConnection).toHaveBeenCalled(); }); + + test('should go back to listing page if clicked on cancel button', async () => { + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('handleCancel')(); + }); + + expect(history.push).toBeCalledWith(''); + }); }); + describe('case2: should fail to load resources', () => { beforeEach(async () => { spyOn(utils, 'getDataSources').and.throwError(''); @@ -116,6 +128,7 @@ describe('Datasource Management: Create Datasource Wizard', () => { }); component.update(); }); + test('should not render component and go back to listing page', () => { expect(history.push).toBeCalledWith(''); }); 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 83477b7a2426..05489ca6258a 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 @@ -116,6 +116,7 @@ export const CreateDataSourceWizard: React.FunctionComponent props.history.push('')} existingDatasourceNamesList={existingDatasourceNamesList} /> {isLoading ? : null} 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 8e3128c7e987..2ea63e252295 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 @@ -23,13 +23,14 @@ import { } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; +import { SigV4Content, SigV4ServiceName } from '../../../../../../data_source/common/data_sources'; import { Header } from '../header'; import { AuthType, credentialSourceOptions, DataSourceAttributes, DataSourceManagementContextValue, - SigV4Content, + sigV4ServiceOptions, ToastMessageItem, UsernamePasswordTypedContent, } from '../../../../types'; @@ -46,9 +47,9 @@ import { UpdateAwsCredentialModal } from '../update_aws_credential_modal'; export interface EditDataSourceProps { existingDataSource: DataSourceAttributes; existingDatasourceNamesList: string[]; - handleSubmit: (formValues: DataSourceAttributes) => void; - handleTestConnection: (formValues: DataSourceAttributes) => void; - onDeleteDataSource?: () => void; + handleSubmit: (formValues: DataSourceAttributes) => Promise; + handleTestConnection: (formValues: DataSourceAttributes) => Promise; + onDeleteDataSource?: () => Promise; displayToastMessage: (info: ToastMessageItem) => void; } export interface EditDataSourceState { @@ -123,7 +124,10 @@ export class EditDataSourceForm extends React.Component { const isValid = !!this.state.auth.credentials.username?.trim().length; this.setState({ @@ -221,6 +226,18 @@ export class EditDataSourceForm extends React.Component) => { + this.setState({ + auth: { + ...this.state.auth, + credentials: { + ...this.state.auth.credentials, + service: e.target.value as SigV4ServiceName, + } as SigV4Content, + }, + }); + }; + onChangeRegion = (e: { target: { value: any } }) => { this.setState({ auth: { @@ -253,7 +270,7 @@ export class EditDataSourceForm extends React.Component { - const isValid = !!this.state.auth.credentials.accessKey; + const isValid = !!this.state.auth.credentials?.accessKey; this.setState({ formErrorsByField: { ...this.state.formErrorsByField, @@ -275,7 +292,7 @@ export class EditDataSourceForm extends React.Component { - const isValid = !!this.state.auth.credentials.secretKey; + const isValid = !!this.state.auth.credentials?.secretKey; this.setState({ formErrorsByField: { ...this.state.formErrorsByField, @@ -338,9 +355,9 @@ export class EditDataSourceForm extends React.Component { + onClickDeleteDataSource = async () => { if (this.props.onDeleteDataSource) { - this.props.onDeleteDataSource(); + await this.props.onDeleteDataSource(); } }; @@ -359,6 +376,7 @@ export class EditDataSourceForm extends React.Component @@ -788,6 +807,19 @@ export class EditDataSourceForm extends React.Component + + this.onChangeSigV4ServiceName(e)} + name="ServiceName" + data-test-subj="createDataSourceFormAuthTypeSelect" + /> + void; closeUpdateAwsCredentialModal: () => void; } export const UpdateAwsCredentialModal = ({ region, + service, handleUpdateAwsCredential, closeUpdateAwsCredentialModal, }: UpdateAwsCredentialModalProps) => { @@ -87,6 +91,16 @@ export const UpdateAwsCredentialModal = ({ + {/* Service Name */} + + + {sigV4ServiceOptions.find((option) => option.value === service)?.text} + + {/* Region */}