Skip to content

Commit

Permalink
[Multi Data Source] Render credential form registered from AuthMethod (
Browse files Browse the repository at this point in the history
…#6002)

* [TokenExchange] Render credential form registered from AuthMethod

Signed-off-by: Xinrui Bai <[email protected]>

* [UT] Add unittest to test registered credential form get rendered in create datasource page

Signed-off-by: Xinrui Bai <[email protected]>

* [UT] Update  test case descriptions

Signed-off-by: Xinrui Bai <[email protected]>

* [Token Exchange] improve code format in create datasource page

Signed-off-by: Xinrui Bai <[email protected]>

* [UT] Add unit test for edit datasource page

Signed-off-by: Xinrui Bai <[email protected]>

* Update changelog file

Signed-off-by: Xinrui Bai <[email protected]>

* update yml config file to original status

Signed-off-by: Xinrui Bai <[email protected]>

* Resolving comments

Signed-off-by: Xinrui Bai <[email protected]>

* [UT] Add more unit test to cover existing auth type and plugin registered Auth type scenario

Signed-off-by: Xinrui Bai <[email protected]>

* Resolving comments, update pmport path

Signed-off-by: Xinrui Bai <[email protected]>

---------

Signed-off-by: Xinrui Bai <[email protected]>
  • Loading branch information
xinruiba authored Mar 5, 2024
1 parent 9901bea commit 5ef10da
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Multiple Datasource] Hide/Show authentication method in multi data source plugin based on configuration ([#5916](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5916))
- [[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))

### 🐛 Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { EuiSuperSelectOption } from '@elastic/eui';
export interface AuthenticationMethod {
name: string;
credentialSourceOption: EuiSuperSelectOption<string>;
credentialForm?: React.JSX.Element;
credentialForm?: (
state: { [key: string]: any },
setState: React.Dispatch<React.SetStateAction<any>>
) => React.JSX.Element;
crendentialFormField?: { [key: string]: string };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
sigV4AuthMethod,
usernamePasswordAuthMethod,
} from '../../../../types';
import { AuthenticationMethodRegistery } from 'src/plugins/data_source_management/public/auth_registry';
import { AuthenticationMethod, AuthenticationMethodRegistery } from '../../../../auth_registry';

const titleIdentifier = '[data-test-subj="createDataSourceFormTitleField"]';
const descriptionIdentifier = `[data-test-subj="createDataSourceFormDescriptionField"]`;
Expand Down Expand Up @@ -363,3 +363,158 @@ describe('Datasource Management: Create Datasource form with different authType
});
});
});

describe('Datasource Management: Create Datasource form with registered Auth Type', () => {
let component: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
const mockSubmitHandler = jest.fn();
const mockTestConnectionHandler = jest.fn();
const mockCancelHandler = jest.fn();

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();
const authTypeToBeTested = 'Some Auth Type';
const authMethodToBeTested = {
name: authTypeToBeTested,
credentialSourceOption: {
value: authTypeToBeTested,
inputDisplay: 'some input',
},
credentialForm: mockCredentialForm,
} as AuthenticationMethod;

const authMethodCombinationsToBeTested = [
[authMethodToBeTested],
[authMethodToBeTested, sigV4AuthMethod],
[authMethodToBeTested, noAuthCredentialAuthMethod],
[authMethodToBeTested, noAuthCredentialAuthMethod, sigV4AuthMethod],
];

authMethodCombinationsToBeTested.forEach((authMethodCombination) => {
const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery();

authMethodCombination.forEach((authMethod) => {
mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethod);
});

component = mount(
wrapWithIntl(
<CreateDataSourceForm
handleTestConnection={mockTestConnectionHandler}
handleSubmit={mockSubmitHandler}
handleCancel={mockCancelHandler}
existingDatasourceNamesList={['dup20']}
/>
),
{
wrappingComponent: OpenSearchDashboardsContextProvider,
wrappingComponentProps: {
services: mockedContext,
},
}
);

expect(mockCredentialForm).toHaveBeenCalled();
});
});

test('should not call registered crendential form at the first round when registered method is at the first place and username & password enabled', () => {
const mockCredentialForm = jest.fn();
const authTypeToBeTested = 'Some Auth Type';
const authMethodToBeTested = {
name: authTypeToBeTested,
credentialSourceOption: {
value: authTypeToBeTested,
inputDisplay: 'some input',
},
credentialForm: mockCredentialForm,
} as AuthenticationMethod;

const authMethodCombinationsToBeTested = [
[authMethodToBeTested, usernamePasswordAuthMethod],
[authMethodToBeTested, usernamePasswordAuthMethod, sigV4AuthMethod],
[authMethodToBeTested, usernamePasswordAuthMethod, noAuthCredentialAuthMethod],
[
authMethodToBeTested,
usernamePasswordAuthMethod,
noAuthCredentialAuthMethod,
sigV4AuthMethod,
],
];

authMethodCombinationsToBeTested.forEach((authMethodCombination) => {
const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery();

authMethodCombination.forEach((authMethod) => {
mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethod);
});

component = mount(
wrapWithIntl(
<CreateDataSourceForm
handleTestConnection={mockTestConnectionHandler}
handleSubmit={mockSubmitHandler}
handleCancel={mockCancelHandler}
existingDatasourceNamesList={['dup20']}
/>
),
{
wrappingComponent: OpenSearchDashboardsContextProvider,
wrappingComponentProps: {
services: mockedContext,
},
}
);

expect(mockCredentialForm).not.toHaveBeenCalled();
});
});

