From dfd7d1daba2175ef90ad820413f07a20b43a2fa0 Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Wed, 6 Mar 2024 00:15:31 +0000 Subject: [PATCH 1/7] [Token Exchange Unification] Send registered crendentail field for dataSource creation and dataSource edition Signed-off-by: Xinrui Bai --- .../create_form/create_data_source_form.tsx | 31 ++++++++++++++----- .../edit_form/edit_data_source_form.tsx | 10 +++++- .../public/components/utils.ts | 16 ++++++++++ .../data_source_management/public/types.ts | 6 +++- 4 files changed, 53 insertions(+), 10 deletions(-) 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 25b082b8c6a..e33cdf5d8e5 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 @@ -39,7 +39,11 @@ import { isTitleValid, performDataSourceFormValidation, } from '../../../validation'; -import { getDefaultAuthMethod, isValidUrl } from '../../../utils'; +import { + extractRegisteredAuthTypeCredentials, + getDefaultAuthMethod, + isValidUrl, +} from '../../../utils'; export interface CreateDataSourceProps { existingDatasourceNamesList: string[]; @@ -56,7 +60,11 @@ export interface CreateDataSourceState { endpoint: string; auth: { type: AuthType; - credentials: UsernamePasswordTypedContent | SigV4Content | undefined; + credentials: + | UsernamePasswordTypedContent + | SigV4Content + | { [key: string]: string } + | undefined; }; } @@ -298,22 +306,29 @@ export class CreateDataSourceForm extends React.Component< getFormValues = (): DataSourceAttributes => { let credentials = this.state.auth.credentials; - if (this.state.auth.type === AuthType.UsernamePasswordType) { + const authType = this.state.auth.type; + + if (authType === AuthType.NoAuth) { + credentials = {}; + } else if (authType === AuthType.UsernamePasswordType) { credentials = { username: this.state.auth.credentials.username, password: this.state.auth.credentials.password, } as UsernamePasswordTypedContent; - } - if (this.state.auth.type === AuthType.SigV4) { + } else if (authType === AuthType.SigV4) { credentials = { 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; - } - if (this.state.auth.type === AuthType.NoAuth) { - credentials = {}; + } else { + const currentCredentials = (credentials ?? {}) as { [key: string]: string }; + credentials = extractRegisteredAuthTypeCredentials( + currentCredentials, + authType, + this.authenticationMethodRegistery + ); } return { 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 ee46af35531..eda0a7a7827 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 @@ -44,7 +44,7 @@ import { } from '../../../validation'; import { UpdatePasswordModal } from '../update_password_modal'; import { UpdateAwsCredentialModal } from '../update_aws_credential_modal'; -import { getDefaultAuthMethod } from '../../../utils'; +import { extractRegisteredAuthTypeCredentials, getDefaultAuthMethod } from '../../../utils'; export interface EditDataSourceProps { existingDataSource: DataSourceAttributes; @@ -362,6 +362,14 @@ export class EditDataSourceForm extends React.Component { + const registeredCredentials = {} as { [key: string]: string }; + const registeredCredentialField = + authenticationMethodRegistery.getAuthenticationMethod(authType)?.crendentialFormField ?? {}; + + Object.keys(registeredCredentialField).forEach((credentialFiled) => { + registeredCredentials[credentialFiled] = currentCredentialState[credentialFiled] ?? ''; + }); + + return registeredCredentials; +}; diff --git a/src/plugins/data_source_management/public/types.ts b/src/plugins/data_source_management/public/types.ts index b0dd6caf1a1..ecfe55f51dc 100644 --- a/src/plugins/data_source_management/public/types.ts +++ b/src/plugins/data_source_management/public/types.ts @@ -142,7 +142,11 @@ export interface DataSourceAttributes extends SavedObjectAttributes { endpoint?: string; auth: { type: AuthType; - credentials: UsernamePasswordTypedContent | SigV4Content | undefined; + credentials: + | UsernamePasswordTypedContent + | SigV4Content + | { [key: string]: string } + | undefined; }; } From 2a3003ee78ba2362a2a57a686114f9b4bc8b5bca Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Wed, 6 Mar 2024 01:26:56 +0000 Subject: [PATCH 2/7] [UT] Add unit test for extractRegisteredAuthTypeCredentials newly defined in untils.ts Signed-off-by: Xinrui Bai --- .../public/components/utils.test.ts | 45 ++++++++++++++++++- .../data_source_management/public/types.ts | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/plugins/data_source_management/public/components/utils.test.ts b/src/plugins/data_source_management/public/components/utils.test.ts index d19136d62f5..bfa2f26064c 100644 --- a/src/plugins/data_source_management/public/components/utils.test.ts +++ b/src/plugins/data_source_management/public/components/utils.test.ts @@ -7,6 +7,7 @@ import { createSingleDataSource, deleteDataSourceById, deleteMultipleDataSources, + extractRegisteredAuthTypeCredentials, getDataSourceById, getDataSources, getDefaultAuthMethod, @@ -31,7 +32,8 @@ import { usernamePasswordAuthMethod, } from '../types'; import { HttpStart } from 'opensearch-dashboards/public'; -import { AuthenticationMethodRegistery } from '../auth_registry'; +import { AuthenticationMethod, AuthenticationMethodRegistery } from '../auth_registry'; +import { deepEqual } from 'assert'; const { savedObjects } = coreMock.createStart(); @@ -272,4 +274,45 @@ describe('DataSourceManagement: Utils.ts', () => { expect(getDefaultAuthMethod(authenticationMethodRegistery)?.name).toBe(AuthType.NoAuth); }); }); + + describe('Check extractRegisteredAuthTypeCredentials method', () => { + test('Should extract credential field successfully', () => { + const authTypeToBeTested = 'Some Auth Type'; + + const authMethodToBeTested = { + name: authTypeToBeTested, + credentialSourceOption: { + value: authTypeToBeTested, + inputDisplay: 'some input', + }, + crendentialFormField: { + userNameRegistered: '', + passWordRegistered: '', + }, + } as AuthenticationMethod; + + const mockedCredentialState = { + userName: 'some userName', + passWord: 'some password', + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + } as { [key: string]: string }; + + const expectExtractedAuthCredentials = { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + }; + + const authenticationMethodRegistery = new AuthenticationMethodRegistery(); + authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTested); + + const registedAuthTypeCredentials = extractRegisteredAuthTypeCredentials( + mockedCredentialState, + authTypeToBeTested, + authenticationMethodRegistery + ); + + expect(deepEqual(registedAuthTypeCredentials, expectExtractedAuthCredentials)); + }); + }); }); diff --git a/src/plugins/data_source_management/public/types.ts b/src/plugins/data_source_management/public/types.ts index ecfe55f51dc..ecfc1fa05b4 100644 --- a/src/plugins/data_source_management/public/types.ts +++ b/src/plugins/data_source_management/public/types.ts @@ -121,7 +121,7 @@ export const sigV4CredentialField = { region: '', accessKey: '', secretKey: '', - service: '', + service: SigV4ServiceName.OpenSearch, }; export const sigV4AuthMethod = { From ef3f1eb54eb77b1a908c835daac9a045b3c9e0d7 Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Wed, 6 Mar 2024 01:58:25 +0000 Subject: [PATCH 3/7] [UT] add more test cases for util.ts Signed-off-by: Xinrui Bai --- .../public/components/utils.test.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/plugins/data_source_management/public/components/utils.test.ts b/src/plugins/data_source_management/public/components/utils.test.ts index bfa2f26064c..cefaa628bf2 100644 --- a/src/plugins/data_source_management/public/components/utils.test.ts +++ b/src/plugins/data_source_management/public/components/utils.test.ts @@ -314,5 +314,71 @@ describe('DataSourceManagement: Utils.ts', () => { expect(deepEqual(registedAuthTypeCredentials, expectExtractedAuthCredentials)); }); + + test('Should extract empty object when no credentialFormField registered ', () => { + const authTypeToBeTested = 'Some Auth Type'; + + const authMethodToBeTested = { + name: authTypeToBeTested, + credentialSourceOption: { + value: authTypeToBeTested, + inputDisplay: 'some input', + }, + } as AuthenticationMethod; + + const mockedCredentialState = { + userName: 'some userName', + passWord: 'some password', + } as { [key: string]: string }; + + const authenticationMethodRegistery = new AuthenticationMethodRegistery(); + authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTested); + + const registedAuthTypeCredentials = extractRegisteredAuthTypeCredentials( + mockedCredentialState, + authTypeToBeTested, + authenticationMethodRegistery + ); + + expect(deepEqual(registedAuthTypeCredentials, {})); + }); + + test('Should fill in empty value when credentail state not have registered field', () => { + const authTypeToBeTested = 'Some Auth Type'; + + const authMethodToBeTested = { + name: authTypeToBeTested, + credentialSourceOption: { + value: authTypeToBeTested, + inputDisplay: 'some input', + }, + crendentialFormField: { + userNameRegistered: '', + passWordRegistered: '', + }, + } as AuthenticationMethod; + + const mockedCredentialState = { + userName: 'some userName', + passWord: 'some password', + userNameRegistered: 'some filled in userName from registed auth credential form', + } as { [key: string]: string }; + + const expectExtractedAuthCredentials = { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: '', + }; + + const authenticationMethodRegistery = new AuthenticationMethodRegistery(); + authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTested); + + const registedAuthTypeCredentials = extractRegisteredAuthTypeCredentials( + mockedCredentialState, + authTypeToBeTested, + authenticationMethodRegistery + ); + + expect(deepEqual(registedAuthTypeCredentials, expectExtractedAuthCredentials)); + }); }); }); From 437315153fc0507994a62ee4193ac5c2a06fcfce Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Wed, 6 Mar 2024 05:58:50 +0000 Subject: [PATCH 4/7] [UT] Add test cases for datasource creation form and datasource edition form Signed-off-by: Xinrui Bai --- .../create_data_source_form.test.tsx | 53 +++++++++++ .../edit_form/edit_data_source_form.test.tsx | 90 ++++++++++++++++++- .../data_source_management/public/mocks.ts | 17 +++- .../data_source_management/public/types.ts | 2 +- 4 files changed, 157 insertions(+), 5 deletions(-) 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 1040a17584a..454766b37ee 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 @@ -369,6 +369,13 @@ describe('Datasource Management: Create Datasource form with registered Auth Typ const mockSubmitHandler = jest.fn(); const mockTestConnectionHandler = jest.fn(); const mockCancelHandler = jest.fn(); + const changeTextFieldValue = (testSubjId: string, value: string) => { + component.find(testSubjId).last().simulate('change', { + target: { + value, + }, + }); + }; test('should call registered crendential form at the first round when registered method is at the first place and username & password disabled', () => { const mockCredentialForm = jest.fn(); @@ -517,4 +524,50 @@ describe('Datasource Management: Create Datasource form with registered Auth Typ expect(mockCredentialForm).not.toHaveBeenCalled(); }); }); + + test('should create data source with registered Auth when all fields are valid', () => { + const mockCredentialForm = jest.fn(); + const authTypeToBeTested = 'Some Auth Type'; + const authMethodToBeTested = { + name: authTypeToBeTested, + credentialSourceOption: { + value: authTypeToBeTested, + inputDisplay: 'some input', + }, + credentialForm: mockCredentialForm, + crendentialFormField: { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + }, + } as AuthenticationMethod; + + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery(); + mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTested); + + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + + changeTextFieldValue(titleIdentifier, 'test'); + changeTextFieldValue(descriptionIdentifier, 'test'); + changeTextFieldValue(endpointIdentifier, 'https://test.com'); + + findTestSubject(component, 'createDataSourceButton').simulate('click'); + + expect(mockSubmitHandler).toHaveBeenCalled(); + }); }); 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 89d5c54cbc2..529cc3981e4 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 @@ -11,6 +11,7 @@ import { mockManagementPlugin, existingDatasourceNamesList, mockDataSourceAttributesWithNoAuth, + mockDataSourceAttributesWithRegisteredAuth, } from '../../../../mocks'; import { OpenSearchDashboardsContextProvider } from '../../../../../../opensearch_dashboards_react/public'; import { EditDataSourceForm } from './edit_data_source_form'; @@ -344,9 +345,25 @@ describe('Datasource Management: Edit Datasource Form', () => { describe('With Registered Authentication', () => { let component: ReactWrapper, React.Component<{}, {}, any>>; - const mockCredentialForm = jest.fn(); + const updateInputFieldAndBlur = ( + comp: ReactWrapper, React.Component<{}, {}, any>>, + fieldName: string, + updatedValue: string, + isTestSubj?: boolean + ) => { + const field = isTestSubj ? comp.find(fieldName) : comp.find({ name: fieldName }); + act(() => { + field.last().simulate('change', { target: { value: updatedValue } }); + }); + comp.update(); + act(() => { + field.last().simulate('focus').simulate('blur'); + }); + comp.update(); + }; test('should call registered crendential form', () => { + const mockedCredentialForm = jest.fn(); const authTypeToBeTested = 'Some Auth Type'; const authMethodToBeTest = { name: authTypeToBeTested, @@ -354,7 +371,7 @@ describe('With Registered Authentication', () => { value: authTypeToBeTested, inputDisplay: 'some input', }, - credentialForm: mockCredentialForm, + credentialForm: mockedCredentialForm, } as AuthenticationMethod; const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); @@ -380,6 +397,73 @@ describe('With Registered Authentication', () => { } ); - expect(mockCredentialForm).toHaveBeenCalled(); + expect(mockedCredentialForm).toHaveBeenCalled(); + }); + + test('should update the form with registered auth type on click save changes', async () => { + const mockedCredentialForm = jest.fn(); + const mockedSubmitHandler = jest.fn(); + const authTypeToBeTested = 'Some Auth Type'; + const authMethodToBeTest = { + name: authTypeToBeTested, + credentialSourceOption: { + value: authTypeToBeTested, + inputDisplay: 'some input', + }, + credentialForm: mockedCredentialForm, + crendentialFormField: { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + }, + } as AuthenticationMethod; + + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery(); + mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTest); + + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + + await new Promise((resolve) => + setTimeout(() => { + updateInputFieldAndBlur(component, descriptionFieldIdentifier, ''); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + resolve(); + }, 100) + ); + await new Promise((resolve) => + setTimeout(() => { + /* Updated description*/ + updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription'); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + + expect(component.find('[data-test-subj="datasource-edit-saveButton"]').exists()).toBe(true); + component.find('[data-test-subj="datasource-edit-saveButton"]').first().simulate('click'); + expect(mockedSubmitHandler).toHaveBeenCalled(); + resolve(); + }, 100) + ); }); }); diff --git a/src/plugins/data_source_management/public/mocks.ts b/src/plugins/data_source_management/public/mocks.ts index 71e0310cb87..01c38d029db 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -6,7 +6,7 @@ import React from 'react'; import { throwError } from 'rxjs'; import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; -import { AuthType } from './types'; +import { AuthType, DataSourceAttributes } from './types'; import { coreMock } from '../../../core/public/mocks'; import { DataSourceManagementPlugin, @@ -167,6 +167,21 @@ export const mockDataSourceAttributesWithNoAuth = { credentials: undefined, }, }; + +export const mockDataSourceAttributesWithRegisteredAuth = { + id: 'testRegisteredAuth', + title: 'create-test-ds-registered-auth', + description: 'jest testing', + endpoint: 'https://test.com', + auth: { + type: 'Some Auth Type', + credentials: { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + } as { [key: string]: string }, + }, +} as DataSourceAttributes; + export const getDataSourceByIdWithCredential = { attributes: { id: 'alpha-test', diff --git a/src/plugins/data_source_management/public/types.ts b/src/plugins/data_source_management/public/types.ts index ecfc1fa05b4..d8df61d304b 100644 --- a/src/plugins/data_source_management/public/types.ts +++ b/src/plugins/data_source_management/public/types.ts @@ -141,7 +141,7 @@ export interface DataSourceAttributes extends SavedObjectAttributes { description?: string; endpoint?: string; auth: { - type: AuthType; + type: AuthType | string; credentials: | UsernamePasswordTypedContent | SigV4Content From 8d5ccb74f0aadecda04689a2bcd6714d8fc6a9e6 Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Wed, 6 Mar 2024 21:29:43 +0000 Subject: [PATCH 5/7] [Token Exchange Unification + UT] Button create-data-source and save-changes enable / disable control and unit test cases Signed-off-by: Xinrui Bai --- .../create_data_source_form.test.tsx | 5 +- .../create_form/create_data_source_form.tsx | 9 ++- .../edit_form/edit_data_source_form.test.tsx | 10 +--- .../edit_form/edit_data_source_form.tsx | 11 +++- .../datasource_form_validation.test.ts | 60 ++++++++++++++++--- .../validation/datasource_form_validation.ts | 27 ++++++--- .../data_source_management/public/mocks.ts | 5 +- 7 files changed, 94 insertions(+), 33 deletions(-) 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 454766b37ee..043f8026784 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 @@ -527,11 +527,10 @@ describe('Datasource Management: Create Datasource form with registered Auth Typ test('should create data source with registered Auth when all fields are valid', () => { const mockCredentialForm = jest.fn(); - const authTypeToBeTested = 'Some Auth Type'; const authMethodToBeTested = { - name: authTypeToBeTested, + name: 'Some Auth Type', credentialSourceOption: { - value: authTypeToBeTested, + value: 'Some Auth Type', inputDisplay: 'some input', }, credentialForm: mockCredentialForm, 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 e33cdf5d8e5..ab5df7220c7 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 @@ -59,7 +59,7 @@ export interface CreateDataSourceState { description: string; endpoint: string; auth: { - type: AuthType; + type: AuthType | string; credentials: | UsernamePasswordTypedContent | SigV4Content @@ -110,7 +110,12 @@ export class CreateDataSourceForm extends React.Component< /* Validations */ isFormValid = () => { - return performDataSourceFormValidation(this.state, this.props.existingDatasourceNamesList, ''); + return performDataSourceFormValidation( + this.state, + this.props.existingDatasourceNamesList, + '', + this.authenticationMethodRegistery + ); }; /* Events */ 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 529cc3981e4..ef6a45e4b30 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 @@ -403,18 +403,14 @@ describe('With Registered Authentication', () => { test('should update the form with registered auth type on click save changes', async () => { const mockedCredentialForm = jest.fn(); const mockedSubmitHandler = jest.fn(); - const authTypeToBeTested = 'Some Auth Type'; const authMethodToBeTest = { - name: authTypeToBeTested, + name: 'Some Auth Type', credentialSourceOption: { - value: authTypeToBeTested, + value: 'Some Auth Type', inputDisplay: 'some input', }, credentialForm: mockedCredentialForm, - crendentialFormField: { - userNameRegistered: 'some filled in userName from registed auth credential form', - passWordRegistered: 'some filled in password from registed auth credential form', - }, + crendentialFormField: {}, } as AuthenticationMethod; const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); 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 eda0a7a7827..0089e8f12af 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 @@ -60,8 +60,12 @@ export interface EditDataSourceState { description: string; endpoint: string; auth: { - type: AuthType; - credentials: UsernamePasswordTypedContent | SigV4Content; + type: AuthType | string; + credentials: + | UsernamePasswordTypedContent + | SigV4Content + | { [key: string]: string } + | undefined; }; showUpdatePasswordModal: boolean; showUpdateAwsCredentialModal: boolean; @@ -155,7 +159,8 @@ export class EditDataSourceForm extends React.Component { describe('validate create/edit datasource', () => { + let authenticationMethodRegistery = new AuthenticationMethodRegistery(); let form: CreateDataSourceState | EditDataSourceState = { formErrorsByField: { ...defaultValidation }, title: '', @@ -25,35 +27,40 @@ describe('DataSourceManagement: Form Validation', () => { }, }; test('should fail validation when title is empty', () => { - const result = performDataSourceFormValidation(form, [], ''); + const result = performDataSourceFormValidation(form, [], '', authenticationMethodRegistery); expect(result).toBe(false); }); test('should fail validation on duplicate title', () => { form.title = 'test'; - const result = performDataSourceFormValidation(form, ['oldTitle', 'test'], 'oldTitle'); + const result = performDataSourceFormValidation( + form, + ['oldTitle', 'test'], + 'oldTitle', + authenticationMethodRegistery + ); expect(result).toBe(false); }); test('should fail validation when endpoint is not valid', () => { form.endpoint = mockDataSourceAttributesWithAuth.endpoint; - const result = performDataSourceFormValidation(form, [], ''); + const result = performDataSourceFormValidation(form, [], '', authenticationMethodRegistery); expect(result).toBe(false); }); test('should fail validation when username is empty', () => { form.endpoint = 'test'; - const result = performDataSourceFormValidation(form, [], ''); + const result = performDataSourceFormValidation(form, [], '', authenticationMethodRegistery); expect(result).toBe(false); }); test('should fail validation when password is empty', () => { form.auth.credentials.username = 'test'; form.auth.credentials.password = ''; - const result = performDataSourceFormValidation(form, [], ''); + const result = performDataSourceFormValidation(form, [], '', authenticationMethodRegistery); expect(result).toBe(false); }); test('should NOT fail validation on empty username/password when No Auth is selected', () => { form.auth.type = AuthType.NoAuth; form.title = 'test'; form.endpoint = mockDataSourceAttributesWithAuth.endpoint; - const result = performDataSourceFormValidation(form, [], ''); + const result = performDataSourceFormValidation(form, [], '', authenticationMethodRegistery); expect(result).toBe(true); }); test('should NOT fail validation on all fields', () => { @@ -61,7 +68,46 @@ describe('DataSourceManagement: Form Validation', () => { const result = performDataSourceFormValidation( form, [mockDataSourceAttributesWithAuth.title], - mockDataSourceAttributesWithAuth.title + mockDataSourceAttributesWithAuth.title, + authenticationMethodRegistery + ); + expect(result).toBe(true); + }); + test('should NOT fail validation when registered auth type is selected and related credential field not empty', () => { + authenticationMethodRegistery = new AuthenticationMethodRegistery(); + const authMethodToBeTested = { + name: 'Some Auth Type', + credentialSourceOption: { + value: 'Some Auth Type', + inputDisplay: 'some input', + }, + credentialForm: jest.fn(), + crendentialFormField: { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + }, + } as AuthenticationMethod; + + authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTested); + + const formWithRegisteredAuth: CreateDataSourceState | EditDataSourceState = { + formErrorsByField: { ...defaultValidation }, + title: 'test registered auth type', + description: '', + endpoint: 'https://test.com', + auth: { + type: 'Some Auth Type', + credentials: { + userNameRegistered: 'some filled in userName from registed auth credential form', + passWordRegistered: 'some filled in password from registed auth credential form', + }, + }, + }; + const result = performDataSourceFormValidation( + formWithRegisteredAuth, + [], + '', + authenticationMethodRegistery ); expect(result).toBe(true); }); diff --git a/src/plugins/data_source_management/public/components/validation/datasource_form_validation.ts b/src/plugins/data_source_management/public/components/validation/datasource_form_validation.ts index aecf6e51730..2ae5585e181 100644 --- a/src/plugins/data_source_management/public/components/validation/datasource_form_validation.ts +++ b/src/plugins/data_source_management/public/components/validation/datasource_form_validation.ts @@ -4,10 +4,11 @@ */ import { i18n } from '@osd/i18n'; -import { isValidUrl } from '../utils'; +import { extractRegisteredAuthTypeCredentials, isValidUrl } from '../utils'; import { CreateDataSourceState } from '../create_data_source_wizard/components/create_form/create_data_source_form'; import { EditDataSourceState } from '../edit_data_source/components/edit_form/edit_data_source_form'; import { AuthType } from '../../types'; +import { AuthenticationMethodRegistery } from '../../auth_registry'; export interface CreateEditDataSourceValidation { title: string[]; @@ -68,7 +69,8 @@ export const isTitleValid = ( export const performDataSourceFormValidation = ( formValues: CreateDataSourceState | EditDataSourceState, existingDatasourceNamesList: string[], - existingTitle: string + existingTitle: string, + authenticationMethodRegistery: AuthenticationMethodRegistery ) => { /* Title validation */ const titleValid = isTitleValid(formValues?.title, existingDatasourceNamesList, existingTitle); @@ -84,8 +86,9 @@ export const performDataSourceFormValidation = ( /* Credential Validation */ - /* Username & Password */ - if (formValues?.auth?.type === AuthType.UsernamePasswordType) { + if (formValues?.auth?.type === AuthType.NoAuth) { + return true; + } else if (formValues?.auth?.type === AuthType.UsernamePasswordType) { /* Username */ if (!formValues.auth.credentials?.username) { return false; @@ -95,9 +98,7 @@ export const performDataSourceFormValidation = ( if (!formValues.auth.credentials?.password) { return false; } - } - /* AWS SigV4 Content */ - if (formValues?.auth?.type === AuthType.SigV4) { + } else if (formValues?.auth?.type === AuthType.SigV4) { /* Access key */ if (!formValues.auth.credentials?.accessKey) { return false; @@ -117,6 +118,18 @@ export const performDataSourceFormValidation = ( if (!formValues.auth.credentials?.service) { return false; } + } else { + const registeredCredentials = extractRegisteredAuthTypeCredentials( + (formValues?.auth?.credentials ?? {}) as { [key: string]: string }, + formValues?.auth?.type ?? '', + authenticationMethodRegistery + ); + + for (const credentialValue of Object.values(registeredCredentials)) { + if (credentialValue.trim().length === 0) { + return false; + } + } } return true; diff --git a/src/plugins/data_source_management/public/mocks.ts b/src/plugins/data_source_management/public/mocks.ts index 01c38d029db..c08efe4e214 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -175,10 +175,7 @@ export const mockDataSourceAttributesWithRegisteredAuth = { endpoint: 'https://test.com', auth: { type: 'Some Auth Type', - credentials: { - userNameRegistered: 'some filled in userName from registed auth credential form', - passWordRegistered: 'some filled in password from registed auth credential form', - } as { [key: string]: string }, + credentials: {} as { [key: string]: string }, }, } as DataSourceAttributes; From 5216e5355c6c745888ce1fd17db3108d37e847cc Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Thu, 7 Mar 2024 01:57:22 +0000 Subject: [PATCH 6/7] Update changelog file Signed-off-by: Xinrui Bai --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64bb8085332..e32e499fed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Dynamic Configurations] Add support for dynamic application configurations ([#5855](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5855)) - [Workspace] Optional workspaces params in repository ([#5949](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5949)) - [Multiple Datasource] Refactoring create and edit form to use authentication registry ([#6002](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6002)) +- [Multiple Datasource] Send registered authentication credentials during the dataSource creation and editing processes ([#6049](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6049)) ### 🐛 Bug Fixes From a93ac4bde07edd800bc0e5abfdae3b9fee6a110b Mon Sep 17 00:00:00 2001 From: Xinrui Bai Date: Fri, 8 Mar 2024 05:06:16 +0000 Subject: [PATCH 7/7] Addressing comments Signed-off-by: Xinrui Bai --- CHANGELOG.md | 2 +- .../components/edit_form/edit_data_source_form.test.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87bab257cda..bc285435f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Handles auth methods from auth registry in DataSource SavedObjects Client Wrapper ([#6062](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6062)) - [Multiple Datasource] Expose a few properties for customize the appearance of the data source selector component ([#6057](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6057)) - [Multiple Datasource] Create data source menu component able to be mount to nav bar ([#6082](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6082)) -- [Multiple Datasource] Send registered authentication credentials during the dataSource creation and editing processes ([#6049](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6049)) +- [Multiple Datasource] Handle form values(request payload) if the selected type is available in the authentication registry ([#6049](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6049)) ### 🐛 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 ef6a45e4b30..25ea22ef274 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 @@ -440,7 +440,6 @@ describe('With Registered Authentication', () => { setTimeout(() => { updateInputFieldAndBlur(component, descriptionFieldIdentifier, ''); expect( - // @ts-ignore component.find(descriptionFormRowIdentifier).first().props().isInvalid ).toBeUndefined(); resolve(); @@ -451,7 +450,6 @@ describe('With Registered Authentication', () => { /* Updated description*/ updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription'); expect( - // @ts-ignore component.find(descriptionFormRowIdentifier).first().props().isInvalid ).toBeUndefined();