Skip to content

Commit

Permalink
[Multiple Datasource] Handle form values(request payload) if the sele…
Browse files Browse the repository at this point in the history
…cted type is available in the authentication registry. (#6049)

* [Token Exchange Unification] Send registered crendentail field for dataSource creation and dataSource edition

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* [UT] Add unit test for extractRegisteredAuthTypeCredentials newly defined in untils.ts

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* [UT] add more test cases for util.ts

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* [UT] Add test cases for datasource creation form and datasource edition form

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* [Token Exchange Unification + UT] Button create-data-source and save-changes enable / disable control and unit test cases

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* Update changelog file

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

* Addressing comments

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>

---------

Signed-off-by: Xinrui Bai <xinruiba@amazon.com>
  • Loading branch information
xinruiba committed Mar 8, 2024
1 parent 9f3a689 commit 6f4d814
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 37 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -517,4 +524,49 @@ 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 authMethodToBeTested = {
name: 'Some Auth Type',
credentialSourceOption: {
value: 'Some Auth Type',
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(
<CreateDataSourceForm
handleTestConnection={mockTestConnectionHandler}
handleSubmit={mockSubmitHandler}
handleCancel={mockCancelHandler}
existingDatasourceNamesList={['dup20']}
/>
),
{
wrappingComponent: OpenSearchDashboardsContextProvider,
wrappingComponentProps: {
services: mockedContext,
},
}
);

changeTextFieldValue(titleIdentifier, 'test');
changeTextFieldValue(descriptionIdentifier, 'test');
changeTextFieldValue(endpointIdentifier, 'https://test.com');

findTestSubject(component, 'createDataSourceButton').simulate('click');

expect(mockSubmitHandler).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -55,8 +59,12 @@ export interface CreateDataSourceState {
description: string;
endpoint: string;
auth: {
type: AuthType;
credentials: UsernamePasswordTypedContent | SigV4Content | undefined;
type: AuthType | string;
credentials:
| UsernamePasswordTypedContent
| SigV4Content
| { [key: string]: string }
| undefined;
};
}

Expand Down Expand Up @@ -102,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 */
Expand Down Expand Up @@ -298,22 +311,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -344,17 +345,33 @@ describe('Datasource Management: Edit Datasource Form', () => {

describe('With Registered Authentication', () => {
let component: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
const mockCredentialForm = jest.fn();
const updateInputFieldAndBlur = (
comp: ReactWrapper<any, Readonly<{}>, 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,
credentialSourceOption: {
value: authTypeToBeTested,
inputDisplay: 'some input',
},
credentialForm: mockCredentialForm,
credentialForm: mockedCredentialForm,
} as AuthenticationMethod;

const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
Expand All @@ -380,6 +397,67 @@ 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 authMethodToBeTest = {
name: 'Some Auth Type',
credentialSourceOption: {
value: 'Some Auth Type',
inputDisplay: 'some input',
},
credentialForm: mockedCredentialForm,
crendentialFormField: {},
} as AuthenticationMethod;

const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery();
mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethodToBeTest);

component = mount(
wrapWithIntl(
<EditDataSourceForm
existingDataSource={mockDataSourceAttributesWithRegisteredAuth}
existingDatasourceNamesList={existingDatasourceNamesList}
onDeleteDataSource={jest.fn()}
handleSubmit={mockedSubmitHandler}
handleTestConnection={jest.fn()}
displayToastMessage={jest.fn()}
/>
),
{
wrappingComponent: OpenSearchDashboardsContextProvider,
wrappingComponentProps: {
services: mockedContext,
},
}
);

await new Promise((resolve) =>
setTimeout(() => {
updateInputFieldAndBlur(component, descriptionFieldIdentifier, '');
expect(
component.find(descriptionFormRowIdentifier).first().props().isInvalid
).toBeUndefined();
resolve();
}, 100)
);
await new Promise((resolve) =>
setTimeout(() => {
/* Updated description*/
updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription');
expect(
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)
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -155,7 +159,8 @@ export class EditDataSourceForm extends React.Component<EditDataSourceProps, Edi
return performDataSourceFormValidation(
this.state,
this.props.existingDatasourceNamesList,
this.props.existingDataSource.title
this.props.existingDataSource.title,
this.authenticationMethodRegistery
);
};

Expand Down Expand Up @@ -362,6 +367,14 @@ export class EditDataSourceForm extends React.Component<EditDataSourceProps, Edi
delete formValues.auth.credentials?.password;
break;
default:
const currentCredentials = (this.state.auth.credentials ?? {}) as {
[key: string]: string;
};
formValues.auth.credentials = extractRegisteredAuthTypeCredentials(
currentCredentials,
this.state.auth.type,
this.authenticationMethodRegistery
);
break;
}

Expand Down
Loading

0 comments on commit 6f4d814

Please sign in to comment.