test('should not call registered crendential form at the first round when registered method is not at the first place', () => {
const mockCredentialForm = jest.fn();
const authTypeToBeTested = 'Some Auth Type';
const authMethodToBeTested = {
name: authTypeToBeTested,
credentialSourceOption: {
value: authTypeToBeTested,
inputDisplay: 'some input',
},
credentialForm: mockCredentialForm,
} as AuthenticationMethod;

const authMethodCombinationsToBeTested = [
[sigV4AuthMethod, authMethodToBeTested],
[noAuthCredentialAuthMethod, authMethodToBeTested],
[noAuthCredentialAuthMethod, authMethodToBeTested, sigV4AuthMethod],
];

authMethodCombinationsToBeTested.forEach((authMethodCombination) => {
const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
mockedContext.authenticationMethodRegistery = new AuthenticationMethodRegistery();

authMethodCombination.forEach((authMethod) => {
mockedContext.authenticationMethodRegistery.registerAuthenticationMethod(authMethod);
});

component = mount(
wrapWithIntl(
<CreateDataSourceForm
handleTestConnection={mockTestConnectionHandler}
handleSubmit={mockSubmitHandler}
handleCancel={mockCancelHandler}
existingDatasourceNamesList={['dup20']}
/>
),
{
wrappingComponent: OpenSearchDashboardsContextProvider,
wrappingComponentProps: {
services: mockedContext,
},
}
);

expect(mockCredentialForm).not.toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { FormattedMessage } from '@osd/i18n/react';
import { AuthenticationMethodRegistery } from '../../../../auth_registry';
import { SigV4Content, SigV4ServiceName } from '../../../../../../data_source/common/data_sources';
import {
AuthType,
Expand Down Expand Up @@ -68,16 +69,17 @@ export class CreateDataSourceForm extends React.Component<

authOptions: Array<EuiSuperSelectOption<string>> = [];
isNoAuthOptionEnabled: boolean;
authenticationMethodRegistery: AuthenticationMethodRegistery;

constructor(props: CreateDataSourceProps, context: DataSourceManagementContextValue) {
super(props, context);

const authenticationMethodRegistery = context.services.authenticationMethodRegistery;
const registeredAuthMethods = authenticationMethodRegistery.getAllAuthenticationMethods();
const initialSelectedAuthMethod = getDefaultAuthMethod(authenticationMethodRegistery);
this.authenticationMethodRegistery = context.services.authenticationMethodRegistery;
const registeredAuthMethods = this.authenticationMethodRegistery.getAllAuthenticationMethods();
const initialSelectedAuthMethod = getDefaultAuthMethod(this.authenticationMethodRegistery);

this.isNoAuthOptionEnabled =
authenticationMethodRegistery.getAuthenticationMethod(AuthType.NoAuth) !== undefined;
this.authenticationMethodRegistery.getAuthenticationMethod(AuthType.NoAuth) !== undefined;

this.authOptions = registeredAuthMethods.map((authMethod) => {
return authMethod.credentialSourceOption;
Expand Down Expand Up @@ -322,6 +324,23 @@ export class CreateDataSourceForm extends React.Component<
};
};

handleStateChange = (state: any) => {
this.setState(state);
};

getCredentialFormFromRegistry = (authType: string) => {
const registeredAuthMethod = this.authenticationMethodRegistery.getAuthenticationMethod(
authType
);
const authCredentialForm = registeredAuthMethod?.credentialForm;

if (authCredentialForm !== undefined) {
return authCredentialForm(this.state, this.handleStateChange);
}

return null;
};

/* Render methods */

/* Render header*/
Expand Down Expand Up @@ -362,6 +381,8 @@ export class CreateDataSourceForm extends React.Component<
/* Render create new credentials*/
renderCreateNewCredentialsForm = (type: AuthType) => {
switch (type) {
case AuthType.NoAuth:
return null;
case AuthType.UsernamePasswordType:
return (
<>
Expand Down Expand Up @@ -498,7 +519,7 @@ export class CreateDataSourceForm extends React.Component<
);

default:
break;
return this.getCredentialFormFromRegistry(type);
}
};

Expand Down Expand Up @@ -632,13 +653,7 @@ export class CreateDataSourceForm extends React.Component<
</EuiFormRow>

{/* 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}
{this.renderCreateNewCredentialsForm(this.state.auth.type)}

<EuiSpacer size="xl" />
<EuiFormRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
sigV4AuthMethod,
usernamePasswordAuthMethod,
} from '../../../../types';
import { AuthenticationMethod, AuthenticationMethodRegistery } from '../../../../auth_registry';

const titleFieldIdentifier = 'dataSourceTitle';
const titleFormRowIdentifier = '[data-test-subj="editDataSourceTitleFormRow"]';
Expand Down Expand Up @@ -340,3 +341,45 @@ describe('Datasource Management: Edit Datasource Form', () => {
});
});
});

describe('With Registered Authentication', () => {
let component: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
const mockCredentialForm = jest.fn();

test('should call registered crendential form', () => {
const authTypeToBeTested = 'Some Auth Type';
const authMethodToBeTest = {
name: authTypeToBeTested,
credentialSourceOption: {
value: authTypeToBeTested,
inputDisplay: 'some input',
},
credentialForm: mockCredentialForm,
} as AuthenticationMethod;

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

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

expect(mockCredentialForm).toHaveBeenCalled();
});
});
Loading

0 comments on commit 5ef10da

Please sign in to comment.